source: Klonkt/src/services/guardianship/help.js@ 72d8eff

main
Last change on this file since 72d8eff was a29c8c8, checked in by roboburr <roboburr@…>, 5 weeks ago

Een hulpvraag oppikken en afsluiten, zichtbaar voor alle guardians (shaer-lgo)

Een hulpvraag (5.2.1) gaat naar ALLE guardians van een kind, op verschillende
servers. Zonder gedeelde staat denken er twee dat de ander het oppakt -- precies
het scenario waar de reddingsboei voor bestaat. Tot nu toe droeg een hulpvraag
geen enkele staat: een vlag op ap_mentions, verder niets.

DE FAALSTAND IS HIER NIET VEILIG, en dat stuurt het hele ontwerp. Bij een gate is
"dicht" het veilige antwoord. Hier is de faalstand "iedereen denkt dat het
geregeld is", en dat is gevaarlijker dan geen markering. Daarom staat een
hulpvraag bij twijfel OPEN: een lege lijst, een rij die we niet kunnen lezen, een
soort die we niet kennen -- alles wat geen expliciete afsluiting is telt als
"er wacht nog iemand".

Twee besluiten van Bart zitten in de vorm.

OPGEPIKT mag stapelen en vervalt niet, maar veroudert zichtbaar. Twee mensen die
tegelijk reageren op een kind is geen probleem; twee die allebei niets doen omdat
de ander het "geclaimd" had, wel. En een signaal dat vanzelf verdwijnt laat een
hulpvraag er onaangeroerd uitzien terwijl er iemand mee bezig is -- dus het blijft
staan en toont hoe oud het is.

AFGEHANDELD kent GEEN terugdraai. Sluiten gaat met een stevige bevestiging (geen
window.confirm, zelfde lijn als het loslaten van een ward), en leeft de vraag
daarna nog, dan wordt hij opnieuw gesteld -- een nieuwe hulpvraag. Er wordt niets
herschreven, er wordt toegevoegd. Een test bewaakt dat er geen undo() of
reopen() bestaat.

Geen nieuw protocol: de markering is een gewone directe note met een
shaer:-eigenschap, net als de zwaai en de afwezigheidsmelding. Daardoor reist hij
over de bestaande bezorging, ziet de WARD hem als bericht ("er komt iemand") en
houden de mede-guardians er staat aan over. Naar de ward en naar de andere
guardians tegelijk.

De eigen kopie wordt meteen weggeschreven, voordat er bezorgd is: het scherm van
degene die klikt hoort niet te liegen omdat een andere server traag is.

11 tests op het pure stuk. Gecontroleerd dat ze bijten: laat helpStatus altijd
"afgehandeld" zeggen en er vallen er vijf om. nl/en/de. Suite 576/576.

Niet gedekt: de weergave zelf (client-JS), en er is geen guardianship op dev om
het end-to-end te zien lopen.

  • Property mode set to 100644
