This updates developer docs and web demos to use backend upload endpoints, adds a client upload helper, and aligns integration tests with the no-direct-MinIO URL flow. Made-with: Cursor
This commit is contained in:
Vendored
+27
-1
@@ -642,10 +642,36 @@ class GeoApiClient {
|
||||
return this.request("/v1/assets", { method: "POST", body: input });
|
||||
}
|
||||
async getAssetSignedUploadUrl(assetId, contentType) {
|
||||
return this.request(`/v1/assets/${assetId}/signed-upload`, {
|
||||
const response = await this.request(`/v1/assets/${assetId}/signed-upload`, {
|
||||
method: "POST",
|
||||
body: { contentType: contentType ?? "application/octet-stream" }
|
||||
});
|
||||
if (response.url.startsWith("/")) {
|
||||
response.url = this.resolveRelativeLink(response.url);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
async uploadAssetBinary(assetId, payload, contentType = "application/octet-stream") {
|
||||
const upload = await this.getAssetSignedUploadUrl(assetId, contentType);
|
||||
const headers = new Headers;
|
||||
if (contentType) {
|
||||
headers.set("Content-Type", contentType);
|
||||
}
|
||||
if (this.accessToken) {
|
||||
headers.set("Authorization", `Bearer ${this.accessToken}`);
|
||||
}
|
||||
const res = await fetch(upload.url, {
|
||||
method: upload.method || "PUT",
|
||||
headers,
|
||||
body: payload
|
||||
});
|
||||
if (!res.ok) {
|
||||
const maybeJson = await res.json().catch(() => ({}));
|
||||
let msg = maybeJson.error ?? `Upload failed (${res.status})`;
|
||||
if (maybeJson.hint)
|
||||
msg += `. ${maybeJson.hint}`;
|
||||
throw new Error(msg);
|
||||
}
|
||||
}
|
||||
async setAssetVisibility(assetId, isPublic) {
|
||||
return this.request(`/v1/assets/${assetId}`, {
|
||||
|
||||
@@ -260,7 +260,10 @@ export class GeoApiClient {
|
||||
return this.request("/v1/assets", { method: "POST", body: input });
|
||||
}
|
||||
|
||||
/** Request a signed upload URL for an existing asset. */
|
||||
/**
|
||||
* Request a backend upload URL for an existing asset.
|
||||
* Backend returns a service URL (for example /v1/assets/{id}/upload), not a direct storage endpoint.
|
||||
*/
|
||||
async getAssetSignedUploadUrl(
|
||||
assetId: string,
|
||||
contentType?: string
|
||||
@@ -275,6 +278,36 @@ export class GeoApiClient {
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload file/binary for an existing asset through backend upload endpoint.
|
||||
* Uses getAssetSignedUploadUrl internally and executes the upload request.
|
||||
*/
|
||||
async uploadAssetBinary(
|
||||
assetId: string,
|
||||
payload: BodyInit,
|
||||
contentType = "application/octet-stream"
|
||||
): Promise<void> {
|
||||
const upload = await this.getAssetSignedUploadUrl(assetId, contentType);
|
||||
const headers = new Headers();
|
||||
if (contentType) {
|
||||
headers.set("Content-Type", contentType);
|
||||
}
|
||||
if (this.accessToken) {
|
||||
headers.set("Authorization", `Bearer ${this.accessToken}`);
|
||||
}
|
||||
const res = await fetch(upload.url, {
|
||||
method: upload.method || "PUT",
|
||||
headers,
|
||||
body: payload,
|
||||
});
|
||||
if (!res.ok) {
|
||||
const maybeJson = (await res.json().catch(() => ({}))) as { error?: string; hint?: string };
|
||||
let msg = maybeJson.error ?? `Upload failed (${res.status})`;
|
||||
if (maybeJson.hint) msg += `. ${maybeJson.hint}`;
|
||||
throw new Error(msg);
|
||||
}
|
||||
}
|
||||
|
||||
/** Update asset visibility (owner only). */
|
||||
async setAssetVisibility(assetId: string, isPublic: boolean): Promise<{ asset: AssetRecord; link: string }> {
|
||||
return this.request(`/v1/assets/${assetId}`, {
|
||||
|
||||
@@ -231,7 +231,12 @@ async function createMockServer(): Promise<{ url: string; server: ReturnType<typ
|
||||
// POST /v1/assets/:id/signed-upload
|
||||
if (method === "POST" && path.match(/^\/v1\/assets\/[^/]+\/signed-upload$/)) {
|
||||
const id = path.split("/")[3]!;
|
||||
return Response.json({ url: `http://upload.local/${id}`, method: "PUT" });
|
||||
return Response.json({ url: `/v1/assets/${id}/upload`, method: "PUT" });
|
||||
}
|
||||
|
||||
// PUT /v1/assets/:id/upload
|
||||
if (method === "PUT" && path.match(/^\/v1\/assets\/[^/]+\/upload$/)) {
|
||||
return new Response(null, { status: 204 });
|
||||
}
|
||||
|
||||
// PATCH /v1/assets/:id
|
||||
@@ -325,7 +330,9 @@ describe("GeoApiClient integration (docs flow)", () => {
|
||||
|
||||
const upload = await client.getAssetSignedUploadUrl(createdAsset.asset.id, "model/gltf-binary");
|
||||
expect(upload.method).toBe("PUT");
|
||||
expect(upload.url).toContain(createdAsset.asset.id);
|
||||
expect(upload.url).toContain(`/v1/assets/${createdAsset.asset.id}/upload`);
|
||||
|
||||
await client.uploadAssetBinary(createdAsset.asset.id, new Blob(["fake-glb"]), "model/gltf-binary");
|
||||
|
||||
const toggled = await client.setAssetVisibility(createdAsset.asset.id, false);
|
||||
expect(toggled.asset.isPublic).toBe(false);
|
||||
|
||||
Reference in New Issue
Block a user