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:
@@ -6,3 +6,4 @@ node_modules
|
|||||||
**/*.log
|
**/*.log
|
||||||
tmp
|
tmp
|
||||||
dist
|
dist
|
||||||
|
.docker/buildx-cache
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -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`
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -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.
|
||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user