Voeg 28 herkenbare kleurplaten en creatieve niveaus toe (v0.4.66-beta) #4
4 changed files with 39 additions and 17 deletions
2
VERSION
2
VERSION
|
|
@ -1 +1 @@
|
||||||
0.3.96-beta
|
0.3.97-beta
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
/* version — shown until /api/version resolves (or if the fetch fails, e.g. offline).
|
/* version — shown until /api/version resolves (or if the fetch fails, e.g. offline).
|
||||||
Kept in sync by hand with the VERSION file at the repo root on every release. */
|
Kept in sync by hand with the VERSION file at the repo root on every release. */
|
||||||
const VERSION = "0.3.96-beta";
|
const VERSION = "0.3.97-beta";
|
||||||
(function(){
|
(function(){
|
||||||
const tag = document.getElementById("verTag");
|
const tag = document.getElementById("verTag");
|
||||||
tag.textContent = "v"+VERSION;
|
tag.textContent = "v"+VERSION;
|
||||||
|
|
|
||||||
38
src/api.js
38
src/api.js
|
|
@ -65,8 +65,12 @@ export default async function api(app) {
|
||||||
|
|
||||||
app.setErrorHandler((err, req, reply) => {
|
app.setErrorHandler((err, req, reply) => {
|
||||||
if (reply.statusCode >= 400 && reply.statusCode < 500) return reply.send({ error: err.message });
|
if (reply.statusCode >= 400 && reply.statusCode < 500) return reply.send({ error: err.message });
|
||||||
req.log.error(err);
|
req.log.error({ err }, 'onverwachte serverfout');
|
||||||
reply.code(500).send({ error: 'serverfout' });
|
// Voor ingelogde staf (die dit sowieso al kan zien in de detailweergave)
|
||||||
|
// geven we de echte oorzaak mee i.p.v. een kale "serverfout": anders is
|
||||||
|
// een fout als deze niet te diagnosticeren zonder in de serverlogs te kijken.
|
||||||
|
const detail = req.user ? `serverfout: ${err.message}` : 'serverfout';
|
||||||
|
reply.code(500).send({ error: detail });
|
||||||
});
|
});
|
||||||
|
|
||||||
// ---- version --------------------------------------------------------------
|
// ---- version --------------------------------------------------------------
|
||||||
|
|
@ -694,25 +698,29 @@ export default async function api(app) {
|
||||||
|
|
||||||
app.delete('/admin/users/:id', async (req, reply) => {
|
app.delete('/admin/users/:id', async (req, reply) => {
|
||||||
need(req, reply, PERMISSIONS['users.manage']);
|
need(req, reply, PERMISSIONS['users.manage']);
|
||||||
const r = await pool.query('SELECT * FROM users WHERE id = $1', [req.params.id]);
|
// Alles vanaf hier (ook de opzoek- en rechtencontroles, niet alleen de
|
||||||
const u = r.rows[0];
|
// verwijdering zelf) in één try/catch: anders valt een fout die vóór
|
||||||
if (!u) return fail(reply, 404, 'gebruiker onbekend');
|
// deleteUserFully() optreedt (bv. een kapotte query) terug op de kale
|
||||||
if (Number(u.id) === Number(req.user.id)) return fail(reply, 400, 'je kunt jezelf niet verwijderen');
|
// "serverfout" van de generieke handler i.p.v. een zichtbare reden.
|
||||||
if (!sameSchool(req, u)) return fail(reply, 403, 'geen rechten');
|
|
||||||
if (teacherOnly(req) && u.role !== 'pupil') return fail(reply, 403, 'geen rechten');
|
|
||||||
if (teacherOnly(req) && !(u.class_id && (await classOwnedByTeacher(u.class_id, req.user.id))))
|
|
||||||
return fail(reply, 403, 'groepsleiding beheert alleen leerlingen uit eigen klassen');
|
|
||||||
if (u.role === 'super' && !req.user.allRoles.includes('super')) return fail(reply, 403, 'geen rechten');
|
|
||||||
try {
|
try {
|
||||||
|
const r = await pool.query('SELECT * FROM users WHERE id = $1', [req.params.id]);
|
||||||
|
const u = r.rows[0];
|
||||||
|
if (!u) return fail(reply, 404, 'gebruiker onbekend');
|
||||||
|
if (Number(u.id) === Number(req.user.id)) return fail(reply, 400, 'je kunt jezelf niet verwijderen');
|
||||||
|
if (!sameSchool(req, u)) return fail(reply, 403, 'geen rechten');
|
||||||
|
if (teacherOnly(req) && u.role !== 'pupil') return fail(reply, 403, 'geen rechten');
|
||||||
|
if (teacherOnly(req) && !(u.class_id && (await classOwnedByTeacher(u.class_id, req.user.id))))
|
||||||
|
return fail(reply, 403, 'groepsleiding beheert alleen leerlingen uit eigen klassen');
|
||||||
|
if (u.role === 'super' && !req.user.allRoles.includes('super')) return fail(reply, 403, 'geen rechten');
|
||||||
await deleteUserFully(u.id);
|
await deleteUserFully(u.id);
|
||||||
|
return { ok: true };
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Zou niet meer moeten voorkomen (alle afhankelijke rijen zijn hierboven
|
// Zou niet meer moeten voorkomen (alle afhankelijke rijen worden in
|
||||||
// al weggehaald), maar als het tóch faalt geven we de echte reden mee i.p.v.
|
// deleteUserFully al weggehaald), maar als het tóch faalt geven we de
|
||||||
// een kale "serverfout", zodat de oorzaak zichtbaar is.
|
// echte reden mee i.p.v. een kale "serverfout", zodat de oorzaak zichtbaar is.
|
||||||
req.log.error({ err: e }, 'verwijderen gebruiker mislukt');
|
req.log.error({ err: e }, 'verwijderen gebruiker mislukt');
|
||||||
return fail(reply, 409, `verwijderen mislukt: ${e.detail || e.message}`);
|
return fail(reply, 409, `verwijderen mislukt: ${e.detail || e.message}`);
|
||||||
}
|
}
|
||||||
return { ok: true };
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// ---- toewijzingen (leerling-omgeving) ------------------------------------------
|
// ---- toewijzingen (leerling-omgeving) ------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -232,6 +232,20 @@ test('als verwijderen tóch faalt, komt de echte reden terug (geen kale serverfo
|
||||||
await app.close();
|
await app.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('faalt de opzoekquery vóór deleteUserFully, dan ook de echte reden i.p.v. kale serverfout', async () => {
|
||||||
|
const { app } = await makeApp(superUser, { id: 9, role: 'teacher', school_id: 2, username: 'juf' });
|
||||||
|
const pool = app.pg;
|
||||||
|
const orig = pool.query.bind(pool);
|
||||||
|
pool.query = async (sql, params) => {
|
||||||
|
if (sql.startsWith('SELECT * FROM users WHERE id')) { const e = new Error('connection terminated'); throw e; }
|
||||||
|
return orig(sql, params);
|
||||||
|
};
|
||||||
|
const res = await app.inject({ method: 'DELETE', url: '/api/admin/users/9', cookies });
|
||||||
|
assert.equal(res.statusCode, 409, res.body);
|
||||||
|
assert.match(res.json().error, /connection terminated/);
|
||||||
|
await app.close();
|
||||||
|
});
|
||||||
|
|
||||||
test('migratie 015 herstelt ON DELETE CASCADE voor user-foreign-keys', async () => {
|
test('migratie 015 herstelt ON DELETE CASCADE voor user-foreign-keys', async () => {
|
||||||
const sql = await readFile('db/015_user_fk_cascade.sql', 'utf8');
|
const sql = await readFile('db/015_user_fk_cascade.sql', 'utf8');
|
||||||
assert.match(sql, /confrelid = 'users'::regclass/);
|
assert.match(sql, /confrelid = 'users'::regclass/);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue