Checkpoint: Migration complète vers authentification locale (JWT + bcrypt) : schéma DB (10 tables), routes tRPC (auth, users, etablissements, inventaire, capex, opex, parametres), page de login avec branding Itinova/Santinova, protection des routes, onglets Établissements et Utilisateurs branchés sur tRPC, import inventaire/établissements/utilisateurs via tRPC, 14 tests Vitest passants.
This commit is contained in:
97
server/storage.ts
Normal file
97
server/storage.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
// Preconfigured storage helpers for Manus WebDev templates
|
||||
// Uploads via Forge Server presigned URL to S3 (PUT direct).
|
||||
// Downloads return /manus-storage/{key} paths served via 307 redirect.
|
||||
|
||||
import { ENV } from "./_core/env";
|
||||
|
||||
function getForgeConfig() {
|
||||
const forgeUrl = ENV.forgeApiUrl;
|
||||
const forgeKey = ENV.forgeApiKey;
|
||||
|
||||
if (!forgeUrl || !forgeKey) {
|
||||
throw new Error(
|
||||
"Storage config missing: set BUILT_IN_FORGE_API_URL and BUILT_IN_FORGE_API_KEY",
|
||||
);
|
||||
}
|
||||
|
||||
return { forgeUrl: forgeUrl.replace(/\/+$/, ""), forgeKey };
|
||||
}
|
||||
|
||||
function normalizeKey(relKey: string): string {
|
||||
return relKey.replace(/^\/+/, "");
|
||||
}
|
||||
|
||||
function appendHashSuffix(relKey: string): string {
|
||||
const hash = crypto.randomUUID().replace(/-/g, "").slice(0, 8);
|
||||
const lastDot = relKey.lastIndexOf(".");
|
||||
if (lastDot === -1) return `${relKey}_${hash}`;
|
||||
return `${relKey.slice(0, lastDot)}_${hash}${relKey.slice(lastDot)}`;
|
||||
}
|
||||
|
||||
export async function storagePut(
|
||||
relKey: string,
|
||||
data: Buffer | Uint8Array | string,
|
||||
contentType = "application/octet-stream",
|
||||
): Promise<{ key: string; url: string }> {
|
||||
const { forgeUrl, forgeKey } = getForgeConfig();
|
||||
const key = appendHashSuffix(normalizeKey(relKey));
|
||||
|
||||
// 1. Get presigned PUT URL from Forge
|
||||
const presignUrl = new URL("v1/storage/presign/put", forgeUrl + "/");
|
||||
presignUrl.searchParams.set("path", key);
|
||||
|
||||
const presignResp = await fetch(presignUrl, {
|
||||
headers: { Authorization: `Bearer ${forgeKey}` },
|
||||
});
|
||||
|
||||
if (!presignResp.ok) {
|
||||
const msg = await presignResp.text().catch(() => presignResp.statusText);
|
||||
throw new Error(`Storage presign failed (${presignResp.status}): ${msg}`);
|
||||
}
|
||||
|
||||
const { url: s3Url } = (await presignResp.json()) as { url: string };
|
||||
if (!s3Url) throw new Error("Forge returned empty presign URL");
|
||||
|
||||
// 2. PUT file directly to S3
|
||||
const blob =
|
||||
typeof data === "string"
|
||||
? new Blob([data], { type: contentType })
|
||||
: new Blob([data as any], { type: contentType });
|
||||
|
||||
const uploadResp = await fetch(s3Url, {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": contentType },
|
||||
body: blob,
|
||||
});
|
||||
|
||||
if (!uploadResp.ok) {
|
||||
throw new Error(`Storage upload to S3 failed (${uploadResp.status})`);
|
||||
}
|
||||
|
||||
return { key, url: `/manus-storage/${key}` };
|
||||
}
|
||||
|
||||
export async function storageGet(relKey: string): Promise<{ key: string; url: string }> {
|
||||
const key = normalizeKey(relKey);
|
||||
return { key, url: `/manus-storage/${key}` };
|
||||
}
|
||||
|
||||
export async function storageGetSignedUrl(relKey: string): Promise<string> {
|
||||
const { forgeUrl, forgeKey } = getForgeConfig();
|
||||
const key = normalizeKey(relKey);
|
||||
|
||||
const getUrl = new URL("v1/storage/presign/get", forgeUrl + "/");
|
||||
getUrl.searchParams.set("path", key);
|
||||
|
||||
const resp = await fetch(getUrl, {
|
||||
headers: { Authorization: `Bearer ${forgeKey}` },
|
||||
});
|
||||
|
||||
if (!resp.ok) {
|
||||
const msg = await resp.text().catch(() => resp.statusText);
|
||||
throw new Error(`Storage signed URL failed (${resp.status}): ${msg}`);
|
||||
}
|
||||
|
||||
const { url } = (await resp.json()) as { url: string };
|
||||
return url;
|
||||
}
|
||||
Reference in New Issue
Block a user