File size: 5.4 KB
Line 
1/**
2 * Wie er op een hulpvraag af is, en wanneer hij is afgesloten (shaer-lgo).
3 *
4 * Een hulpvraag (FEP-633c 5.2.1) gaat naar ALLE guardians van een kind, die op
5 * verschillende servers zitten. Zonder gedeelde staat denken er twee dat de
6 * ander het oppakt -- en dat is precies het scenario waar de reddingsboei voor
7 * bestaat.
8 *
9 * DE FAALSTAND IS HIER NIET VEILIG, en dat maakt dit anders dan elke gate. Bij
10 * een gate is "dicht" het veilige antwoord. Hier is de faalstand "iedereen denkt
11 * dat het geregeld is", en dat is gevaarlijker dan geen markering. Daaruit volgt
12 * de regel die overal in dit bestand terugkomt: bij twijfel is een hulpvraag
13 * OPEN.
14 *
15 * Twee besluiten van Bart (7-8) zitten in de vorm:
16 *
17 * OPGEPIKT mag stapelen en vervalt niet, maar VEROUDERT zichtbaar. Twee mensen
18 * die tegelijk reageren op een kind is geen probleem; twee die allebei niets
19 * doen omdat de ander het "geclaimd" had, wel. En een signaal dat vanzelf
20 * verdwijnt laat een hulpvraag er onaangeroerd uitzien terwijl er iemand mee
21 * bezig is.
22 *
23 * AFGEHANDELD kent geen terugdraai. Sluiten gebeurt met een stevige
24 * bevestiging, en leeft de vraag daarna nog, dan wordt hij OPNIEUW GESTELD --
25 * een nieuwe hulpvraag. Er wordt niets herschreven, er wordt toegevoegd.
26 */
27
28import db from '../../config/database.js';
29
30let _s = null;
31function stmts() {
32 if (!_s) {
33 _s = {
34 ins: db.prepare(`INSERT OR IGNORE INTO ap_help_state (note_uri, guardian_uri, kind, guardian_handle)
35 VALUES (?,?,?,?)`),
36 forNote: db.prepare('SELECT * FROM ap_help_state WHERE note_uri = ? ORDER BY created_at ASC'),
37 forNotes: db.prepare('SELECT * FROM ap_help_state WHERE note_uri IN (SELECT value FROM json_each(?))'),
38 };
39 }
40 return _s;
41}
42
43/** Leg vast dat iemand deze hulpvraag heeft opgepikt of afgesloten. */
44export function record(noteUri, guardianUri, kind, handle = null) {
45 if (!noteUri || !guardianUri) return;
46 const k = kind === 'handled' ? 'handled' : 'pickup';
47 try { stmts().ins.run(noteUri, guardianUri, k, handle); } catch { /* nooit dragend */ }
48}
49
50/**
51 * De staat van een hulpvraag, uit zijn rijen. Puur, zodat de regels te toetsen
52 * zijn zonder database of scherm.
53 *
54 * `ageMs` is de leeftijd van de OUDSTE oppik. Daar tekent het scherm mee dat een
55 * signaal oud wordt -- niets verdwijnt, maar je ziet wel dat er misschien niets
56 * meer gebeurt.
57 */
58export function helpStatus(rows, now = Date.now()) {
59 const list = rows || [];
60 const pickups = list.filter((r) => r.kind === 'pickup');
61 const done = list.find((r) => r.kind === 'handled') || null;
62 const stamp = (r) => { const t = Date.parse(r.created_at); return isNaN(t) ? null : t; };
63 const oudste = pickups.map(stamp).filter((t) => t !== null).sort((a, b) => a - b)[0];
64 return {
65 // Namen erbij: "door wie" was de hele vraag. Zonder dat is het een vinkje.
66 pickedUpBy: pickups.map((r) => ({ uri: r.guardian_uri, handle: r.guardian_handle || null, at: r.created_at })),
67 handled: done ? { uri: done.guardian_uri, handle: done.guardian_handle || null, at: done.created_at } : null,
68 // Alleen betekenisvol zolang er niets is afgesloten.
69 ageMs: (!done && oudste) ? Math.max(0, now - oudste) : null,
70 // Waar het scherm op afgaat. Bij twijfel OPEN: een lege lijst, een rij die we
71 // niet kunnen lezen, wat dan ook -- alles wat geen expliciete afsluiting is,
72 // is een hulpvraag die nog op iemand wacht.
73 open: !done,
74 };
75}
76
77/** De staat van een hulpvraag zoals die nu is opgeslagen. */
78export function statusOf(noteUri, now = Date.now()) {
79 if (!noteUri) return helpStatus([], now);
80 try { return helpStatus(stmts().forNote.all(noteUri), now); } catch { return helpStatus([], now); }
81}
82
83/** Idem voor een hele lijst in een query, zodat een paneel geen N+1 wordt. */
84export function statusFor(noteUris, now = Date.now()) {
85 const uit = new Map();
86 const lijst = [...new Set((noteUris || []).filter(Boolean))];
87 if (!lijst.length) return uit;
88 let rijen = [];
89 try { rijen = stmts().forNotes.all(JSON.stringify(lijst)); } catch { rijen = []; }
90 const perNote = new Map();
91 for (const r of rijen) {
92 if (!perNote.has(r.note_uri)) perNote.set(r.note_uri, []);
93 perNote.get(r.note_uri).push(r);
94 }
95 for (const uri of lijst) uit.set(uri, helpStatus(perNote.get(uri) || [], now));
96 return uit;
97}
98
99/**
100 * De markering als bericht. Bewust een gewone directe note met een
101 * shaer:-markering, zoals de zwaai en de afwezigheidsmelding: dan reist het over
102 * de bestaande bezorging, en de WARD leest het als wat het is -- er komt iemand.
103 */
104export function markerNote({ id, me, noteUri, kind, to }) {
105 const k = kind === 'handled' ? 'handled' : 'pickup';
106 return {
107 id, type: 'Note', attributedTo: me, to,
108 [k === 'handled' ? 'shaer:helpHandled' : 'shaer:helpPickup']: noteUri,
109 content: k === 'handled'
110 ? '<p>Deze hulpvraag is afgehandeld.</p>'
111 : '<p>Ik kijk hiernaar.</p>',
112 };
113}
114
115/** Leest een binnengekomen note als markering, of null als hij er geen is. */
116export function parseMarker(object) {
117 if (!object || typeof object !== 'object') return null;
118 const pickup = object['shaer:helpPickup'];
119 const handled = object['shaer:helpHandled'];
120 if (typeof handled === 'string' && handled) return { kind: 'handled', noteUri: handled };
121 if (typeof pickup === 'string' && pickup) return { kind: 'pickup', noteUri: pickup };
122 return null;
123}
124
125export default { record, helpStatus, statusOf, statusFor, markerNote, parseMarker };
Note: See TracBrowser for help on using the repository browser.