feat: progressive Level-Kurve + atomarer /auth/progress-Vertrag
- levelForEp/levelInfo (Level 1 bei 20 EP statt fixer 500/Level), src/lib/leveling.js - /auth/me liefert level + ep_into_level + ep_to_next_level - /auth/progress liefert prev_level, streak_increased, daily_ep, daily_goal_ep, goal_just_reached (CTE fängt die Pre-Update-Werte, damit Level-Up/Streak-Up atomar erkennbar sind) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,7 @@ const router = require('express').Router();
|
||||
const bcrypt = require('bcryptjs');
|
||||
const jwt = require('jsonwebtoken');
|
||||
const { query } = require('../db');
|
||||
const { levelForEp, levelInfo } = require('../lib/leveling');
|
||||
|
||||
function signToken(user) {
|
||||
return jwt.sign(
|
||||
@@ -153,7 +154,7 @@ router.get('/me', requireJwt, async (req, res, next) => {
|
||||
);
|
||||
if (!r.rows.length) return res.status(404).json({ error: 'User not found' });
|
||||
const row = r.rows[0];
|
||||
row.level = Math.floor((row.total_ep || 0) / 500);
|
||||
Object.assign(row, levelInfo(row.total_ep)); // level + ep_into_level + ep_to_next_level
|
||||
res.json(row);
|
||||
} catch (err) { next(err); }
|
||||
});
|
||||
@@ -179,38 +180,58 @@ router.post('/progress', requireJwt, async (req, res, next) => {
|
||||
[userId, pair_id, isCorrect ? 1 : 0, isCorrect ? 0 : 1, pts]
|
||||
);
|
||||
|
||||
// Tagesverlauf upserten (für Streak-Kalender, Wochengraph, Tagesziel)
|
||||
await query(
|
||||
// Tagesverlauf upserten (für Streak-Kalender, Wochengraph, Tagesziel).
|
||||
// RETURNING ep_earned = NEUER Tagesstand → Tagesziel-Übergang erkennbar.
|
||||
const day = await query(
|
||||
`INSERT INTO user_daily_activity (user_id, activity_date, ep_earned, cards_done, correct_count)
|
||||
VALUES ($1, CURRENT_DATE, $2, 1, $3)
|
||||
ON CONFLICT (user_id, activity_date) DO UPDATE SET
|
||||
ep_earned = user_daily_activity.ep_earned + $2,
|
||||
cards_done = user_daily_activity.cards_done + 1,
|
||||
correct_count = user_daily_activity.correct_count + $3`,
|
||||
correct_count = user_daily_activity.correct_count + $3
|
||||
RETURNING ep_earned`,
|
||||
[userId, pts, isCorrect ? 1 : 0]
|
||||
);
|
||||
|
||||
// EP + Streak auf users_public; Streak: +1 bei neuem Tag, Reset bei Lücke > 1 Tag
|
||||
// EP + Streak auf users_public; Streak: +1 bei neuem Tag, Reset bei Lücke > 1 Tag.
|
||||
// CTE fängt die Pre-Update-Werte mit, damit Level-Up/Streak-Up atomar erkennbar sind.
|
||||
const upd = await query(
|
||||
`UPDATE users_public SET
|
||||
total_ep = total_ep + $2,
|
||||
`WITH prev AS (
|
||||
SELECT total_ep AS prev_ep, streak_days AS prev_streak
|
||||
FROM users_public WHERE user_id = $1
|
||||
)
|
||||
UPDATE users_public up SET
|
||||
total_ep = up.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
|
||||
WHEN up.last_practice_at IS NULL THEN 1
|
||||
WHEN up.last_practice_at::date = CURRENT_DATE THEN up.streak_days
|
||||
WHEN up.last_practice_at::date = CURRENT_DATE - INTERVAL '1 day' THEN up.streak_days + 1
|
||||
ELSE 1
|
||||
END,
|
||||
last_practice_at = NOW()
|
||||
WHERE user_id = $1
|
||||
RETURNING total_ep, streak_days`,
|
||||
FROM prev
|
||||
WHERE up.user_id = $1
|
||||
RETURNING up.total_ep, up.streak_days, up.daily_goal_ep, prev.prev_ep, prev.prev_streak`,
|
||||
[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) });
|
||||
const r = upd.rows[0];
|
||||
const daily_ep = day.rows[0]?.ep_earned ?? pts;
|
||||
const daily_goal_ep = r.daily_goal_ep || 30;
|
||||
res.json({
|
||||
total_ep: r.total_ep,
|
||||
level: levelForEp(r.total_ep),
|
||||
prev_level: levelForEp(r.prev_ep),
|
||||
streak_days: r.streak_days,
|
||||
streak_increased: r.streak_days > r.prev_streak,
|
||||
daily_ep,
|
||||
daily_goal_ep,
|
||||
// Schwellen-Übergang: jetzt erreicht, vorher (ohne diese Karte) noch nicht
|
||||
goal_just_reached: daily_ep >= daily_goal_ep && (daily_ep - pts) < daily_goal_ep,
|
||||
});
|
||||
} catch (err) { next(err); }
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user