diff --git a/src/routes/feed.js b/src/routes/feed.js index b82ce9d..9230502 100644 --- a/src/routes/feed.js +++ b/src/routes/feed.js @@ -39,12 +39,20 @@ function collectIds(lists, filterType) { router.get('/', requireJwt, async (req, res, next) => { try { - const lang = ['de', 'en', 'sv'].includes(req.query.lang) ? req.query.lang : 'de'; - const limit = Math.min(parseInt(req.query.limit) || 20, 100); + const lang = ['de', 'en', 'sv'].includes(req.query.lang) ? req.query.lang : 'de'; + const limit = Math.min(parseInt(req.query.limit) || 20, 100); + const userId = req.user.userId; + // Vom Client schon geladene Pairs (In-Session-Dedupe) – nur gültige UUIDs übernehmen. + const exclude = String(req.query.exclude || '') + .split(',') + .map(s => s.trim()) + .filter(s => /^[0-9a-f-]{36}$/i.test(s)); // 1. Random pairs — only fully ready content: // pair published + linked question/statements published + a published picture exists. // (Audio coverage is additionally enforced in Phase 2.) + // Pagination: bereits abgeschlossene (user_pair_progress) und vom Client + // geladene Pairs werden ausgeschlossen; leere Antwort = keine weiteren Karten. const pairsRes = await query( `SELECT p.id, p.answer_type, p.status, p.difficulty_level, p.question_id, p.positive_statement_id, p.negative_statement_id @@ -61,9 +69,13 @@ router.get('/', requireJwt, async (req, res, next) => { JOIN object_pictures pic ON pic.object_id = op.object_id JOIN pictures pp ON pp.id = pic.picture_id WHERE op.pair_id = p.id AND pp.status = 'published') + AND NOT EXISTS ( + SELECT 1 FROM user_pair_progress upp + WHERE upp.pair_id = p.id AND upp.user_id = $2) + AND p.id <> ALL($3::uuid[]) ORDER BY random() LIMIT $1`, - [limit] + [limit, userId, exclude] ); if (!pairsRes.rows.length) return res.json([]); const pairs = pairsRes.rows;