Improve developer integration docs and Docker dev workflow.
CI / test (push) Successful in 7s

Add TypeScript frontend integration documentation, repository agent guidance files, Go-served frontend routing, and an advanced Docker Compose setup with watch mode plus BuildKit cache configuration.

Made-with: Cursor
This commit is contained in:
2026-03-01 11:57:22 +00:00
parent 242acd7fa6
commit f51126419c
8 changed files with 265 additions and 8 deletions
+1
View File
@@ -6,3 +6,4 @@ node_modules
**/*.log **/*.log
tmp tmp
dist dist
.docker/buildx-cache
+50
View File
@@ -0,0 +1,50 @@
# AGENTS.md
This file gives future coding agents a fast path map for this repository.
## Repository map
- API entrypoint: `cmd/api/main.go`
- HTTP routes/handlers: `internal/http/handlers.go`
- Core domain logic: `internal/app/service.go`
- In-memory persistence: `internal/store/`
- Auth utilities: `internal/auth/`
- Frontend static app: `web/`
- TypeScript API client: `libs/geo-api-client/`
- CI workflow: `.gitea/workflows/ci.yml`
- Architecture/planning docs: `docs/`
## Most common commands
From repo root:
```bash
go test ./...
go run ./cmd/api
docker compose up --build -d
docker compose down
```
TypeScript client:
```bash
cd libs/geo-api-client
bun install
bun test
bun run build
```
## Path conventions
- Use repository-relative paths in docs and comments (never absolute machine paths).
- Keep API route changes in `internal/http/handlers.go`.
- Keep business rule changes in `internal/app/service.go`.
- Keep frontend integration docs under `docs/`.
## Editing guidance for agents
- Prefer minimal changes and avoid unrelated refactors.
- Add tests when behavior changes.
- Verify Go tests after backend changes.
- Verify Bun tests after TS client changes.
- If CI fails due runner/network infrastructure, keep logs explicit in workflow output.
+9 -2
View File
@@ -1,6 +1,6 @@
# syntax=docker/dockerfile:1.7 # syntax=docker/dockerfile:1.7
FROM --platform=$BUILDPLATFORM golang:1.25-bookworm AS builder FROM --platform=$BUILDPLATFORM golang:1.25-bookworm AS base
ARG TARGETOS ARG TARGETOS
ARG TARGETARCH ARG TARGETARCH
@@ -15,12 +15,19 @@ RUN --mount=type=cache,target=/go/pkg/mod \
go mod download go mod download
COPY . . COPY . .
FROM base AS builder
RUN --mount=type=cache,target=/go/pkg/mod \ RUN --mount=type=cache,target=/go/pkg/mod \
--mount=type=cache,target=/root/.cache/go-build \ --mount=type=cache,target=/root/.cache/go-build \
CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} \ CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} \
go build -p "$(nproc)" -trimpath -ldflags="-s -w" -o /out/api ./cmd/api go build -p "$(nproc)" -trimpath -ldflags="-s -w" -o /out/api ./cmd/api
FROM gcr.io/distroless/static-debian12:nonroot FROM base AS dev
ENV ADDR=:8080
EXPOSE 8080
CMD ["go", "run", "./cmd/api"]
FROM gcr.io/distroless/static-debian12:nonroot AS runtime
WORKDIR /app WORKDIR /app
COPY --from=builder /out/api /app/api COPY --from=builder /out/api /app/api
+15 -5
View File
@@ -32,7 +32,7 @@ Optional environment variables:
Build and run the backend service: Build and run the backend service:
```bash ```bash
docker compose up --build -d COMPOSE_BAKE=true docker compose up --build -d
``` ```
Stop the service: Stop the service:
@@ -44,20 +44,26 @@ docker compose down
For local development with auto-rebuild on file changes: For local development with auto-rebuild on file changes:
```bash ```bash
docker compose up --watch COMPOSE_BAKE=true docker compose --profile dev up --watch
``` ```
Notes:
- `api` service uses the production `runtime` image target.
- `api-dev` profile uses the `dev` image target and Docker Compose watch.
- Build cache is persisted at `.docker/buildx-cache` via `cache_from`/`cache_to`.
## Frontend ## Frontend
Open `web/index.html` through a static server (recommended) or browser file URL. Frontend is served by the Go backend at runtime.
Example: Example:
```bash ```bash
python -m http.server 4173 go run ./cmd/api
``` ```
Then visit `http://localhost:4173/web/`. Then visit `http://localhost:8080/web/`.
## API client library ## API client library
@@ -70,6 +76,10 @@ bun test
bun run build bun run build
``` ```
Frontend TypeScript integration guide:
- `docs/typescript-frontend-integration.md`
## CI ## CI
Workflow: `.gitea/workflows/ci.yml` Workflow: `.gitea/workflows/ci.yml`
+50
View File
@@ -0,0 +1,50 @@
---
name: backend-fast-path
description: Quick orientation and execution paths for agents working in this repository.
---
# Skill: Backend Fast Path
Use this skill when implementing or debugging backend, frontend integration, TypeScript client, or CI behavior in this repository.
## Key paths
- API bootstrap: `cmd/api/main.go`
- HTTP layer: `internal/http/handlers.go`
- Service logic: `internal/app/service.go`
- Tests (Go): `internal/http/api_test.go`
- Frontend app (served by Go): `web/index.html`, `web/app.js`, `web/api.js`
- TS client source: `libs/geo-api-client/src/`
- TS client tests: `libs/geo-api-client/test/`
- CI pipeline: `.gitea/workflows/ci.yml`
- Dev docs: `README.md`, `docs/typescript-frontend-integration.md`, `docs/geo-auth-backend-plan.md`
## Task routing rules
1. API behavior changes
- Update `internal/app/service.go` first.
- Then wire request/response changes in `internal/http/handlers.go`.
- Add/update tests in `internal/http/api_test.go`.
2. Frontend integration changes
- Update TS client first in `libs/geo-api-client/src/`.
- Then update `web/` integration usage if needed.
- Document usage changes in `docs/typescript-frontend-integration.md`.
3. CI failures
- Inspect `.gitea/workflows/ci.yml`.
- Keep logs actionable.
- Preserve explicit warnings when infrastructure prevents source checkout/tests.
## Verification checklist
```bash
go test ./...
cd libs/geo-api-client && bun test && bun run build
```
If Docker-related changes are made:
```bash
docker compose config
```
+36 -1
View File
@@ -3,6 +3,11 @@ services:
build: build:
context: . context: .
dockerfile: Dockerfile dockerfile: Dockerfile
target: runtime
cache_from:
- type=local,src=.docker/buildx-cache
cache_to:
- type=local,dest=.docker/buildx-cache,mode=max
image: momswap-backend:latest image: momswap-backend:latest
container_name: momswap-backend-api container_name: momswap-backend-api
environment: environment:
@@ -11,7 +16,37 @@ services:
ports: ports:
- "8080:8080" - "8080:8080"
restart: unless-stopped restart: unless-stopped
api-dev:
profiles: ["dev"]
build:
context: .
dockerfile: Dockerfile
target: dev
cache_from:
- type=local,src=.docker/buildx-cache
cache_to:
- type=local,dest=.docker/buildx-cache,mode=max
image: momswap-backend:dev
container_name: momswap-backend-api-dev
environment:
ADDR: ":8080"
ADMIN_PUBLIC_KEY: "${ADMIN_PUBLIC_KEY:-}"
ports:
- "8080:8080"
restart: unless-stopped
develop: develop:
watch: watch:
- action: sync+restart
path: ./web
target: /src/web
- action: sync+restart
path: ./internal
target: /src/internal
- action: sync+restart
path: ./cmd
target: /src/cmd
- action: rebuild - action: rebuild
path: . path: ./go.mod
- action: rebuild
path: ./Dockerfile
+99
View File
@@ -0,0 +1,99 @@
# TypeScript Frontend Integration Guide
This document explains how frontend developers should integrate with the backend through the reusable TypeScript client at `libs/geo-api-client`.
## Goals
- Keep cryptographic signing logic in one place.
- Avoid duplicating API request code in frontend apps.
- Use a consistent local key storage format across projects.
## Client package location
- Source: `libs/geo-api-client/src`
- Entry point: `libs/geo-api-client/src/index.ts`
- Build output (browser ESM): `libs/geo-api-client/dist/index.js`
## Build and test the client
```bash
cd libs/geo-api-client
bun install
bun test
bun run build
```
## Public API (current)
### Class: `GeoApiClient`
Constructor:
- `new GeoApiClient(baseUrl, storage, storageKey?)`
Key methods:
- `ensureKeysInStorage()`
- `getStoredKeys()`
- `importKeys(keys)`
- `exportKeys()`
- `setAccessToken(token)`
- `createChallenge(publicKey)`
- `loginWithSignature(publicKey, privateKey)`
- `createInvitation(payload, inviterPrivateKey)`
- `registerWithInvitation(...)`
- `listCollections()`
- `createCollection(name)`
- `listFeatures(collectionId)`
- `createPointFeature(collectionId, lon, lat, properties)`
## Recommended integration flow
1. Create one `GeoApiClient` instance per backend base URL.
2. Call `ensureKeysInStorage()` when app initializes.
3. Use `loginWithSignature()` to obtain and set a bearer token.
4. Call collection/feature methods after authentication.
5. Use `importKeys`/`exportKeys` in profile settings UX.
## Example (TypeScript app)
```ts
import { GeoApiClient } from "../libs/geo-api-client/dist/index.js";
const storage = window.localStorage;
const storageLike = {
getItem: (key: string) => storage.getItem(key),
setItem: (key: string, value: string) => storage.setItem(key, value),
removeItem: (key: string) => storage.removeItem(key),
};
const client = new GeoApiClient("http://localhost:8080", storageLike);
const keys = await client.ensureKeysInStorage();
await client.loginWithSignature(keys.publicKey, keys.privateKey);
const created = await client.createCollection("My Places");
await client.createPointFeature(created.id, -16.6291, 28.4636, { name: "Santa Cruz" });
const features = await client.listFeatures(created.id);
console.log(features);
```
## Security notes
- Private keys are currently stored in browser storage via the selected storage adapter.
- If your frontend has stronger security requirements, wrap the storage adapter with your own encryption/decryption layer before calling `setItem`/`getItem`.
- Never send private keys to the backend.
## No-build frontend compatibility
For no-bundler apps, import the built ESM file:
```html
<script type="module">
import { GeoApiClient } from "../libs/geo-api-client/dist/index.js";
// use client...
</script>
```
The backend itself serves static UI at `/web/`, but this library can be consumed by any frontend runtime that supports `fetch`, `TextEncoder`, and ES modules.
+5
View File
@@ -21,6 +21,8 @@ func NewAPI(svc *app.Service) *API {
func (a *API) Routes() http.Handler { func (a *API) Routes() http.Handler {
mux := http.NewServeMux() mux := http.NewServeMux()
staticFiles := http.FileServer(http.Dir("web"))
mux.HandleFunc("GET /healthz", a.health) mux.HandleFunc("GET /healthz", a.health)
mux.HandleFunc("POST /v1/auth/challenge", a.createChallenge) mux.HandleFunc("POST /v1/auth/challenge", a.createChallenge)
mux.HandleFunc("POST /v1/auth/login", a.login) mux.HandleFunc("POST /v1/auth/login", a.login)
@@ -34,6 +36,9 @@ func (a *API) Routes() http.Handler {
mux.HandleFunc("GET /v1/collections/{id}/features", a.listFeatures) mux.HandleFunc("GET /v1/collections/{id}/features", a.listFeatures)
mux.HandleFunc("DELETE /v1/features/{id}", a.deleteFeature) mux.HandleFunc("DELETE /v1/features/{id}", a.deleteFeature)
mux.Handle("/web/", http.StripPrefix("/web/", staticFiles))
mux.Handle("/", http.RedirectHandler("/web/", http.StatusTemporaryRedirect))
return withCORS(mux) return withCORS(mux)
} }