This replaces old momswap.produktor.duckdns.org references with tenerife.baby and refreshes the TypeScript integration guide to reflect the current asset upload, sharing, and relative-link flow. Made-with: Cursor
This commit is contained in:
@@ -51,13 +51,13 @@ sequenceDiagram
|
||||
|
||||
```bash
|
||||
# Step 1: Get challenge
|
||||
CHALLENGE=$(curl -s -X POST https://momswap.produktor.duckdns.org/v1/auth/challenge \
|
||||
CHALLENGE=$(curl -s -X POST https://tenerife.baby/v1/auth/challenge \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"publicKey":"txdkGKNdcZIEoQMJ0dqum3msjT6-2mO4yLVhtidRFJI"}')
|
||||
NONCE=$(echo "$CHALLENGE" | jq -r '.nonce')
|
||||
|
||||
# Step 2: Sign "login:$NONCE" with your private key, then:
|
||||
curl -s -X POST https://momswap.produktor.duckdns.org/v1/auth/login \
|
||||
curl -s -X POST https://tenerife.baby/v1/auth/login \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"publicKey\":\"txdkGKNdcZIEoQMJ0dqum3msjT6-2mO4yLVhtidRFJI\",\"nonce\":\"$NONCE\",\"signature\":\"<your_signature_base64url>\"}"
|
||||
```
|
||||
@@ -91,10 +91,10 @@ Registers a new user without an invitation. User proves key ownership by signing
|
||||
|
||||
```bash
|
||||
# Step 1: Fetch service key
|
||||
SERVICE_KEY=$(curl -s https://momswap.produktor.duckdns.org/v1/service-key | jq -r '.publicKey')
|
||||
SERVICE_KEY=$(curl -s https://tenerife.baby/v1/service-key | jq -r '.publicKey')
|
||||
|
||||
# Step 2: Sign $SERVICE_KEY with your private key, then:
|
||||
curl -s -X POST https://momswap.produktor.duckdns.org/v1/auth/register-by-signature \
|
||||
curl -s -X POST https://tenerife.baby/v1/auth/register-by-signature \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"publicKey\":\"<your_pubkey_base64url>\",\"signature\":\"<signature_base64url>\"}"
|
||||
```
|
||||
|
||||
@@ -8,9 +8,9 @@ This document explains how frontend developers should integrate with the backend
|
||||
|
||||
Primary backend URL for integration:
|
||||
|
||||
- `https://momswap.produktor.duckdns.org/`
|
||||
- `https://tenerife.baby/`
|
||||
|
||||
Deployment: API is proxied via reverse proxy from `https://momswap.produktor.duckdns.org` to backend at `172.17.0.1:8122`. Docker Compose maps port 8122 for the reverse proxy.
|
||||
Deployment: API is proxied via reverse proxy from `https://tenerife.baby` to backend at `172.17.0.1:8122`. Docker Compose maps port 8122 for the reverse proxy.
|
||||
|
||||
## Goals
|
||||
|
||||
@@ -33,60 +33,60 @@ bun test # unit + integration tests (docs flow)
|
||||
bun run build
|
||||
```
|
||||
|
||||
Integration tests in `test/integration.test.ts` cover the recommended flow: register, login, create collection, create point feature, list features.
|
||||
Integration tests in `test/integration.test.ts` cover both:
|
||||
|
||||
- Base flow: register, login, collection and feature CRUD
|
||||
- Asset flow: create/link asset, request signed upload URL, and toggle visibility
|
||||
|
||||
## Public API (current)
|
||||
|
||||
### Class: `GeoApiClient`
|
||||
|
||||
Source: [GeoApiClient.ts](https://git.produktor.io/momswap/backend/src/branch/main/libs/geo-api-client/src/GeoApiClient.ts)
|
||||
Source: `libs/geo-api-client/src/GeoApiClient.ts`
|
||||
|
||||
Constructor:
|
||||
|
||||
- [`new GeoApiClient(baseUrl, storage, storageKey?)`](https://git.produktor.io/momswap/backend/src/branch/main/libs/geo-api-client/src/GeoApiClient.ts#L14)
|
||||
- `new GeoApiClient(baseUrl, storage, storageKey?)`
|
||||
|
||||
Key methods:
|
||||
|
||||
**Key storage**
|
||||
- **Key storage**
|
||||
- `ensureKeysInStorage()`
|
||||
- `getStoredKeys()`
|
||||
- `derivePublicKey(privateKey)`
|
||||
- `importKeys(keys)`
|
||||
- `exportKeys()`
|
||||
- `setAccessToken(token)`
|
||||
|
||||
- [`ensureKeysInStorage()`](https://git.produktor.io/momswap/backend/src/branch/main/libs/geo-api-client/src/GeoApiClient.ts#L20) — Ensure a keypair exists; if none found, generate and save. Use on app init.
|
||||
- [`getStoredKeys()`](https://git.produktor.io/momswap/backend/src/branch/main/libs/geo-api-client/src/GeoApiClient.ts#L29) — Get stored keypair without generating. Returns null if none.
|
||||
- [`derivePublicKey(privateKey)`](https://git.produktor.io/momswap/backend/src/branch/main/libs/geo-api-client/src/GeoApiClient.ts#L33) — Derive public key from private key (Ed25519). Use when importing pk from backup/QR.
|
||||
- [`importKeys(keys)`](https://git.produktor.io/momswap/backend/src/branch/main/libs/geo-api-client/src/GeoApiClient.ts#L37) — Overwrite stored keypair (e.g. after import or restore from QR).
|
||||
- [`exportKeys()`](https://git.produktor.io/momswap/backend/src/branch/main/libs/geo-api-client/src/GeoApiClient.ts#L41) — Read stored keypair for export/backup.
|
||||
- [`setAccessToken(token)`](https://git.produktor.io/momswap/backend/src/branch/main/libs/geo-api-client/src/GeoApiClient.ts#L45) — Set bearer token for authenticated requests. Call after login.
|
||||
- **Auth**
|
||||
- `getServicePublicKey()`
|
||||
- `createChallenge(publicKey)`
|
||||
- `loginWithSignature(publicKey, privateKey)`
|
||||
- `registerBySigningServiceKey(publicKey, privateKey)`
|
||||
- `createInvitation(payload, inviterPrivateKey)`
|
||||
- `registerWithInvitation(...)`
|
||||
|
||||
**Auth**
|
||||
- **Collections and features**
|
||||
- `listCollections()`
|
||||
- `createCollection(name)`
|
||||
- `updateCollection(collectionId, name)`
|
||||
- `deleteCollection(collectionId)`
|
||||
- `listFeatures(collectionId)` (typed with `properties.assets`)
|
||||
- `createPointFeature(collectionId, lon, lat, properties)`
|
||||
- `deleteFeature(featureId)`
|
||||
|
||||
- [`getServicePublicKey()`](https://git.produktor.io/momswap/backend/src/branch/main/libs/geo-api-client/src/GeoApiClient.ts#L69) — Fetch API service public key (for register-by-signature).
|
||||
- [`createChallenge(publicKey)`](https://git.produktor.io/momswap/backend/src/branch/main/libs/geo-api-client/src/GeoApiClient.ts#L73) — Request login challenge; returns nonce and messageToSign.
|
||||
- [`loginWithSignature(publicKey, privateKey)`](https://git.produktor.io/momswap/backend/src/branch/main/libs/geo-api-client/src/GeoApiClient.ts#L86) — Login via challenge-response. Returns bearer token; stores it internally.
|
||||
- [`registerBySigningServiceKey(publicKey, privateKey)`](https://git.produktor.io/momswap/backend/src/branch/main/libs/geo-api-client/src/GeoApiClient.ts#L76) — Register without invitation by signing the API service key. 409 if already registered.
|
||||
- [`createInvitation(payload, inviterPrivateKey)`](https://git.produktor.io/momswap/backend/src/branch/main/libs/geo-api-client/src/GeoApiClient.ts#L101) — Create invitation for new users. Inviter signs the payload.
|
||||
- [`registerWithInvitation(...)`](https://git.produktor.io/momswap/backend/src/branch/main/libs/geo-api-client/src/GeoApiClient.ts#L114) — Register using an invitation. Proves key ownership and redeems invite.
|
||||
- **Assets (new)**
|
||||
- `createOrLinkAsset({...})` — create metadata or reuse existing asset by checksum/ext and link it to a feature
|
||||
- `getAssetSignedUploadUrl(assetId, contentType?)` — get signed `PUT` URL for binary upload
|
||||
- `setAssetVisibility(assetId, isPublic)` — owner toggles public/private access
|
||||
- `resolveRelativeLink(path)` — converts backend-relative asset links to absolute URLs for browser usage
|
||||
|
||||
**Collections**
|
||||
## Asset frontend contract
|
||||
|
||||
- [`listCollections()`](https://git.produktor.io/momswap/backend/src/branch/main/libs/geo-api-client/src/GeoApiClient.ts#L133) — List collections for the authenticated user.
|
||||
- [`createCollection(name)`](https://git.produktor.io/momswap/backend/src/branch/main/libs/geo-api-client/src/GeoApiClient.ts#L137) — Create a new collection. Returns id and name.
|
||||
- [`updateCollection(collectionId, name)`](https://git.produktor.io/momswap/backend/src/branch/main/libs/geo-api-client/src/GeoApiClient.ts#L140) — Rename a collection.
|
||||
- [`deleteCollection(collectionId)`](https://git.produktor.io/momswap/backend/src/branch/main/libs/geo-api-client/src/GeoApiClient.ts#L148) — Delete a collection and its features.
|
||||
|
||||
**Features**
|
||||
|
||||
- [`listFeatures(collectionId)`](https://git.produktor.io/momswap/backend/src/branch/main/libs/geo-api-client/src/GeoApiClient.ts#L153) — List GeoJSON features in a collection.
|
||||
- [`createPointFeature(collectionId, lon, lat, properties)`](https://git.produktor.io/momswap/backend/src/branch/main/libs/geo-api-client/src/GeoApiClient.ts#L156) — Add a Point. lon ∈ [-180,180], lat ∈ [-90,90]. Returns feature id.
|
||||
- [`deleteFeature(featureId)`](https://git.produktor.io/momswap/backend/src/branch/main/libs/geo-api-client/src/GeoApiClient.ts#L172) — Delete a feature.
|
||||
|
||||
## Asset API integration note
|
||||
|
||||
Asset endpoints are currently available at backend API level (`/v1/assets...`) and can be called from frontend apps directly with authenticated `fetch` requests.
|
||||
|
||||
Current frontend contract points:
|
||||
|
||||
- Feature list responses include linked media under `feature.properties.assets`.
|
||||
- Each asset includes a backend-relative download path (`link`) like `/v1/assets/{id}/download`.
|
||||
- Frontend should use this relative path and avoid constructing direct S3 URLs.
|
||||
- Feature responses include linked assets in `feature.properties.assets`.
|
||||
- Asset item fields include: `id`, `kind`, `name`, `description`, `checksum`, `ext`, `isPublic`, `link`.
|
||||
- `link` is always backend-relative (for example `/v1/assets/{id}/download`).
|
||||
- Frontend should call `resolveRelativeLink(link)` and must not build direct S3 URLs.
|
||||
|
||||
## Recommended integration flow
|
||||
|
||||
@@ -94,8 +94,15 @@ Current frontend contract points:
|
||||
2. Call `ensureKeysInStorage()` when app initializes.
|
||||
3. If not yet registered: call `registerBySigningServiceKey(publicKey, privateKey)` (signs the API service key and publishes your public key).
|
||||
4. Use `loginWithSignature()` to obtain and set a bearer token.
|
||||
5. Call collection/feature methods after authentication.
|
||||
6. Use `importKeys`/`exportKeys` in profile settings UX.
|
||||
5. Create collection and map features.
|
||||
6. For media upload:
|
||||
- compute file checksum
|
||||
- call `createOrLinkAsset`
|
||||
- call `getAssetSignedUploadUrl`
|
||||
- upload file to signed URL
|
||||
7. Render and share assets from `properties.assets` links.
|
||||
8. Use `setAssetVisibility` to toggle sharing.
|
||||
9. Use `importKeys`/`exportKeys` in profile settings UX.
|
||||
|
||||
## Registration by signing service key
|
||||
|
||||
@@ -119,7 +126,7 @@ const storageLike = {
|
||||
removeItem: (key: string) => storage.removeItem(key),
|
||||
};
|
||||
|
||||
const client = new GeoApiClient("https://momswap.produktor.duckdns.org", storageLike);
|
||||
const client = new GeoApiClient("https://tenerife.baby", storageLike);
|
||||
const keys = await client.ensureKeysInStorage();
|
||||
|
||||
// Register (ignored if already registered); then login
|
||||
@@ -131,8 +138,29 @@ try {
|
||||
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 feature = await client.createPointFeature(created.id, -16.6291, 28.4636, { name: "Santa Cruz" });
|
||||
|
||||
// 3D/image asset flow
|
||||
const asset = await client.createOrLinkAsset({
|
||||
featureId: feature.id,
|
||||
checksum: "sha256hex...",
|
||||
ext: "glb",
|
||||
kind: "3d",
|
||||
mimeType: "model/gltf-binary",
|
||||
sizeBytes: 1024,
|
||||
name: "Palm Tree",
|
||||
description: "Low-poly model",
|
||||
isPublic: true,
|
||||
});
|
||||
const signed = await client.getAssetSignedUploadUrl(asset.asset.id, "model/gltf-binary");
|
||||
// fetch(signed.url, { method: signed.method, body: file })
|
||||
|
||||
const features = await client.listFeatures(created.id);
|
||||
const firstAsset = features.features[0]?.properties?.assets?.[0];
|
||||
if (firstAsset) {
|
||||
const shareUrl = client.resolveRelativeLink(firstAsset.link);
|
||||
console.log("Share URL:", shareUrl);
|
||||
}
|
||||
console.log(features);
|
||||
```
|
||||
|
||||
|
||||
Reference in New Issue
Block a user