Wire API services to PostGIS in docker-compose, broaden CORS handling for cross-origin clients, and align developer docs with the production momswap URL. Made-with: Cursor
This commit is contained in:
@@ -20,7 +20,9 @@ go test ./...
|
|||||||
go run ./cmd/api
|
go run ./cmd/api
|
||||||
```
|
```
|
||||||
|
|
||||||
Server default: `http://localhost:8080`.
|
Primary deployed base URL: `https://momswap.produktor.duckdns.org/`.
|
||||||
|
|
||||||
|
Local default (for development): `http://localhost:8080`.
|
||||||
|
|
||||||
Optional environment variables:
|
Optional environment variables:
|
||||||
|
|
||||||
@@ -35,6 +37,11 @@ Build and run the backend service:
|
|||||||
COMPOSE_BAKE=true docker compose up --build -d
|
COMPOSE_BAKE=true docker compose up --build -d
|
||||||
```
|
```
|
||||||
|
|
||||||
|
This starts:
|
||||||
|
|
||||||
|
- `db` (`postgis/postgis`) on `5432`
|
||||||
|
- `api` on `8080`, wired with `DATABASE_URL` to the `db` service
|
||||||
|
|
||||||
Stop the service:
|
Stop the service:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -52,6 +59,7 @@ Notes:
|
|||||||
- `api` service uses the production `runtime` image target.
|
- `api` service uses the production `runtime` image target.
|
||||||
- `api-dev` profile uses the `dev` image target and Docker Compose watch.
|
- `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`.
|
- Build cache is persisted at `.docker/buildx-cache` via `cache_from`/`cache_to`.
|
||||||
|
- DB defaults can be overridden via `POSTGRES_DB`, `POSTGRES_USER`, `POSTGRES_PASSWORD`.
|
||||||
|
|
||||||
## Frontend
|
## Frontend
|
||||||
|
|
||||||
@@ -63,7 +71,10 @@ Example:
|
|||||||
go run ./cmd/api
|
go run ./cmd/api
|
||||||
```
|
```
|
||||||
|
|
||||||
Then visit `http://localhost:8080/web/`.
|
Then visit:
|
||||||
|
|
||||||
|
- Production: `https://momswap.produktor.duckdns.org/web/`
|
||||||
|
- Local: `http://localhost:8080/web/`
|
||||||
|
|
||||||
## API client library
|
## API client library
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,23 @@
|
|||||||
services:
|
services:
|
||||||
|
db:
|
||||||
|
image: postgis/postgis:17-3.5
|
||||||
|
container_name: momswap-backend-db
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: "${POSTGRES_DB:-momswap}"
|
||||||
|
POSTGRES_USER: "${POSTGRES_USER:-momswap}"
|
||||||
|
POSTGRES_PASSWORD: "${POSTGRES_PASSWORD:-momswap}"
|
||||||
|
ports:
|
||||||
|
- "5432:5432"
|
||||||
|
volumes:
|
||||||
|
- ./var/posrgres:/var/lib/postgresql/data
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-momswap} -d ${POSTGRES_DB:-momswap}"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 10
|
||||||
|
start_period: 10s
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
api:
|
api:
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
@@ -13,6 +32,10 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
ADDR: ":8080"
|
ADDR: ":8080"
|
||||||
ADMIN_PUBLIC_KEY: "${ADMIN_PUBLIC_KEY:-}"
|
ADMIN_PUBLIC_KEY: "${ADMIN_PUBLIC_KEY:-}"
|
||||||
|
DATABASE_URL: "postgres://${POSTGRES_USER:-momswap}:${POSTGRES_PASSWORD:-momswap}@db:5432/${POSTGRES_DB:-momswap}?sslmode=disable"
|
||||||
|
depends_on:
|
||||||
|
db:
|
||||||
|
condition: service_healthy
|
||||||
ports:
|
ports:
|
||||||
- "8080:8080"
|
- "8080:8080"
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
@@ -32,6 +55,10 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
ADDR: ":8080"
|
ADDR: ":8080"
|
||||||
ADMIN_PUBLIC_KEY: "${ADMIN_PUBLIC_KEY:-}"
|
ADMIN_PUBLIC_KEY: "${ADMIN_PUBLIC_KEY:-}"
|
||||||
|
DATABASE_URL: "postgres://${POSTGRES_USER:-momswap}:${POSTGRES_PASSWORD:-momswap}@db:5432/${POSTGRES_DB:-momswap}?sslmode=disable"
|
||||||
|
depends_on:
|
||||||
|
db:
|
||||||
|
condition: service_healthy
|
||||||
ports:
|
ports:
|
||||||
- "8080:8080"
|
- "8080:8080"
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
@@ -50,3 +77,4 @@ services:
|
|||||||
path: ./go.mod
|
path: ./go.mod
|
||||||
- action: rebuild
|
- action: rebuild
|
||||||
path: ./Dockerfile
|
path: ./Dockerfile
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,10 @@
|
|||||||
|
|
||||||
This document explains how frontend developers should integrate with the backend through the reusable TypeScript client at `libs/geo-api-client`.
|
This document explains how frontend developers should integrate with the backend through the reusable TypeScript client at `libs/geo-api-client`.
|
||||||
|
|
||||||
|
Primary backend URL for integration:
|
||||||
|
|
||||||
|
- `https://momswap.produktor.duckdns.org/`
|
||||||
|
|
||||||
## Goals
|
## Goals
|
||||||
|
|
||||||
- Keep cryptographic signing logic in one place.
|
- Keep cryptographic signing logic in one place.
|
||||||
@@ -68,7 +72,7 @@ const storageLike = {
|
|||||||
removeItem: (key: string) => storage.removeItem(key),
|
removeItem: (key: string) => storage.removeItem(key),
|
||||||
};
|
};
|
||||||
|
|
||||||
const client = new GeoApiClient("http://localhost:8080", storageLike);
|
const client = new GeoApiClient("https://momswap.produktor.duckdns.org", storageLike);
|
||||||
|
|
||||||
const keys = await client.ensureKeysInStorage();
|
const keys = await client.ensureKeysInStorage();
|
||||||
await client.loginWithSignature(keys.publicKey, keys.privateKey);
|
await client.loginWithSignature(keys.publicKey, keys.privateKey);
|
||||||
@@ -97,3 +101,5 @@ For no-bundler apps, import the built ESM file:
|
|||||||
```
|
```
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
|
For local development you can switch the client base URL to `http://localhost:8080`.
|
||||||
|
|||||||
@@ -45,8 +45,15 @@ func (a *API) Routes() http.Handler {
|
|||||||
func withCORS(next http.Handler) http.Handler {
|
func withCORS(next http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||||
w.Header().Set("Access-Control-Allow-Headers", "Authorization, Content-Type")
|
w.Header().Set("Vary", "Origin, Access-Control-Request-Method, Access-Control-Request-Headers")
|
||||||
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PATCH, DELETE, OPTIONS")
|
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD")
|
||||||
|
w.Header().Set("Access-Control-Max-Age", "86400")
|
||||||
|
requestHeaders := r.Header.Get("Access-Control-Request-Headers")
|
||||||
|
if requestHeaders != "" {
|
||||||
|
w.Header().Set("Access-Control-Allow-Headers", requestHeaders)
|
||||||
|
} else {
|
||||||
|
w.Header().Set("Access-Control-Allow-Headers", "Authorization, Content-Type, Accept, Origin, X-Requested-With")
|
||||||
|
}
|
||||||
if r.Method == http.MethodOptions {
|
if r.Method == http.MethodOptions {
|
||||||
w.WriteHeader(http.StatusNoContent)
|
w.WriteHeader(http.StatusNoContent)
|
||||||
return
|
return
|
||||||
|
|||||||
Reference in New Issue
Block a user