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

40
apps/api/src/lib/media.ts Normal file
View file

@ -0,0 +1,40 @@
/**
* Shared media helper routes image uploads through mana-media
* for CAS deduplication, thumbnail generation, and Photos gallery visibility.
*/
import { MediaClient, type MediaResult } from '@manacore/media-client';
const MEDIA_URL = process.env.MANA_MEDIA_URL || 'http://localhost:3015';
let client: MediaClient | null = null;
function getMediaClient(): MediaClient {
if (!client) client = new MediaClient(MEDIA_URL);
return client;
}
export async function uploadImageToMedia(
buffer: ArrayBuffer,
filename: string,
options: { app: string; userId: string }
): Promise<MediaResult> {
return getMediaClient().upload(buffer, {
app: options.app,
userId: options.userId,
filename,
});
}
export function getMediaUrls(mediaId: string) {
const c = getMediaClient();
return {
original: c.getOriginalUrl(mediaId),
thumbnail: c.getThumbnailUrl(mediaId),
medium: c.getMediumUrl(mediaId),
large: c.getLargeUrl(mediaId),
};
}
export function isImageMimeType(mimeType: string): boolean {
return mimeType.startsWith('image/') && mimeType !== 'image/svg+xml';
}