Implement Wettkampf mode with random type-aware question selection and answer persistence
This commit is contained in:
+9
-1
@@ -61,4 +61,12 @@ async function batchCreateRows(tableId, rows) {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { listRows, createRow, batchCreateRows };
|
||||
async function updateRow(tableId, rowId, data) {
|
||||
console.log(`[baserow] Update row ${rowId} in table ${tableId}`);
|
||||
return baserowFetch(`/database/rows/table/${tableId}/${rowId}/?user_field_names=true`, {
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = { listRows, createRow, batchCreateRows, updateRow };
|
||||
+128
-4
@@ -2,7 +2,7 @@ const express = require('express');
|
||||
const http = require('http');
|
||||
const path = require('path');
|
||||
const { getEnvConfig, readConfig, writeConfig } = require('./config');
|
||||
const { listRows, createRow, batchCreateRows } = require('./baserow');
|
||||
const { listRows, createRow, batchCreateRows, updateRow } = require('./baserow');
|
||||
const { setupWebSocket, getSessions } = require('./live-session');
|
||||
const { extractFieldValue } = require('./quiz-logic');
|
||||
|
||||
@@ -106,19 +106,77 @@ app.get('/api/sets/:id/questions', async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
/* ---- Solo: Save single answer immediately ---- */
|
||||
app.get('/api/sets/:id/count', async (req, res) => {
|
||||
try {
|
||||
const config = readConfig();
|
||||
const set = config.sets.find((s) => s.id === req.params.id);
|
||||
if (!set) return res.status(404).json({ error: 'Fragenset nicht gefunden' });
|
||||
const rows = await listRows(set.tableId);
|
||||
res.json({ count: rows.length });
|
||||
} catch (e) {
|
||||
res.status(500).json({ error: e.message });
|
||||
}
|
||||
});
|
||||
|
||||
/* ---- Solo/Wettkampf: Save single answer immediately ---- */
|
||||
app.post('/api/results/single', async (req, res) => {
|
||||
try {
|
||||
const config = readConfig();
|
||||
if (!config.answersTableId) return res.status(400).json({ error: 'Antworten-Tabelle nicht konfiguriert' });
|
||||
await createRow(config.answersTableId, req.body);
|
||||
res.json({ success: true });
|
||||
const row = await createRow(config.answersTableId, req.body);
|
||||
res.json({ success: true, id: row.id });
|
||||
} catch (e) {
|
||||
console.error('[api] Single save error:', e.message);
|
||||
res.status(500).json({ error: e.message });
|
||||
}
|
||||
});
|
||||
|
||||
/* ---- Wettkampf: Update existing answer ---- */
|
||||
app.put('/api/results/update', async (req, res) => {
|
||||
try {
|
||||
const config = readConfig();
|
||||
if (!config.answersTableId) return res.status(400).json({ error: 'Antworten-Tabelle nicht konfiguriert' });
|
||||
const { sessionId, frageId, nutzername, antwort } = req.body;
|
||||
if (!sessionId || !frageId || !nutzername) return res.status(400).json({ error: 'sessionId, frageId und nutzername erforderlich' });
|
||||
|
||||
// Find the existing row
|
||||
const rows = await listRows(config.answersTableId, {
|
||||
Session_ID: sessionId,
|
||||
Nutzername: nutzername,
|
||||
});
|
||||
const existing = rows.find((r) => r['Frage_ID'] === frageId);
|
||||
if (!existing) return res.status(404).json({ error: 'Antwort nicht gefunden' });
|
||||
|
||||
await updateRow(config.answersTableId, existing.id, { Antwort: antwort });
|
||||
res.json({ success: true });
|
||||
} catch (e) {
|
||||
console.error('[api] Update answer error:', e.message);
|
||||
res.status(500).json({ error: e.message });
|
||||
}
|
||||
});
|
||||
|
||||
/* ---- Wettkampf: Finalize scores ---- */
|
||||
app.put('/api/results/finalize', async (req, res) => {
|
||||
try {
|
||||
const config = readConfig();
|
||||
if (!config.answersTableId) return res.status(400).json({ error: 'Antworten-Tabelle nicht konfiguriert' });
|
||||
const { results } = req.body;
|
||||
if (!results || !results.length) return res.status(400).json({ error: 'Keine Ergebnisse' });
|
||||
|
||||
// results is array of { rowId, richtig, punkte }
|
||||
for (const r of results) {
|
||||
await updateRow(config.answersTableId, r.rowId, {
|
||||
Richtig: r.richtig,
|
||||
Punkte: r.punkte,
|
||||
});
|
||||
}
|
||||
res.json({ success: true });
|
||||
} catch (e) {
|
||||
console.error('[api] Finalize error:', e.message);
|
||||
res.status(500).json({ error: e.message });
|
||||
}
|
||||
});
|
||||
|
||||
/* ---- Solo: Check for incomplete session ---- */
|
||||
app.get('/api/progress/:name/:setId', async (req, res) => {
|
||||
try {
|
||||
@@ -187,6 +245,72 @@ app.get('/api/progress/:name/:setId', async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
/* ---- Wettkampf: Check for incomplete session ---- */
|
||||
app.get('/api/wettkampf-progress/:name/:setId', async (req, res) => {
|
||||
try {
|
||||
const config = readConfig();
|
||||
const set = config.sets.find((s) => s.id === req.params.setId);
|
||||
if (!set) return res.status(404).json({ error: 'Set nicht gefunden' });
|
||||
if (!config.answersTableId) return res.json({ hasProgress: false });
|
||||
|
||||
const allAnswers = await listRows(config.answersTableId, {
|
||||
Nutzername: req.params.name,
|
||||
Fragenset: set.name,
|
||||
});
|
||||
|
||||
// Filter to Wettkampf only
|
||||
const wkAnswers = allAnswers.filter((a) => {
|
||||
return extractFieldValue(a['Modus'], '') === 'Wettkampf';
|
||||
});
|
||||
|
||||
// Group by Session_ID
|
||||
const bySession = {};
|
||||
wkAnswers.forEach((a) => {
|
||||
const sid = a['Session_ID'] || '';
|
||||
if (!bySession[sid]) bySession[sid] = [];
|
||||
bySession[sid].push(a);
|
||||
});
|
||||
|
||||
// Find the most recent session where NOT all answers are finalized (Punkte is still 0 for all)
|
||||
let best = null;
|
||||
for (const [sid, answers] of Object.entries(bySession)) {
|
||||
// A session is "incomplete" if all Punkte are 0 and all Richtig are false (not yet confirmed)
|
||||
const allUnscored = answers.every((a) => {
|
||||
const punkte = Number(a['Punkte']) || 0;
|
||||
const richtig = a['Richtig'];
|
||||
return punkte === 0 && (richtig === false || richtig === 'false' || !richtig);
|
||||
});
|
||||
if (allUnscored && answers.length > 0) {
|
||||
const latest = answers.reduce((max, a) => {
|
||||
const ts = a['Zeitstempel'] || '';
|
||||
return ts > max ? ts : max;
|
||||
}, '');
|
||||
if (!best || latest > best.latest) {
|
||||
best = { sid, answers, latest };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (best) {
|
||||
res.json({
|
||||
hasProgress: true,
|
||||
sessionId: best.sid,
|
||||
answers: best.answers.map((a) => ({
|
||||
rowId: a.id,
|
||||
Frage_ID: a['Frage_ID'],
|
||||
Antwort: a['Antwort'] || '',
|
||||
})),
|
||||
totalAnswered: best.answers.length,
|
||||
});
|
||||
} else {
|
||||
res.json({ hasProgress: false });
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('[api] Wettkampf progress error:', e.message);
|
||||
res.status(500).json({ error: e.message });
|
||||
}
|
||||
});
|
||||
|
||||
/* ---- Batch save (still used by live sessions) ---- */
|
||||
app.post('/api/results', async (req, res) => {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user