feat: Status-Pipeline (reviewed), Audio-Verknüpfung+Coverage, EP-Fortschritt, Wort-Generierung
- reviewed-Status für objects/questions/statements/pairs (Constraints) - feed: nur fertige Inhalte (published + Bild + Audio-Gate), audio_url - pairs: Publish-Gating (draft→published = 409) - audios: source_table/source_id/source_field/language + Unique-Index; generate-for, generate-batch, GET /coverage; voices.js (Voice je Sprache) - auth: POST /auth/progress, /auth/me mit total_ep/streak/level; users_public EP-Spalten + user_pair_progress.earned_points - claude: POST /generate-words; words POST akzeptiert status Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -136,6 +136,9 @@ router.get('/me', requireJwt, async (req, res, next) => {
|
||||
const r = await query(
|
||||
`SELECT u.id, u.email, u.role,
|
||||
un.username,
|
||||
COALESCE(up.total_ep, 0) AS total_ep,
|
||||
COALESCE(up.streak_days, 0) AS streak_days,
|
||||
up.last_practice_at,
|
||||
ln.id AS language_native_id, ln.short_en AS language_native_short, ln.titel_de AS language_native_titel,
|
||||
lt.id AS language_target_id, lt.short_en AS language_target_short, lt.titel_de AS language_target_titel
|
||||
FROM users u
|
||||
@@ -147,7 +150,54 @@ router.get('/me', requireJwt, async (req, res, next) => {
|
||||
[req.user.userId]
|
||||
);
|
||||
if (!r.rows.length) return res.status(404).json({ error: 'User not found' });
|
||||
res.json(r.rows[0]);
|
||||
const row = r.rows[0];
|
||||
row.level = Math.floor((row.total_ep || 0) / 500);
|
||||
res.json(row);
|
||||
} catch (err) { next(err); }
|
||||
});
|
||||
|
||||
// POST /auth/progress — eine gelöste Karte verbuchen (EP + Streak + Detail pro Pair)
|
||||
router.post('/progress', requireJwt, async (req, res, next) => {
|
||||
try {
|
||||
const userId = req.user.userId;
|
||||
const { pair_id, correct, points } = req.body;
|
||||
if (!pair_id) return res.status(400).json({ error: 'pair_id is required' });
|
||||
const pts = Math.max(0, parseInt(points) || 0);
|
||||
const isCorrect = correct === true || correct === 'true';
|
||||
|
||||
// Detail pro Pair upserten
|
||||
await query(
|
||||
`INSERT INTO user_pair_progress (user_id, pair_id, seen_count, correct_count, wrong_count, earned_points)
|
||||
VALUES ($1, $2, 1, $3, $4, $5)
|
||||
ON CONFLICT (user_id, pair_id) DO UPDATE SET
|
||||
seen_count = user_pair_progress.seen_count + 1,
|
||||
correct_count = user_pair_progress.correct_count + $3,
|
||||
wrong_count = user_pair_progress.wrong_count + $4,
|
||||
earned_points = user_pair_progress.earned_points + $5`,
|
||||
[userId, pair_id, isCorrect ? 1 : 0, isCorrect ? 0 : 1, pts]
|
||||
);
|
||||
|
||||
// EP + Streak auf users_public; Streak: +1 bei neuem Tag, Reset bei Lücke > 1 Tag
|
||||
const upd = await query(
|
||||
`UPDATE users_public SET
|
||||
total_ep = total_ep + $2,
|
||||
streak_days = CASE
|
||||
WHEN last_practice_at IS NULL THEN 1
|
||||
WHEN last_practice_at::date = CURRENT_DATE THEN streak_days
|
||||
WHEN last_practice_at::date = CURRENT_DATE - INTERVAL '1 day' THEN streak_days + 1
|
||||
ELSE 1
|
||||
END,
|
||||
last_practice_at = NOW()
|
||||
WHERE user_id = $1
|
||||
RETURNING total_ep, streak_days`,
|
||||
[userId, pts]
|
||||
);
|
||||
|
||||
if (!upd.rows.length)
|
||||
return res.status(409).json({ error: 'Kein Profil vorhanden. Bitte zuerst Profil anlegen.' });
|
||||
|
||||
const { total_ep, streak_days } = upd.rows[0];
|
||||
res.json({ total_ep, streak_days, level: Math.floor(total_ep / 500) });
|
||||
} catch (err) { next(err); }
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user