This adds typed asset APIs to the geo client, covers the 3D/image upload-share flow in integration tests, and introduces a simple Leaflet web demo that places objects on map features and manages sharing visibility via backend links. Made-with: Cursor
This commit is contained in:
@@ -1,10 +1,23 @@
|
||||
import { generateKeyPair, signMessage, getPublicKeyFromPrivate } from "./keys";
|
||||
import { bytesToBase64Url, textToBytes } from "./encoding";
|
||||
import { DEFAULT_KEYS_STORAGE_KEY, loadKeys, saveKeys } from "./storage";
|
||||
import type { InvitationPayload, StorageLike, StoredKeys } from "./types";
|
||||
import type { AssetKind, AssetRecord, FeatureAsset, InvitationPayload, StorageLike, StoredKeys } from "./types";
|
||||
|
||||
type RequestInitLike = Omit<RequestInit, "body"> & { body?: unknown };
|
||||
|
||||
type FeatureProperties = {
|
||||
assets?: FeatureAsset[];
|
||||
[key: string]: unknown;
|
||||
};
|
||||
|
||||
type GeoFeature = {
|
||||
id: string;
|
||||
geometry?: {
|
||||
coordinates?: number[];
|
||||
};
|
||||
properties?: FeatureProperties;
|
||||
};
|
||||
|
||||
/**
|
||||
* TypeScript API client for Momswap Geo backend.
|
||||
* Handles Ed25519 key storage, auth flows, and GeoJSON collection/feature CRUD.
|
||||
@@ -201,7 +214,7 @@ export class GeoApiClient {
|
||||
}
|
||||
|
||||
/** List GeoJSON features in a collection. Must own the collection. */
|
||||
async listFeatures(collectionId: string): Promise<{ features: unknown[] }> {
|
||||
async listFeatures(collectionId: string): Promise<{ features: GeoFeature[] }> {
|
||||
return this.request(`/v1/collections/${collectionId}/features`, { method: "GET" });
|
||||
}
|
||||
|
||||
@@ -228,4 +241,47 @@ export class GeoApiClient {
|
||||
async deleteFeature(featureId: string): Promise<void> {
|
||||
return this.request(`/v1/features/${featureId}`, { method: "DELETE" });
|
||||
}
|
||||
|
||||
/**
|
||||
* Create or reuse an asset by checksum+ext for the authenticated owner and link it to a feature.
|
||||
* If checksum/ext already exists for this owner, backend returns existing asset and refreshes link metadata.
|
||||
*/
|
||||
async createOrLinkAsset(input: {
|
||||
featureId: string;
|
||||
checksum: string;
|
||||
ext: string;
|
||||
kind: AssetKind;
|
||||
mimeType?: string;
|
||||
sizeBytes?: number;
|
||||
name?: string;
|
||||
description?: string;
|
||||
isPublic?: boolean;
|
||||
}): Promise<{ asset: AssetRecord; link: string }> {
|
||||
return this.request("/v1/assets", { method: "POST", body: input });
|
||||
}
|
||||
|
||||
/** Request a signed upload URL for an existing asset. */
|
||||
async getAssetSignedUploadUrl(
|
||||
assetId: string,
|
||||
contentType?: string
|
||||
): Promise<{ url: string; method: string }> {
|
||||
return this.request(`/v1/assets/${assetId}/signed-upload`, {
|
||||
method: "POST",
|
||||
body: { contentType: contentType ?? "application/octet-stream" },
|
||||
});
|
||||
}
|
||||
|
||||
/** Update asset visibility (owner only). */
|
||||
async setAssetVisibility(assetId: string, isPublic: boolean): Promise<{ asset: AssetRecord; link: string }> {
|
||||
return this.request(`/v1/assets/${assetId}`, {
|
||||
method: "PATCH",
|
||||
body: { isPublic },
|
||||
});
|
||||
}
|
||||
|
||||
/** Build absolute download URL from service-relative link returned in feature assets. */
|
||||
resolveRelativeLink(path: string): string {
|
||||
if (!path.startsWith("/")) return `${this.baseUrl}/${path}`;
|
||||
return `${this.baseUrl}${path}`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
export { GeoApiClient } from "./GeoApiClient";
|
||||
export { generateKeyPair, signMessage, getPublicKeyFromPrivate } from "./keys";
|
||||
export { clearKeys, loadKeys, saveKeys, DEFAULT_KEYS_STORAGE_KEY } from "./storage";
|
||||
export type { InvitationPayload, StorageLike, StoredKeys } from "./types";
|
||||
export type {
|
||||
AssetKind,
|
||||
AssetRecord,
|
||||
AssetVisibility,
|
||||
FeatureAsset,
|
||||
InvitationPayload,
|
||||
StorageLike,
|
||||
StoredKeys,
|
||||
} from "./types";
|
||||
|
||||
@@ -16,3 +16,34 @@ export type StorageLike = {
|
||||
setItem(key: string, value: string): void;
|
||||
removeItem(key: string): void;
|
||||
};
|
||||
|
||||
export type AssetKind = "image" | "3d";
|
||||
|
||||
export type AssetVisibility = {
|
||||
isPublic: boolean;
|
||||
};
|
||||
|
||||
export type AssetRecord = {
|
||||
id: string;
|
||||
ownerKey: string;
|
||||
checksum: string;
|
||||
ext: string;
|
||||
kind: AssetKind;
|
||||
mimeType?: string;
|
||||
sizeBytes: number;
|
||||
objectKey: string;
|
||||
isPublic: boolean;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
};
|
||||
|
||||
export type FeatureAsset = {
|
||||
id: string;
|
||||
kind: AssetKind;
|
||||
name?: string;
|
||||
description?: string;
|
||||
checksum: string;
|
||||
ext: string;
|
||||
isPublic: boolean;
|
||||
link: string;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user