fix: gebruikerslijst per school laadde nooit (SELECT DISTINCT/ORDER BY) (v0.3.99-beta)
All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 1m13s

- linkedParents() gebruikte "SELECT DISTINCT p.* ... ORDER BY lower(p.username)":
  Postgres weigert dit omdat de ORDER BY-expressie niet letterlijk in de
  select-lijst staat. Deze query loopt bij élke school-gescopede
  gebruikerslijst (elke schoolbeheerder, en elke systeemmanager die een
  specifieke school kiest i.p.v. "alle scholen") - dus die lijst laadde
  nooit, klassen werden om dezelfde reden niet (opnieuw) opgehaald, en
  "alle scholen" was de enige werkende weergave
- fix: lower(p.username) expliciet als kolom (sort_key) in de select-lijst,
  ORDER BY daarop i.p.v. op de losse expressie
- regressietest die de exacte SQL-vorm vastlegt (een gemockte pool voert geen
  echte SQL uit en had deze Postgres-grammaticafout niet kunnen vangen)
This commit is contained in:
Ramon 2026-07-18 03:19:36 +02:00
parent 205ba74eff
commit be8ecc5378
4 changed files with 16 additions and 5 deletions

View file

@ -1 +1 @@
0.3.98-beta
0.3.99-beta

View file

@ -2,7 +2,7 @@
"use strict";
/* 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. */
const VERSION = "0.3.98-beta";
const VERSION = "0.3.99-beta";
(function(){
const tag = document.getElementById("verTag");
tag.textContent = "v"+VERSION;

View file

@ -441,11 +441,15 @@ export default async function api(app) {
need(req, reply, PERMISSIONS['users.manage']);
// Ouders hebben school_id NULL en vallen daardoor buiten elke schoollijst;
// een school ziet wél de ouders die aan haar eigen leerlingen gekoppeld zijn.
// Postgres eist bij SELECT DISTINCT dat elke ORDER BY-expressie letterlijk
// in de select-lijst staat - lower(p.username) alleen via p.* volstond niet
// (gaf "for SELECT DISTINCT, ORDER BY expressions must appear in select
// list"), dus expliciet als extra kolom toevoegen.
const linkedParents = async (schoolId, extra = '', params = []) => (await pool.query(
`SELECT DISTINCT p.* FROM users p
`SELECT DISTINCT p.*, lower(p.username) AS sort_key FROM users p
JOIN parent_children pc ON pc.parent_id = p.id
JOIN users k ON k.id = pc.pupil_id
WHERE k.school_id = $1 ${extra} ORDER BY lower(p.username)`,
WHERE k.school_id = $1 ${extra} ORDER BY sort_key`,
[schoolId, ...params])).rows;
let rows;
if (req.user.allRoles.includes('super')) {

View file

@ -289,7 +289,7 @@ test('afwijzen verwijdert alleen het verzoek; andere school mag niet afhandelen'
});
test('schoolbeheerder ziet gekoppelde ouders (met kindernamen) in de gebruikerslijst', async () => {
const { app } = await makeApp({ user: admin, respond: (sql) => {
const { app, calls } = await makeApp({ user: admin, respond: (sql) => {
if (sql.startsWith('SELECT * FROM users WHERE school_id')) return { rows: [
{ id: 50, username: 'lena', role: 'pupil', school_id: 2, class_id: 5 },
] };
@ -306,6 +306,13 @@ test('schoolbeheerder ziet gekoppelde ouders (met kindernamen) in de gebruikersl
const p = res.json().users.find((u) => u.role === 'parent');
assert.ok(p, 'ouder hoort in de schoollijst te staan');
assert.deepEqual(p.childNames, ['Lena']);
// regressie: Postgres weigert "SELECT DISTINCT ... ORDER BY lower(p.username)"
// omdat die expressie niet letterlijk in de select-lijst staat (was stuk in
// productie voor elke school-gescopede gebruikerslijst) - de sort-expressie
// moet dus als kolom in de SELECT staan, niet alleen in de ORDER BY.
const linkedParentsCall = calls.find((c) => c.sql.includes('JOIN parent_children pc ON pc.parent_id = p.id'));
assert.match(linkedParentsCall.sql, /SELECT DISTINCT p\.\*, lower\(p\.username\) AS sort_key/);
assert.match(linkedParentsCall.sql, /ORDER BY sort_key/);
await app.close();
});