Add MapLibre demo and route uploads through backend.
CI / test (push) Successful in 5s

This introduces a MapLibre GL + Three.js web demo for object placement and sharing, and changes asset upload flow to use backend upload endpoints so clients no longer receive direct MinIO URLs.

Made-with: Cursor
This commit is contained in:
2026-03-02 21:48:08 +00:00
parent 6cbaab73dc
commit e981a334ea
10 changed files with 645 additions and 23 deletions
+32 -5
View File
@@ -57,6 +57,13 @@ function kindFromExt(ext) {
return ext === "gltf" || ext === "glb" ? "3d" : "image";
}
function isLikelyInternalHostname(hostname) {
if (!hostname) return false;
if (hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1") return true;
if (hostname.endsWith(".local") || hostname.endsWith(".internal")) return true;
return hostname.includes("minio") || hostname.includes("docker") || hostname.includes("kubernetes");
}
async function sha256Hex(file) {
const buffer = await file.arrayBuffer();
const digest = await crypto.subtle.digest("SHA-256", buffer);
@@ -202,11 +209,31 @@ async function createFeatureAndUpload() {
isPublic: true,
});
const signedUpload = await client.getAssetSignedUploadUrl(created.asset.id, file.type || "application/octet-stream");
const uploadRes = await fetch(signedUpload.url, {
method: signedUpload.method || "PUT",
headers: file.type ? { "Content-Type": file.type } : undefined,
body: file,
});
let signedHost = "";
try {
signedHost = new URL(signedUpload.url).hostname;
} catch {
signedHost = "";
}
if (signedHost && isLikelyInternalHostname(signedHost) && signedHost !== window.location.hostname) {
throw new Error(
`Upload URL host "${signedHost}" is not browser-reachable from this page. ` +
`Configure S3 endpoint/signing host to a public domain (for example s3.tenerife.baby) or proxy uploads through the API.`
);
}
let uploadRes;
try {
uploadRes = await fetch(signedUpload.url, {
method: signedUpload.method || "PUT",
headers: file.type ? { "Content-Type": file.type } : undefined,
body: file,
});
} catch (error) {
throw new Error(
`Network error while uploading to signed URL. ` +
`Check that object storage endpoint is publicly reachable and CORS allows browser PUT requests.`
);
}
if (!uploadRes.ok) {
throw new Error(`Upload failed with status ${uploadRes.status}`);
}