feat(geom9 ch3 wave2 + final): §12 «Герон» + Финал Главы 3
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
'use strict';
|
||||
const router = require('express').Router();
|
||||
const db = require('../db/db');
|
||||
const { authMiddleware } = require('../middleware/auth');
|
||||
|
||||
router.use(authMiddleware);
|
||||
|
||||
/* ── Statements (prepared once) ────────────────────────────────── */
|
||||
const SQL = {
|
||||
listTracks: db.prepare(`
|
||||
SELECT exam_key, title, subject_slug, grade, duration_min,
|
||||
tasks_per_variant, variants_count, intro_html, sort_order
|
||||
FROM exam_tracks
|
||||
WHERE enabled = 1
|
||||
ORDER BY sort_order, exam_key
|
||||
`),
|
||||
getTrack: db.prepare(`
|
||||
SELECT exam_key, title, subject_slug, grade, duration_min,
|
||||
tasks_per_variant, variants_count, scoring_json, intro_html
|
||||
FROM exam_tracks
|
||||
WHERE exam_key = ? AND enabled = 1
|
||||
`),
|
||||
countTasks: db.prepare(`
|
||||
SELECT
|
||||
COUNT(*) AS total,
|
||||
COALESCE(SUM(CASE WHEN task_type = 'mc' THEN 1 ELSE 0 END), 0) AS mc,
|
||||
COALESCE(SUM(CASE WHEN task_type = 'open' THEN 1 ELSE 0 END), 0) AS open,
|
||||
COALESCE(SUM(CASE WHEN task_type = 'long' THEN 1 ELSE 0 END), 0) AS long
|
||||
FROM exam_tasks
|
||||
WHERE exam_key = ?
|
||||
`),
|
||||
/* For each (user, task) — was the most-recent attempt correct?
|
||||
A task counts as «solved» iff at least one historical attempt is_correct = 1. */
|
||||
userProgress: db.prepare(`
|
||||
SELECT
|
||||
COUNT(DISTINCT exam_task_id) AS tasks_attempted,
|
||||
COUNT(DISTINCT CASE WHEN is_correct = 1 THEN exam_task_id END) AS tasks_solved,
|
||||
COUNT(*) AS total_attempts,
|
||||
COALESCE(SUM(CASE WHEN is_correct = 1 THEN 1 ELSE 0 END), 0) AS correct_attempts
|
||||
FROM exam_attempts a
|
||||
JOIN exam_tasks t ON t.id = a.exam_task_id
|
||||
WHERE a.user_id = ? AND t.exam_key = ?
|
||||
`),
|
||||
};
|
||||
|
||||
/* ── GET /api/exam-prep/tracks ──
|
||||
Public list of enabled exam tracks (for a future landing page). */
|
||||
router.get('/tracks', (_req, res) => {
|
||||
const tracks = SQL.listTracks.all();
|
||||
res.json({ tracks });
|
||||
});
|
||||
|
||||
/* ── GET /api/exam-prep/:examKey/info ──
|
||||
Track metadata + global counts + this user's aggregate progress. */
|
||||
router.get('/:examKey/info', (req, res) => {
|
||||
const { examKey } = req.params;
|
||||
const track = SQL.getTrack.get(examKey);
|
||||
if (!track) return res.status(404).json({ error: 'Unknown exam track' });
|
||||
|
||||
const counts = SQL.countTasks.get(examKey);
|
||||
const progress = SQL.userProgress.get(req.user.id, examKey);
|
||||
|
||||
// Parse scoring grid (JSON in DB). Tolerant of malformed values.
|
||||
let scoring = null;
|
||||
if (track.scoring_json) {
|
||||
try { scoring = JSON.parse(track.scoring_json); } catch { scoring = null; }
|
||||
}
|
||||
|
||||
res.json({
|
||||
track: {
|
||||
exam_key: track.exam_key,
|
||||
title: track.title,
|
||||
subject: track.subject_slug,
|
||||
grade: track.grade,
|
||||
duration_min: track.duration_min,
|
||||
tasks_per_variant: track.tasks_per_variant,
|
||||
variants_count: track.variants_count,
|
||||
intro_html: track.intro_html,
|
||||
scoring,
|
||||
},
|
||||
counts,
|
||||
progress,
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
Reference in New Issue
Block a user