diff --git a/apps/api/src/routes/tools.ts b/apps/api/src/routes/tools.ts index 7018c8d..854616d 100644 --- a/apps/api/src/routes/tools.ts +++ b/apps/api/src/routes/tools.ts @@ -4,8 +4,11 @@ import { Hono } from 'hono'; import { CardsCreateInputSchema, CardsSearchInputSchema, + cardContentHash, + maskRegionCount, newReview, subIndexCount, + subIndexCountForCloze, } from '@cards/domain'; import { getDb, type CardsDb } from '../db/connection.ts'; @@ -55,8 +58,39 @@ export function toolsRouter(deps: ToolsDeps = {}): Hono<{ Variables: AuthVars }> if (!deck) return c.json({ error: 'deck_not_found' }, 404); if (deck.userId !== userId) return c.json({ error: 'deck_not_owned' }, 403); + // Text-abhängige Sub-Index-Counts identisch zum REST-Pfad + // (cards.ts POST). Cloze ohne Cluster + Image-Occlusion + // ohne Mask-Regions werden 422. + let count: number; + if (parsed.data.type === 'cloze') { + count = subIndexCountForCloze(parsed.data.fields.text ?? ''); + if (count === 0) { + return c.json( + { error: 'invalid_input', issues: ['cloze.text contains no {{cN::…}} clusters'] }, + 422 + ); + } + } else if (parsed.data.type === 'image-occlusion') { + count = maskRegionCount(parsed.data.fields.mask_regions ?? ''); + if (count === 0) { + return c.json( + { + error: 'invalid_input', + issues: ['image-occlusion.mask_regions must be JSON array with >=1 region'], + }, + 422 + ); + } + } else { + count = subIndexCount(parsed.data.type); + } + const cardId = ulid(); const now = new Date(); + const contentHash = await cardContentHash({ + type: parsed.data.type, + fields: parsed.data.fields, + }); const [row] = await dbOf().transaction(async (tx) => { const [card] = await tx .insert(cards) @@ -67,14 +101,12 @@ export function toolsRouter(deps: ToolsDeps = {}): Hono<{ Variables: AuthVars }> type: parsed.data.type, fields: parsed.data.fields, mediaRefs: parsed.data.media_refs ?? [], + contentHash, createdAt: now, updatedAt: now, }) .returning(); - const initial = Array.from( - { length: subIndexCount(parsed.data.type) }, - (_, i) => i - ).map((subIndex) => { + const initial = Array.from({ length: count }, (_, i) => i).map((subIndex) => { const r = newReview({ userId, cardId, subIndex, now }); return { cardId: r.card_id,