mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-16 10:19:40 +02:00
feat(profile,api): meImages foundation for AI reference generation (M1)
M1 of docs/plans/me-images-and-reference-generation.md — a user-owned pool of reference images (face, fullbody, hands, …) that will back image generation where the user appears as themselves (outfit try-on, glasses, portraits) via OpenAI /v1/images/edits. Data layer only in this commit; UI lands in M2, the edits endpoint in M3. - Dexie v38: meImages table with id/kind/primaryFor/createdAt indices. Added to USER_LEVEL_TABLES so the hook stamps userId and skips the spaceId/authorId/visibility trio (one human = one face across every Space, not per-Space). - Encryption registry: label + tags encrypted; kind/primaryFor/usage stay plaintext because they drive the indexed queries and the Reference picker's filtering. mediaId/URLs/dimensions are structural. - Profile module store: createMeImage, updateMeImage, setAiReferenceEnabled (per-image KI opt-in — plan decision #5), setPrimary (transactional slot swap — only one row per primary slot), deleteMeImage. Emits MeImage* domain events. - Queries: useAllMeImages, useMeImagesByKind, useReferenceImages (only the rows the user opted in for KI), useImageByPrimary. - POST /api/v1/profile/me-images/upload: thin wrapper over mana-media with app='me' as the reference tag. No new MinIO bucket — plan decision #1 revised after verifying mana-media uses one bucket and only tags references by app. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
32c95a3780
commit
89258eb451
10 changed files with 790 additions and 4 deletions
54
apps/api/src/modules/profile/routes.ts
Normal file
54
apps/api/src/modules/profile/routes.ts
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
/**
|
||||
* Profile module — server endpoints.
|
||||
*
|
||||
* Upload route for me-images (docs/plans/me-images-and-reference-generation.md M1).
|
||||
* Thin wrapper over mana-media — the stored row lands in Dexie on the
|
||||
* client after this returns. We keep server-side storage of the image
|
||||
* in mana-media (CAS + thumbnails) so the Picture generator can pull
|
||||
* the original bytes by `mediaId` for the eventual /v1/images/edits
|
||||
* call (M3) without the client needing to re-upload each time.
|
||||
*/
|
||||
|
||||
import { Hono } from 'hono';
|
||||
import type { AuthVariables } from '@mana/shared-hono';
|
||||
|
||||
const routes = new Hono<{ Variables: AuthVariables }>();
|
||||
|
||||
// Max upload size for me-images. 10MB matches /picture/upload — same
|
||||
// real-world phone-camera PNG range, same mana-media pipeline downstream.
|
||||
const MAX_UPLOAD_BYTES = 10 * 1024 * 1024;
|
||||
|
||||
routes.post('/me-images/upload', async (c) => {
|
||||
const userId = c.get('userId');
|
||||
const formData = await c.req.formData();
|
||||
const file = formData.get('file') as File | null;
|
||||
|
||||
if (!file) return c.json({ error: 'No file' }, 400);
|
||||
if (file.size > MAX_UPLOAD_BYTES) return c.json({ error: 'Max 10MB' }, 400);
|
||||
|
||||
try {
|
||||
const { uploadImageToMedia } = await import('../../lib/media');
|
||||
const buffer = await file.arrayBuffer();
|
||||
// `app='me'` tags the media_references row so a later
|
||||
// GET /api/v1/media?app=me&userId=X can list all me-images,
|
||||
// and the /v1/images/edits path can verify ownership in O(1).
|
||||
const result = await uploadImageToMedia(buffer, file.name, {
|
||||
app: 'me',
|
||||
userId,
|
||||
});
|
||||
|
||||
return c.json(
|
||||
{
|
||||
mediaId: result.id,
|
||||
storagePath: result.id,
|
||||
publicUrl: result.urls.original,
|
||||
thumbnailUrl: result.urls.thumbnail,
|
||||
},
|
||||
201
|
||||
);
|
||||
} catch (_err) {
|
||||
return c.json({ error: 'Upload failed' }, 500);
|
||||
}
|
||||
});
|
||||
|
||||
export { routes as profileRoutes };
|
||||
Loading…
Add table
Add a link
Reference in a new issue