mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 20:41:09 +02:00
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:
parent
46dae20fa3
commit
502813f49c
10 changed files with 238 additions and 213 deletions
|
|
@ -25,6 +25,8 @@ PUBLIC_GLITCHTIP_DSN=
|
|||
MANA_CORE_AUTH_URL=http://localhost:3001
|
||||
# Mana Credits Service
|
||||
MANA_CREDITS_URL=http://localhost:3061
|
||||
# Mana Media Service (CAS, thumbnails, Photos gallery)
|
||||
MANA_MEDIA_URL=http://localhost:3015
|
||||
# Service key for service-to-service communication
|
||||
MANA_CORE_SERVICE_KEY=dev-service-key-for-bot-sso-2024
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
"type-check": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@manacore/media-client": "workspace:*",
|
||||
"@manacore/shared-hono": "workspace:*",
|
||||
"@manacore/shared-storage": "workspace:*",
|
||||
"@mozilla/readability": "^0.5.0",
|
||||
|
|
|
|||
40
apps/api/src/lib/media.ts
Normal file
40
apps/api/src/lib/media.ts
Normal 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';
|
||||
}
|
||||
|
|
@ -29,22 +29,37 @@ routes.post('/:id/avatar', async (c) => {
|
|||
}
|
||||
|
||||
try {
|
||||
const { createContactsStorage, generateUserFileKey, getContentType } = await import(
|
||||
const buffer = await file.arrayBuffer();
|
||||
|
||||
if (file.type === 'image/svg+xml') {
|
||||
// SVGs stay on shared-storage (Sharp can't process SVG)
|
||||
const { createContactsStorage, generateUserFileKey } = await import(
|
||||
'@manacore/shared-storage'
|
||||
);
|
||||
const storage = createContactsStorage();
|
||||
const key = generateUserFileKey(
|
||||
userId,
|
||||
`avatar-${c.req.param('id')}.${file.name.split('.').pop()}`
|
||||
);
|
||||
const buffer = Buffer.from(await file.arrayBuffer());
|
||||
|
||||
const result = await storage.upload(key, buffer, {
|
||||
contentType: getContentType(file.name),
|
||||
const key = generateUserFileKey(userId, `avatar-${c.req.param('id')}.svg`);
|
||||
const result = await storage.upload(key, Buffer.from(buffer), {
|
||||
contentType: 'image/svg+xml',
|
||||
public: true,
|
||||
});
|
||||
|
||||
return c.json({ avatarUrl: result.url }, 201);
|
||||
}
|
||||
|
||||
// Raster images -> mana-media for dedup, thumbnails & Photos gallery
|
||||
const { uploadImageToMedia } = await import('../../lib/media');
|
||||
const result = await uploadImageToMedia(
|
||||
buffer,
|
||||
`avatar-${c.req.param('id')}.${file.name.split('.').pop()}`,
|
||||
{ app: 'contacts', userId }
|
||||
);
|
||||
|
||||
return c.json(
|
||||
{
|
||||
avatarUrl: result.urls.thumbnail || result.urls.original,
|
||||
mediaId: result.id,
|
||||
},
|
||||
201
|
||||
);
|
||||
} catch {
|
||||
return c.json({ error: 'Upload failed' }, 500);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,11 +25,15 @@ const routes = new Hono();
|
|||
// ─── Photo Analysis (server-only: Gemini Vision) ────────────
|
||||
|
||||
routes.post('/analysis/photo', async (c) => {
|
||||
const userId = c.get('userId');
|
||||
const { imageBase64, mimeType } = await c.req.json();
|
||||
if (!imageBase64) return c.json({ error: 'imageBase64 required' }, 400);
|
||||
|
||||
const mime = mimeType || 'image/jpeg';
|
||||
|
||||
try {
|
||||
const res = await fetch(`${LLM_URL}/api/v1/chat/completions`, {
|
||||
// Run AI analysis and mana-media upload in parallel
|
||||
const analysisPromise = fetch(`${LLM_URL}/api/v1/chat/completions`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
|
|
@ -41,7 +45,7 @@ routes.post('/analysis/photo', async (c) => {
|
|||
{ type: 'text', text: 'Analysiere diese Mahlzeit.' },
|
||||
{
|
||||
type: 'image_url',
|
||||
image_url: { url: `data:${mimeType || 'image/jpeg'};base64,${imageBase64}` },
|
||||
image_url: { url: `data:${mime};base64,${imageBase64}` },
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
@ -52,12 +56,29 @@ routes.post('/analysis/photo', async (c) => {
|
|||
}),
|
||||
});
|
||||
|
||||
// Store meal photo in mana-media for Photos gallery & persistence
|
||||
const ext = mime.split('/')[1] || 'jpg';
|
||||
const { uploadImageToMedia } = await import('../../lib/media');
|
||||
const buffer = Uint8Array.from(atob(imageBase64), (ch) => ch.charCodeAt(0));
|
||||
const mediaPromise = uploadImageToMedia(buffer.buffer, `meal-${Date.now()}.${ext}`, {
|
||||
app: 'nutriphi',
|
||||
userId,
|
||||
}).catch(() => null); // Don't fail analysis if media upload fails
|
||||
|
||||
const [res, mediaResult] = await Promise.all([analysisPromise, mediaPromise]);
|
||||
|
||||
if (!res.ok) return c.json({ error: 'AI analysis failed' }, 502);
|
||||
|
||||
const data = await res.json();
|
||||
const content = data.choices?.[0]?.message?.content;
|
||||
const analysis = typeof content === 'string' ? JSON.parse(content) : content;
|
||||
|
||||
// Attach media info so the frontend can store photoMediaId on the meal
|
||||
if (mediaResult) {
|
||||
analysis.mediaId = mediaResult.id;
|
||||
analysis.photoUrl = mediaResult.urls.thumbnail || mediaResult.urls.original;
|
||||
}
|
||||
|
||||
return c.json(analysis);
|
||||
} catch (err) {
|
||||
console.error('Photo analysis failed:', err);
|
||||
|
|
|
|||
|
|
@ -98,7 +98,27 @@ routes.post('/generate', async (c) => {
|
|||
|
||||
await consumeCredits(userId, 'AI_IMAGE_GENERATION', cost, `Image: ${prompt.slice(0, 50)}`);
|
||||
|
||||
// Store generated image in mana-media for dedup, thumbnails & Photos gallery
|
||||
try {
|
||||
const { uploadImageToMedia } = await import('../../lib/media');
|
||||
const imgRes = await fetch(imageUrl);
|
||||
const imgBuffer = await imgRes.arrayBuffer();
|
||||
const media = await uploadImageToMedia(imgBuffer, `generated-${Date.now()}.png`, {
|
||||
app: 'picture',
|
||||
userId,
|
||||
});
|
||||
|
||||
return c.json({
|
||||
imageUrl: media.urls.original,
|
||||
mediaId: media.id,
|
||||
thumbnailUrl: media.urls.thumbnail,
|
||||
prompt,
|
||||
model: model || 'flux-schnell',
|
||||
});
|
||||
} catch {
|
||||
// Fallback: return raw imageUrl if mana-media is unavailable
|
||||
return c.json({ imageUrl, prompt, model: model || 'flux-schnell' });
|
||||
}
|
||||
} catch (_err) {
|
||||
return c.json({ error: 'Generation failed' }, 500);
|
||||
}
|
||||
|
|
@ -115,19 +135,19 @@ routes.post('/upload', async (c) => {
|
|||
if (file.size > 10 * 1024 * 1024) return c.json({ error: 'Max 10MB' }, 400);
|
||||
|
||||
try {
|
||||
const { createPictureStorage, generateUserFileKey, getContentType } = await import(
|
||||
'@manacore/shared-storage'
|
||||
const { uploadImageToMedia } = await import('../../lib/media');
|
||||
const buffer = await file.arrayBuffer();
|
||||
const result = await uploadImageToMedia(buffer, file.name, { app: 'picture', userId });
|
||||
|
||||
return c.json(
|
||||
{
|
||||
storagePath: result.id,
|
||||
publicUrl: result.urls.original,
|
||||
mediaId: result.id,
|
||||
thumbnailUrl: result.urls.thumbnail,
|
||||
},
|
||||
201
|
||||
);
|
||||
const storage = createPictureStorage();
|
||||
const key = generateUserFileKey(userId, file.name);
|
||||
const buffer = Buffer.from(await file.arrayBuffer());
|
||||
|
||||
const result = await storage.upload(key, buffer, {
|
||||
contentType: getContentType(file.name),
|
||||
public: true,
|
||||
});
|
||||
|
||||
return c.json({ storagePath: key, publicUrl: result.url }, 201);
|
||||
} catch (_err) {
|
||||
return c.json({ error: 'Upload failed' }, 500);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,19 +24,19 @@ routes.post('/photos/upload', async (c) => {
|
|||
if (file.size > 10 * 1024 * 1024) return c.json({ error: 'File too large (max 10MB)' }, 400);
|
||||
|
||||
try {
|
||||
const { createPlantaStorage, generateUserFileKey, getContentType } = await import(
|
||||
'@manacore/shared-storage'
|
||||
const { uploadImageToMedia } = await import('../../lib/media');
|
||||
const buffer = await file.arrayBuffer();
|
||||
const result = await uploadImageToMedia(buffer, file.name, { app: 'planta', userId });
|
||||
|
||||
return c.json(
|
||||
{
|
||||
storagePath: result.id,
|
||||
publicUrl: result.urls.original,
|
||||
mediaId: result.id,
|
||||
plantId,
|
||||
},
|
||||
201
|
||||
);
|
||||
const storage = createPlantaStorage();
|
||||
const key = generateUserFileKey(userId, file.name);
|
||||
const buffer = Buffer.from(await file.arrayBuffer());
|
||||
|
||||
const result = await storage.upload(key, buffer, {
|
||||
contentType: getContentType(file.name),
|
||||
public: true,
|
||||
});
|
||||
|
||||
return c.json({ storagePath: key, publicUrl: result.url, plantId }, 201);
|
||||
} catch (err) {
|
||||
console.error('Upload failed:', err);
|
||||
return c.json({ error: 'Upload failed' }, 500);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -24,6 +24,8 @@ export interface LocalMeal extends BaseRecord {
|
|||
portionSize?: string | null;
|
||||
confidence: number;
|
||||
nutrition?: NutritionData | null;
|
||||
photoMediaId?: string | null;
|
||||
photoUrl?: string | null;
|
||||
}
|
||||
|
||||
export interface LocalGoal extends BaseRecord {
|
||||
|
|
@ -65,5 +67,7 @@ export interface MealWithNutrition {
|
|||
portionSize?: string | null;
|
||||
confidence: number;
|
||||
nutrition: NutritionData | null;
|
||||
photoMediaId?: string | null;
|
||||
photoUrl?: string | null;
|
||||
createdAt: string;
|
||||
}
|
||||
|
|
|
|||
239
pnpm-lock.yaml
generated
239
pnpm-lock.yaml
generated
|
|
@ -64,6 +64,9 @@ importers:
|
|||
|
||||
apps/api:
|
||||
dependencies:
|
||||
'@manacore/media-client':
|
||||
specifier: workspace:*
|
||||
version: link:../../services/mana-media/packages/client
|
||||
'@manacore/shared-hono':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/shared-hono
|
||||
|
|
@ -26455,7 +26458,7 @@ snapshots:
|
|||
ws: 8.18.3
|
||||
zod: 3.25.76
|
||||
optionalDependencies:
|
||||
expo-router: 55.0.5(7734cwvz7fkv6rui37yzuzjxt4)
|
||||
expo-router: 55.0.5(qfbmyq6c2bmncbs6qduwm3ekeu)
|
||||
react-native: 0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4)
|
||||
transitivePeerDependencies:
|
||||
- '@expo/dom-webview'
|
||||
|
|
@ -27204,7 +27207,7 @@ snapshots:
|
|||
react: 19.2.4
|
||||
optionalDependencies:
|
||||
'@expo/metro-runtime': 55.0.7(@expo/dom-webview@55.0.3)(expo@55.0.5)(react-dom@19.2.4(react@19.2.4))(react-native@0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4)
|
||||
expo-router: 55.0.5(7734cwvz7fkv6rui37yzuzjxt4)
|
||||
expo-router: 55.0.5(qfbmyq6c2bmncbs6qduwm3ekeu)
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
|
@ -27661,42 +27664,6 @@ snapshots:
|
|||
- supports-color
|
||||
- ts-node
|
||||
|
||||
'@jest/core@30.3.0(esbuild-register@3.6.0(esbuild@0.19.12))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3))':
|
||||
dependencies:
|
||||
'@jest/console': 30.3.0
|
||||
'@jest/pattern': 30.0.1
|
||||
'@jest/reporters': 30.3.0
|
||||
'@jest/test-result': 30.3.0
|
||||
'@jest/transform': 30.3.0
|
||||
'@jest/types': 30.3.0
|
||||
'@types/node': 22.19.1
|
||||
ansi-escapes: 4.3.2
|
||||
chalk: 4.1.2
|
||||
ci-info: 4.3.1
|
||||
exit-x: 0.2.2
|
||||
graceful-fs: 4.2.11
|
||||
jest-changed-files: 30.3.0
|
||||
jest-config: 30.3.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.19.12))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3))
|
||||
jest-haste-map: 30.3.0
|
||||
jest-message-util: 30.3.0
|
||||
jest-regex-util: 30.0.1
|
||||
jest-resolve: 30.3.0
|
||||
jest-resolve-dependencies: 30.3.0
|
||||
jest-runner: 30.3.0
|
||||
jest-runtime: 30.3.0
|
||||
jest-snapshot: 30.3.0
|
||||
jest-util: 30.3.0
|
||||
jest-validate: 30.3.0
|
||||
jest-watcher: 30.3.0
|
||||
pretty-format: 30.3.0
|
||||
slash: 3.0.0
|
||||
transitivePeerDependencies:
|
||||
- babel-plugin-macros
|
||||
- esbuild-register
|
||||
- supports-color
|
||||
- ts-node
|
||||
optional: true
|
||||
|
||||
'@jest/core@30.3.0(esbuild-register@3.6.0(esbuild@0.27.5))(ts-node@10.9.2(@types/node@20.19.25)(typescript@5.9.3))':
|
||||
dependencies:
|
||||
'@jest/console': 30.3.0
|
||||
|
|
@ -32225,19 +32192,6 @@ snapshots:
|
|||
jest: 30.3.0(@types/node@20.19.25)(esbuild-register@3.6.0(esbuild@0.27.5))(ts-node@10.9.2(@types/node@20.19.25)(typescript@5.9.3))
|
||||
optional: true
|
||||
|
||||
'@testing-library/react-native@13.3.3(jest@30.3.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.19.12))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)))(react-native@0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4))(react-test-renderer@19.1.0(react@19.2.4))(react@19.2.4)':
|
||||
dependencies:
|
||||
jest-matcher-utils: 30.3.0
|
||||
picocolors: 1.1.1
|
||||
pretty-format: 30.3.0
|
||||
react: 19.2.4
|
||||
react-native: 0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4)
|
||||
react-test-renderer: 19.1.0(react@19.2.4)
|
||||
redent: 3.0.0
|
||||
optionalDependencies:
|
||||
jest: 30.3.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.19.12))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3))
|
||||
optional: true
|
||||
|
||||
'@testing-library/react-native@13.3.3(jest@30.3.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.27.5))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.3.3)))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react-test-renderer@19.1.0(react@19.2.0))(react@19.2.0)':
|
||||
dependencies:
|
||||
jest-matcher-utils: 30.3.0
|
||||
|
|
@ -32303,6 +32257,19 @@ snapshots:
|
|||
jest: 30.3.0(@types/node@24.10.1)(esbuild-register@3.6.0(esbuild@0.27.5))(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.9.3))
|
||||
optional: true
|
||||
|
||||
'@testing-library/react-native@13.3.3(jest@30.3.0(@types/node@24.10.1)(esbuild-register@3.6.0(esbuild@0.27.5))(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.9.3)))(react-native@0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4))(react-test-renderer@19.1.0(react@19.2.4))(react@19.2.4)':
|
||||
dependencies:
|
||||
jest-matcher-utils: 30.3.0
|
||||
picocolors: 1.1.1
|
||||
pretty-format: 30.3.0
|
||||
react: 19.2.4
|
||||
react-native: 0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4)
|
||||
react-test-renderer: 19.1.0(react@19.2.4)
|
||||
redent: 3.0.0
|
||||
optionalDependencies:
|
||||
jest: 30.3.0(@types/node@24.10.1)(esbuild-register@3.6.0(esbuild@0.27.5))(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.9.3))
|
||||
optional: true
|
||||
|
||||
'@testing-library/svelte-core@1.0.0(svelte@5.44.0)':
|
||||
dependencies:
|
||||
svelte: 5.44.0
|
||||
|
|
@ -38632,57 +38599,6 @@ snapshots:
|
|||
- expo-font
|
||||
- supports-color
|
||||
|
||||
expo-router@55.0.5(7734cwvz7fkv6rui37yzuzjxt4):
|
||||
dependencies:
|
||||
'@expo/log-box': 55.0.8(@expo/dom-webview@55.0.3)(expo@55.0.5)(react-native@0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4)
|
||||
'@expo/metro-runtime': 55.0.7(@expo/dom-webview@55.0.3)(expo@55.0.5)(react-dom@19.2.4(react@19.2.4))(react-native@0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4)
|
||||
'@expo/schema-utils': 55.0.2
|
||||
'@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.4)
|
||||
'@radix-ui/react-tabs': 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@react-navigation/bottom-tabs': 7.15.5(@react-navigation/native@7.1.33(react-native@0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4))(react-native-safe-area-context@5.7.0(react-native@0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4))(react-native-screens@4.24.0(react-native@0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4))(react-native@0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4)
|
||||
'@react-navigation/native': 7.1.33(react-native@0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4)
|
||||
'@react-navigation/native-stack': 7.14.4(@react-navigation/native@7.1.33(react-native@0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4))(react-native-safe-area-context@5.7.0(react-native@0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4))(react-native-screens@4.24.0(react-native@0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4))(react-native@0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4)
|
||||
client-only: 0.0.1
|
||||
debug: 4.4.3
|
||||
escape-string-regexp: 4.0.0
|
||||
expo: 55.0.5(@babel/core@7.28.5)(@expo/dom-webview@55.0.3)(@expo/metro-runtime@55.0.7)(expo-router@55.0.5)(react-dom@19.2.4(react@19.2.4))(react-native-webview@13.12.2(react-native@0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4))(react-native@0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4)(typescript@5.9.3)
|
||||
expo-constants: 55.0.7(expo@55.0.5)(react-native@0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4))(typescript@5.9.3)
|
||||
expo-glass-effect: 55.0.8(expo@55.0.5)(react-native@0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4)
|
||||
expo-image: 55.0.6(expo@55.0.5)(react-native-web@0.21.2(encoding@0.1.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-native@0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4)
|
||||
expo-linking: 55.0.7(expo@55.0.5)(react-native@0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4)(typescript@5.9.3)
|
||||
expo-server: 55.0.6
|
||||
expo-symbols: 55.0.5(expo-font@55.0.4)(expo@55.0.5)(react-native@0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4)
|
||||
fast-deep-equal: 3.1.3
|
||||
invariant: 2.2.4
|
||||
nanoid: 3.3.11
|
||||
query-string: 7.1.3
|
||||
react: 19.2.4
|
||||
react-fast-compare: 3.2.2
|
||||
react-native: 0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4)
|
||||
react-native-is-edge-to-edge: 1.2.1(react-native@0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4)
|
||||
react-native-safe-area-context: 5.7.0(react-native@0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4)
|
||||
react-native-screens: 4.24.0(react-native@0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4)
|
||||
semver: 7.6.3
|
||||
server-only: 0.0.1
|
||||
sf-symbols-typescript: 2.2.0
|
||||
shallowequal: 1.1.0
|
||||
use-latest-callback: 0.2.6(react@19.2.4)
|
||||
vaul: 1.1.2(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
optionalDependencies:
|
||||
'@react-navigation/drawer': 7.7.4(@react-navigation/native@7.1.33(react-native@0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4))(react-native-gesture-handler@2.30.0(react-native@0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4))(react-native-reanimated@4.2.2(react-native-worklets@0.7.4(@babel/core@7.28.5)(react-native@0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4))(react-native@0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4))(react-native-safe-area-context@5.7.0(react-native@0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4))(react-native-screens@4.24.0(react-native@0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4))(react-native@0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4)
|
||||
'@testing-library/react-native': 13.3.3(jest@30.3.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.19.12))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)))(react-native@0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4))(react-test-renderer@19.1.0(react@19.2.4))(react@19.2.4)
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
react-native-gesture-handler: 2.30.0(react-native@0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4)
|
||||
react-native-reanimated: 4.2.2(react-native-worklets@0.7.4(@babel/core@7.28.5)(react-native@0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4))(react-native@0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4)
|
||||
react-native-web: 0.21.2(encoding@0.1.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
transitivePeerDependencies:
|
||||
- '@react-native-masked-view/masked-view'
|
||||
- '@types/react'
|
||||
- '@types/react-dom'
|
||||
- expo-font
|
||||
- supports-color
|
||||
optional: true
|
||||
|
||||
expo-router@55.0.5(alplmueeiabdlyo62r6crpdhli):
|
||||
dependencies:
|
||||
'@expo/log-box': 55.0.8(@expo/dom-webview@55.0.3)(expo@55.0.5)(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0)
|
||||
|
|
@ -38884,6 +38800,57 @@ snapshots:
|
|||
- supports-color
|
||||
optional: true
|
||||
|
||||
expo-router@55.0.5(qfbmyq6c2bmncbs6qduwm3ekeu):
|
||||
dependencies:
|
||||
'@expo/log-box': 55.0.8(@expo/dom-webview@55.0.3)(expo@55.0.5)(react-native@0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4)
|
||||
'@expo/metro-runtime': 55.0.7(@expo/dom-webview@55.0.3)(expo@55.0.5)(react-dom@19.2.4(react@19.2.4))(react-native@0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4)
|
||||
'@expo/schema-utils': 55.0.2
|
||||
'@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.4)
|
||||
'@radix-ui/react-tabs': 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@react-navigation/bottom-tabs': 7.15.5(@react-navigation/native@7.1.33(react-native@0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4))(react-native-safe-area-context@5.7.0(react-native@0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4))(react-native-screens@4.24.0(react-native@0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4))(react-native@0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4)
|
||||
'@react-navigation/native': 7.1.33(react-native@0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4)
|
||||
'@react-navigation/native-stack': 7.14.4(@react-navigation/native@7.1.33(react-native@0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4))(react-native-safe-area-context@5.7.0(react-native@0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4))(react-native-screens@4.24.0(react-native@0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4))(react-native@0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4)
|
||||
client-only: 0.0.1
|
||||
debug: 4.4.3
|
||||
escape-string-regexp: 4.0.0
|
||||
expo: 55.0.5(@babel/core@7.28.5)(@expo/dom-webview@55.0.3)(@expo/metro-runtime@55.0.7)(expo-router@55.0.5)(react-dom@19.2.4(react@19.2.4))(react-native-webview@13.12.2(react-native@0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4))(react-native@0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4)(typescript@5.9.3)
|
||||
expo-constants: 55.0.7(expo@55.0.5)(react-native@0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4))(typescript@5.9.3)
|
||||
expo-glass-effect: 55.0.8(expo@55.0.5)(react-native@0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4)
|
||||
expo-image: 55.0.6(expo@55.0.5)(react-native-web@0.21.2(encoding@0.1.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-native@0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4)
|
||||
expo-linking: 55.0.7(expo@55.0.5)(react-native@0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4)(typescript@5.9.3)
|
||||
expo-server: 55.0.6
|
||||
expo-symbols: 55.0.5(expo-font@55.0.4)(expo@55.0.5)(react-native@0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4)
|
||||
fast-deep-equal: 3.1.3
|
||||
invariant: 2.2.4
|
||||
nanoid: 3.3.11
|
||||
query-string: 7.1.3
|
||||
react: 19.2.4
|
||||
react-fast-compare: 3.2.2
|
||||
react-native: 0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4)
|
||||
react-native-is-edge-to-edge: 1.2.1(react-native@0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4)
|
||||
react-native-safe-area-context: 5.7.0(react-native@0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4)
|
||||
react-native-screens: 4.24.0(react-native@0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4)
|
||||
semver: 7.6.3
|
||||
server-only: 0.0.1
|
||||
sf-symbols-typescript: 2.2.0
|
||||
shallowequal: 1.1.0
|
||||
use-latest-callback: 0.2.6(react@19.2.4)
|
||||
vaul: 1.1.2(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
optionalDependencies:
|
||||
'@react-navigation/drawer': 7.7.4(@react-navigation/native@7.1.33(react-native@0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4))(react-native-gesture-handler@2.30.0(react-native@0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4))(react-native-reanimated@4.2.2(react-native-worklets@0.7.4(@babel/core@7.28.5)(react-native@0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4))(react-native@0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4))(react-native-safe-area-context@5.7.0(react-native@0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4))(react-native-screens@4.24.0(react-native@0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4))(react-native@0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4)
|
||||
'@testing-library/react-native': 13.3.3(jest@30.3.0(@types/node@24.10.1)(esbuild-register@3.6.0(esbuild@0.27.5))(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.9.3)))(react-native@0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4))(react-test-renderer@19.1.0(react@19.2.4))(react@19.2.4)
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
react-native-gesture-handler: 2.30.0(react-native@0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4)
|
||||
react-native-reanimated: 4.2.2(react-native-worklets@0.7.4(@babel/core@7.28.5)(react-native@0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4))(react-native@0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4)
|
||||
react-native-web: 0.21.2(encoding@0.1.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
transitivePeerDependencies:
|
||||
- '@react-native-masked-view/masked-view'
|
||||
- '@types/react'
|
||||
- '@types/react-dom'
|
||||
- expo-font
|
||||
- supports-color
|
||||
optional: true
|
||||
|
||||
expo-router@55.0.5(x6ppsusqzuln52ezuk2uhtcwta):
|
||||
dependencies:
|
||||
'@expo/log-box': 55.0.8(@expo/dom-webview@55.0.3)(expo@55.0.5)(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)
|
||||
|
|
@ -40916,26 +40883,6 @@ snapshots:
|
|||
- ts-node
|
||||
optional: true
|
||||
|
||||
jest-cli@30.3.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.19.12))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)):
|
||||
dependencies:
|
||||
'@jest/core': 30.3.0(esbuild-register@3.6.0(esbuild@0.19.12))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3))
|
||||
'@jest/test-result': 30.3.0
|
||||
'@jest/types': 30.3.0
|
||||
chalk: 4.1.2
|
||||
exit-x: 0.2.2
|
||||
import-local: 3.2.0
|
||||
jest-config: 30.3.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.19.12))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3))
|
||||
jest-util: 30.3.0
|
||||
jest-validate: 30.3.0
|
||||
yargs: 17.7.2
|
||||
transitivePeerDependencies:
|
||||
- '@types/node'
|
||||
- babel-plugin-macros
|
||||
- esbuild-register
|
||||
- supports-color
|
||||
- ts-node
|
||||
optional: true
|
||||
|
||||
jest-cli@30.3.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.27.5))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.3.3)):
|
||||
dependencies:
|
||||
'@jest/core': 30.3.0(esbuild-register@3.6.0(esbuild@0.27.5))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.3.3))
|
||||
|
|
@ -41112,40 +41059,6 @@ snapshots:
|
|||
- supports-color
|
||||
optional: true
|
||||
|
||||
jest-config@30.3.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.19.12))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)):
|
||||
dependencies:
|
||||
'@babel/core': 7.28.5
|
||||
'@jest/get-type': 30.1.0
|
||||
'@jest/pattern': 30.0.1
|
||||
'@jest/test-sequencer': 30.3.0
|
||||
'@jest/types': 30.3.0
|
||||
babel-jest: 30.3.0(@babel/core@7.28.5)
|
||||
chalk: 4.1.2
|
||||
ci-info: 4.3.1
|
||||
deepmerge: 4.3.1
|
||||
glob: 10.5.0
|
||||
graceful-fs: 4.2.11
|
||||
jest-circus: 30.3.0
|
||||
jest-docblock: 30.2.0
|
||||
jest-environment-node: 30.3.0
|
||||
jest-regex-util: 30.0.1
|
||||
jest-resolve: 30.3.0
|
||||
jest-runner: 30.3.0
|
||||
jest-util: 30.3.0
|
||||
jest-validate: 30.3.0
|
||||
parse-json: 5.2.0
|
||||
pretty-format: 30.3.0
|
||||
slash: 3.0.0
|
||||
strip-json-comments: 3.1.1
|
||||
optionalDependencies:
|
||||
'@types/node': 22.19.1
|
||||
esbuild-register: 3.6.0(esbuild@0.19.12)
|
||||
ts-node: 10.9.2(@types/node@22.19.1)(typescript@5.9.3)
|
||||
transitivePeerDependencies:
|
||||
- babel-plugin-macros
|
||||
- supports-color
|
||||
optional: true
|
||||
|
||||
jest-config@30.3.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.27.5))(ts-node@10.9.2(@types/node@20.19.25)(typescript@5.9.3)):
|
||||
dependencies:
|
||||
'@babel/core': 7.28.5
|
||||
|
|
@ -41900,20 +41813,6 @@ snapshots:
|
|||
- ts-node
|
||||
optional: true
|
||||
|
||||
jest@30.3.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.19.12))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)):
|
||||
dependencies:
|
||||
'@jest/core': 30.3.0(esbuild-register@3.6.0(esbuild@0.19.12))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3))
|
||||
'@jest/types': 30.3.0
|
||||
import-local: 3.2.0
|
||||
jest-cli: 30.3.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.19.12))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3))
|
||||
transitivePeerDependencies:
|
||||
- '@types/node'
|
||||
- babel-plugin-macros
|
||||
- esbuild-register
|
||||
- supports-color
|
||||
- ts-node
|
||||
optional: true
|
||||
|
||||
jest@30.3.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.27.5))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.3.3)):
|
||||
dependencies:
|
||||
'@jest/core': 30.3.0(esbuild-register@3.6.0(esbuild@0.27.5))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.3.3))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue