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>
69 lines
2.2 KiB
TypeScript
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)}`;
|
|
}
|