feat: reviewed-Status für Bilder, Auto-Trigger, Übersetzungen, Vertonbarkeits-Regel

- pictures: reviewed-Status (Constraint + ALLOWED_STATUSES + Auto-Trigger beim Object-Linking)
- objects: STATUSES um reviewed erweitert; Auto-Trigger draft→reviewed wenn Pair verlinkt
- pairs/statements/questions: STATUSES um reviewed (Phase-1-Lücke)
- pairs: POST /:id/review kaskadiert Pair+Frage+Statements (verlangt alle 3 Sprachen)
- words: Auto requested→translated wenn alle titel_* gefüllt (POST+PATCH)
- audios computeUnits: nur vertonbar wenn ALLE 3 Sprachen pro Feld gefüllt
- claude: translate-text/translate-row/translate-missing mit Placeholder-Schutz
  (⟦PHn:label⟧-Tokenisierung, Label übersetzt, UUID erhalten); translation-coverage

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-03 07:35:37 +02:00
parent 6c74aabc3f
commit a3ff787259
9 changed files with 346 additions and 21 deletions

View File

@@ -42,16 +42,24 @@ router.get('/', async (req, res, next) => {
} catch (err) { next(err); }
});
// Hilfsfunktion: wenn alle 3 Sprachen gefüllt sind und Status `requested`, auto → `translated`.
function autoTranslatedStatus(row) {
return (row.titel_de && row.titel_en && row.titel_sv && row.status === 'requested') ? 'translated' : null;
}
// POST /api/words
router.post('/', async (req, res, next) => {
try {
const { titel_de, titel_en, titel_sv, difficulty_level, status } = req.body;
if (status && !STATUSES.includes(status))
return res.status(400).json({ error: `status must be one of: ${STATUSES.join(', ')}` });
// Auto: alle 3 Sprachen direkt mitgeliefert + kein expliziter Status → 'translated'
const allLangs = titel_de && titel_en && titel_sv;
const effectiveStatus = status || (allLangs ? 'translated' : 'requested');
const result = await query(
`INSERT INTO words (titel_de, titel_en, titel_sv, difficulty_level, status, requested_at)
VALUES ($1, $2, $3, $4, COALESCE($5, 'requested'), NOW()) RETURNING *`,
[titel_de || null, titel_en || null, titel_sv || null, difficulty_level || null, status || null]
VALUES ($1, $2, $3, $4, $5, NOW()) RETURNING *`,
[titel_de || null, titel_en || null, titel_sv || null, difficulty_level || null, effectiveStatus]
);
res.status(201).json({ ...result.rows[0], picture_ids: [], category_ids: [] });
} catch (err) { next(err); }
@@ -82,7 +90,15 @@ router.patch('/:id', async (req, res, next) => {
values
);
if (!result.rows.length) return res.status(404).json({ error: 'Not found' });
res.json(result.rows[0]);
// Auto-Übergang: requested → translated wenn jetzt alle 3 Sprachen gefüllt sind
let row = result.rows[0];
const next = autoTranslatedStatus(row);
if (next) {
const upd = await query(`UPDATE words SET status = $1 WHERE id = $2 RETURNING *`, [next, row.id]);
row = upd.rows[0];
}
res.json(row);
} catch (err) { next(err); }
});