feat: words-Tabelle – Brysbaert-Import + hierarchische Kategorien + Batch-Anreicherung
- categories: parent_id (self-referential) + 49 Unterkategorien geseedet - words: neue Spalten conc_m, dom_pos, level, themenfeld_id + unique index titel_en - enrich_batches + word_generative Tabellen - src/lib/enrichWords.js: Batch-Anreicherung (DE/SV-Übersetzung, Wortart, CEFR, Themenfeld) - src/routes/wordGenerative.js: CRUD für KI-Bild-Pipeline - src/routes/words.js: Filter dom_pos/level/themenfeld_id/has_conc_m + picture_count - scripts/import-brysbaert.js: CSV-Import-Skript (lokal gegen Prod-DB) - POST /api/words/enrich-batch als manueller Trigger Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -12,11 +12,17 @@ const STATUS_TIMESTAMP = {
|
||||
// GET /api/words
|
||||
router.get('/', async (req, res, next) => {
|
||||
try {
|
||||
const { status, titel_de, search, limit = 50, offset = 0 } = req.query;
|
||||
const { status, titel_de, search, dom_pos, level, themenfeld_id, has_conc_m,
|
||||
limit = 50, offset = 0 } = req.query;
|
||||
const params = [Math.min(parseInt(limit), 500), parseInt(offset)];
|
||||
const conditions = [];
|
||||
if (status) { conditions.push(`w.status = $${params.length + 1}`); params.push(status); }
|
||||
if (titel_de) { conditions.push(`lower(w.titel_de) = lower($${params.length + 1})`); params.push(titel_de); }
|
||||
if (status) { conditions.push(`w.status = $${params.length + 1}`); params.push(status); }
|
||||
if (titel_de) { conditions.push(`lower(w.titel_de) = lower($${params.length + 1})`); params.push(titel_de); }
|
||||
if (dom_pos) { conditions.push(`w.dom_pos = $${params.length + 1}`); params.push(dom_pos); }
|
||||
if (level) { conditions.push(`w.level = $${params.length + 1}`); params.push(level); }
|
||||
if (themenfeld_id) { conditions.push(`w.themenfeld_id = $${params.length + 1}`); params.push(themenfeld_id); }
|
||||
if (has_conc_m === 'true') conditions.push(`w.conc_m IS NOT NULL`);
|
||||
if (has_conc_m === 'false') conditions.push(`w.conc_m IS NULL`);
|
||||
if (search) {
|
||||
const p = `%${search.toLowerCase()}%`;
|
||||
conditions.push(`(lower(w.titel_de) LIKE $${params.length + 1} OR lower(w.titel_en) LIKE $${params.length + 1} OR lower(w.titel_sv) LIKE $${params.length + 1})`);
|
||||
@@ -26,12 +32,14 @@ router.get('/', async (req, res, next) => {
|
||||
const result = await query(
|
||||
`SELECT w.*,
|
||||
COALESCE(json_agg(DISTINCT p.id) FILTER (WHERE p.id IS NOT NULL), '[]') AS picture_ids,
|
||||
COALESCE(json_agg(DISTINCT c.id) FILTER (WHERE c.id IS NOT NULL), '[]') AS category_ids
|
||||
COALESCE(json_agg(DISTINCT c.id) FILTER (WHERE c.id IS NOT NULL), '[]') AS category_ids,
|
||||
COUNT(DISTINCT wp2.picture_id)::int AS picture_count
|
||||
FROM words w
|
||||
LEFT JOIN word_pictures wp ON wp.word_id = w.id
|
||||
LEFT JOIN pictures p ON p.id = wp.picture_id
|
||||
LEFT JOIN word_categories wc ON wc.word_id = w.id
|
||||
LEFT JOIN categories c ON c.id = wc.category_id
|
||||
LEFT JOIN word_pictures wp ON wp.word_id = w.id
|
||||
LEFT JOIN pictures p ON p.id = wp.picture_id
|
||||
LEFT JOIN word_categories wc ON wc.word_id = w.id
|
||||
LEFT JOIN categories c ON c.id = wc.category_id
|
||||
LEFT JOIN word_pictures wp2 ON wp2.word_id = w.id
|
||||
${where}
|
||||
GROUP BY w.id
|
||||
ORDER BY w.created_at DESC
|
||||
@@ -69,7 +77,8 @@ router.post('/', async (req, res, next) => {
|
||||
router.patch('/:id', async (req, res, next) => {
|
||||
try {
|
||||
const allowed = ['titel_de', 'titel_en', 'titel_sv', 'status',
|
||||
'difficulty_level', 'requested_at', 'published_at', 'blocked_at'];
|
||||
'difficulty_level', 'requested_at', 'published_at', 'blocked_at',
|
||||
'conc_m', 'dom_pos', 'level', 'themenfeld_id'];
|
||||
const fields = Object.keys(req.body).filter(k => allowed.includes(k));
|
||||
if (!fields.length) return res.status(400).json({ error: 'No valid fields provided' });
|
||||
|
||||
@@ -117,12 +126,14 @@ router.get('/:id', async (req, res, next) => {
|
||||
const result = await query(
|
||||
`SELECT w.*,
|
||||
COALESCE(json_agg(DISTINCT p.id) FILTER (WHERE p.id IS NOT NULL), '[]') AS picture_ids,
|
||||
COALESCE(json_agg(DISTINCT c.id) FILTER (WHERE c.id IS NOT NULL), '[]') AS category_ids
|
||||
COALESCE(json_agg(DISTINCT c.id) FILTER (WHERE c.id IS NOT NULL), '[]') AS category_ids,
|
||||
COUNT(DISTINCT wp2.picture_id)::int AS picture_count
|
||||
FROM words w
|
||||
LEFT JOIN word_pictures wp ON wp.word_id = w.id
|
||||
LEFT JOIN pictures p ON p.id = wp.picture_id
|
||||
LEFT JOIN word_categories wc ON wc.word_id = w.id
|
||||
LEFT JOIN categories c ON c.id = wc.category_id
|
||||
LEFT JOIN word_pictures wp ON wp.word_id = w.id
|
||||
LEFT JOIN pictures p ON p.id = wp.picture_id
|
||||
LEFT JOIN word_categories wc ON wc.word_id = w.id
|
||||
LEFT JOIN categories c ON c.id = wc.category_id
|
||||
LEFT JOIN word_pictures wp2 ON wp2.word_id = w.id
|
||||
WHERE w.id = $1
|
||||
GROUP BY w.id`,
|
||||
[req.params.id]
|
||||
|
||||
Reference in New Issue
Block a user