feat(api): route all image uploads through mana-media for CAS, thumbnails & Photos gallery

Picture, Contacts, Planta, Storage, and NutriPhi image uploads now go
through mana-media instead of directly to S3. This enables SHA-256
deduplication, automatic thumbnail generation, EXIF extraction, and
makes all images visible in the Photos gallery. Non-image files (PDFs,
audio, docs) continue to use shared-storage directly. SVG avatars in
Contacts also stay on shared-storage since Sharp can't process SVGs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-04-04 10:38:30 +02:00
parent 46dae20fa3
commit 502813f49c
10 changed files with 238 additions and 213 deletions

View file

@ -22,14 +22,37 @@ routes.post('/files/upload', async (c) => {
if (file.size > 100 * 1024 * 1024) return c.json({ error: 'Max 100MB' }, 400);
try {
const buffer = await file.arrayBuffer();
const { isImageMimeType } = await import('../../lib/media');
// Images -> mana-media for dedup, thumbnails & Photos gallery
if (isImageMimeType(file.type)) {
const { uploadImageToMedia } = await import('../../lib/media');
const result = await uploadImageToMedia(buffer, file.name, { app: 'storage', userId });
return c.json(
{
id: crypto.randomUUID(),
name: file.name,
storagePath: result.id,
storageKey: result.id,
mimeType: file.type,
size: file.size,
parentFolderId: folderId,
mediaId: result.id,
},
201
);
}
// Non-images -> shared-storage as before
const { createStorageStorage, generateUserFileKey, getContentType } = await import(
'@manacore/shared-storage'
);
const storage = createStorageStorage();
const key = generateUserFileKey(userId, file.name);
const buffer = Buffer.from(await file.arrayBuffer());
await storage.upload(key, buffer, {
await storage.upload(key, Buffer.from(buffer), {
contentType: getContentType(file.name),
public: false,
});