Ignore:
File:
1 edited

Legend:

Unmodified
Added
Removed
  • src/routes/guardian.js

    rfa33214 ree78e03  
    5757    'prop_line', 'prop_embeds', 'prop_play', 'prop_on', 'prop_off',
    5858    'prop_st_open', 'prop_st_accepted', 'prop_st_rejected', 'prop_st_expired',
    59     'panel_guards_far'];
     59    'panel_guards_far',
     60    // Het gate-paneel per ward (shaer-ahy.1): een rij per gate, met het soort en
     61    // de drempel erbij. De namen volgen de catalogus in gated.js.
     62    'gate_externalEmbeds', 'gate_externalPlayback', 'gate_follows',
     63    'gate_kind_setting', 'gate_kind_perRequest', 'gate_kind_handover',
     64    'gate_unknown', 'gate_threshold', 'gate_threshold_unknown',
     65    'gate_irreversible', 'gate_waiting', 'gate_blocked', 'gate_propose',
     66    // Oppikken en afhandelen van een hulpvraag (shaer-lgo).
     67    'help_pick', 'help_close', 'help_picked_by', 'help_handled_by', 'help_handled_note',
     68    'help_close_ask', 'help_close_yes', 'help_just_now', 'help_hours', 'help_days',
     69    'help_archive', 'help_archive_hide'];
    6070  const s = Object.fromEntries(keys.map((k) => [k, i18nT(L, `guardian.${k}`)]));
    6171  s.wave = i18nT(L, 'guardian.wave');
     
    7181            emoji_json, actor_emoji_json, media_json, quote_json, embed_json
    7282     FROM ap_mentions WHERE slug = ? AND help_request = 1 ORDER BY created_at DESC LIMIT 50`
    73   ).all(site.slug).map((h) => ({
     83  ).all(site.slug);
     84  // De gedeelde staat in EEN query (shaer-lgo): wie er al op af is en of het is
     85  // afgesloten. Per kaart vragen zou hier een N+1 opleveren, en dit is precies
     86  // het scherm dat een guardian in een haast openslaat.
     87  const helpStaat = Guardianship.help.statusFor(help.map((h) => h.object_uri));
     88  const helpItems = help.map((h) => ({
    7489    ...h,
     90    // Bij twijfel OPEN. Een hulpvraag die er afgehandeld uitziet terwijl hij dat
     91    // niet is, is de gevaarlijke fout -- niet andersom.
     92    state: helpStaat.get(h.object_uri) || { open: true, pickedUpBy: [], handled: null, ageMs: null },
    7593    // The dashboard is built in the browser, so it gets the body finished: the
    7694    // same partial de Krant and Berichten use. A 🛟 often carries a screenshot
     
    103121        status: Guardianship.gated.sentStatus(p, Date.now()),
    104122      })),
     123      // Alles wat voor dit kind gated is op EEN plek, met per gate het soort en
     124      // de drempel (shaer-ahy.1). Losse knoppen lieten een guardian zelf
     125      // uitzoeken wat er allemaal geldt; wat niet verstelbaar is stond nergens.
     126      gates: wardGates(site.slug, w.other_uri),
    105127    })),
    106128    offers: Guardianship.offersCollection(`${me}/queues/offers`, site.slug, me).orderedItems,
     
    113135      id: r.id, ward: r.ward_uri, proposer: r.proposer, feature: r.feature, value: !!r.value,
    114136    })),
    115     help,
     137    help: helpItems,
    116138    strings: uiStrings(L),
    117139  };
     
    192214  const site = siteForUser(req);
    193215  if (!site) return res.status(404).json({ error: 'no_site' });
     216  const L = resolveLang(req);
    194217  ensureWardConnections(site);
    195218  const wardUris = new Set(Guardianship.listWards(site.slug).map((w) => w.other_uri));
     
    209232      cw: p.cw || null,
    210233      media: p.media_json ? JSON.parse(p.media_json) : [],
     234      // Een post van je ward hoort er hetzelfde uit te zien als in de Krant en
     235      // in Berichten: dezelfde partial, dus opmaak, media, quote-kaart en
     236      // embed. Tot nu toe kreeg de PWA alleen kale content -- een guardian zag
     237      // een lege regel waar een foto stond. `content` blijft ernaast staan voor
     238      // een client die nog uit de cache draait.
     239      body_html: renderNoteBody(p, L),
    211240    }));
    212241  res.json({ items, following: wardUris.size });
     
    319348  if (!r) return res.status(502).json({ error: 'delivery' });
    320349  res.json({ ok: true, delivered: r.delivered });
     350});
     351
     352// ── Een hulpvraag oppikken of afsluiten (shaer-lgo) ───────────────
     353// Gaat naar de WARD en naar de MEDE-GUARDIANS. De ward hoort te weten dat er
     354// iemand komt -- dat is de helft van de gerustheid -- en de anderen dat het
     355// loopt, zodat niemand denkt dat de ander het al doet.
     356//
     357// OPPIKKEN mag stapelen: twee mensen die tegelijk reageren is geen probleem.
     358// AFSLUITEN kent geen terugdraai; leeft de vraag nog, dan wordt hij opnieuw
     359// gesteld. De stevige bevestiging zit in de client, net als bij het loslaten van
     360// een ward: nooit een window.confirm.
     361router.post('/api/help/:kind', requireAuth, express.json({ limit: '2kb' }), async (req, res) => {
     362  const site = siteForUser(req);
     363  if (!site) return res.status(404).json({ error: 'no_site' });
     364  const kind = req.params.kind === 'handled' ? 'handled' : 'pickup';
     365  const noteUri = String(req.body?.note || '').trim();
     366  const wardUri = String(req.body?.ward || '').trim();
     367  if (!noteUri || !/^https?:\/\//i.test(noteUri)) return res.status(400).json({ error: 'no_note' });
     368  // Alleen over een hulpvraag van een kind dat je echt bewaakt.
     369  const isWard = Guardianship.listWards(site.slug).some((w) => w.other_uri === wardUri);
     370  if (!isWard) return res.status(403).json({ error: 'not_your_ward' });
     371
     372  const me = AP.actorId((process.env.PUBLIC_BASE_URL || '').replace(/\/+$/, ''), site.slug);
     373  // Onze eigen kopie meteen, zonder op bezorging te wachten: het scherm van
     374  // degene die klikt hoort niet te liegen omdat een andere server traag is.
     375  Guardianship.help.record(noteUri, me, kind, null);
     376
     377  const anderen = Guardianship.listGuardians(wardUri.replace(/.*\/ap\/users\//, '')) || [];
     378  const ontvangers = [wardUri, ...anderen.map((g) => g.other_uri)].filter((u) => u && u !== me);
     379  const r = await AP.deliverDirectNote(site, {
     380    recipients: ontvangers,
     381    text: kind === 'handled' ? 'Deze hulpvraag is afgehandeld.' : 'Ik kijk hiernaar.',
     382    helpMark: { kind, noteUri },
     383  }).catch(() => null);
     384  // Bezorging kan mislukken; de eigen staat staat er dan toch. Dat melden we,
     385  // want "verstuurd" zeggen terwijl het niet aankwam is hier het ergste soort
     386  // stilte.
     387  res.json({ ok: true, delivered: r ? r.delivered : 0, recipients: ontvangers.length });
    321388});
    322389
     
    531598/** The playback gate of a ward we host (5.6): the heavier sibling. */
    532599function wardPlaybackSetting(uri) { return wardGateSetting(uri, 'external_playback'); }
     600/**
     601 * De gate-rijen van een ward voor het paneel.
     602 *
     603 * De standen komen uit onze eigen kolommen als we het kind hosten; bij een ward
     604 * elders weten we ze niet en blijft het NULL -- onbekend, niet uit. Het aantal
     605 * guardians idem: dat wordt op de server van die ward bijgehouden, en zonder dat
     606 * getal wordt er geen drempel verzonnen.
     607 */
     608function wardGates(mySlug, wardUri) {
     609  const statuses = wardGuardianStatuses(wardUri);
     610  const wachtend = Guardianship.follows.listReviewsByDirection(mySlug, 'incoming')
     611    .filter((r) => r.ward_uri === wardUri).length;
     612  return Guardianship.gated.gateRows({
     613    settings: {
     614      'shaer:externalEmbeds': wardEmbedSetting(wardUri),
     615      'shaer:externalPlayback': wardPlaybackSetting(wardUri),
     616    },
     617    guardianCount: statuses ? statuses.length : null,
     618    proposals: Guardianship.gated.listSent(mySlug, wardUri).map((p) => ({
     619      feature: p.feature, value: !!p.value, status: Guardianship.gated.sentStatus(p, Date.now()),
     620    })),
     621    waiting: { 'shaer:follows': wachtend || undefined },
     622  });
     623}
     624
    533625function wardGateSetting(uri, column) {
    534626  const base = (process.env.PUBLIC_BASE_URL || '').replace(/\/+$/, '');
Note: See TracChangeset for help on using the changeset viewer.