retrotoon-studio/server/storage.ts
Ubuntu c1606ad4c9 feat: migration complète Manus -> auto-hébergé (MinIO + Gemini)
Infrastructure:
- MinIO déployé en local pour le stockage S3 (docker-compose)
- Storage proxy réécrit: sert les fichiers depuis MinIO en streaming
  (plus de 307 redirect vers CDN externe)
- Legacy /manus-storage/ redirige vers /storage/

LLM & Image Generation:
- LLM: Gemini uniquement (suppression du fallback Forge)
- Image generation: Gemini Imagen direct (suppression Forge GenerateImage)
- llmConfig simplifié, un seul provider

Nettoyage Manus:
- Modules Forge stubbés (dataApi, heartbeat, map, notification, voiceTranscription)
- ENV simplifié (suppression forgeApiUrl, forgeApiKey)
- Analytics Manus supprimées du HTML
- systemRouter simplifié

Migration données:
- 750 fichiers migrés de Forge S3 vers MinIO (69.8 MB)
- URLs DB mises à jour: /manus-storage/ -> /storage/
- Script de migration inclus (scripts/migrate-to-minio.mjs)

Performance:
- Frame load: 500ms -> 62ms (8x plus rapide)
- Plus aucune dépendance réseau transatlantique pour le stockage

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-21 04:27:48 +00:00

69 lines
2.2 KiB
TypeScript

import { S3Client, PutObjectCommand, GetObjectCommand } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
const BUCKET = process.env.MINIO_BUCKET || "retrotoon";
let _client: S3Client | null = null;
function getClient(): S3Client {
if (!_client) {
_client = new S3Client({
endpoint: `http://${process.env.MINIO_ENDPOINT || "minio"}:${process.env.MINIO_PORT || "9000"}`,
region: "us-east-1",
credentials: {
accessKeyId: process.env.MINIO_ACCESS_KEY || "retrotoon",
secretAccessKey: process.env.MINIO_SECRET_KEY || "retrotoon-secret-key-2026",
},
forcePathStyle: true,
});
}
return _client;
}
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 key = appendHashSuffix(normalizeKey(relKey));
const client = getClient();
const body = typeof data === "string" ? Buffer.from(data) : data;
await client.send(new PutObjectCommand({
Bucket: BUCKET,
Key: key,
Body: body,
ContentType: contentType,
}));
return { key, url: `/storage/${key}` };
}
export async function storageGet(relKey: string): Promise<{ key: string; url: string }> {
const key = normalizeKey(relKey);
return { key, url: `/storage/${key}` };
}
export async function storageGetSignedUrl(relKey: string): Promise<string> {
const key = normalizeKey(relKey);
const client = getClient();
const command = new GetObjectCommand({ Bucket: BUCKET, Key: key });
return getSignedUrl(client, command, { expiresIn: 3600 });
}
export function storagePublicUrl(key: string): string {
const publicBase = process.env.MINIO_PUBLIC_URL || "http://localhost:9100/retrotoon";
return `${publicBase}/${normalizeKey(key)}`;
}