Changeset 952baf3 in Klonkt for src/routes/guardian.js


Ignore:
Timestamp:
08/07/2026 05:15:52 PM (5 weeks ago)
Author:
Robin <roboburr@…>
Branches:
main
Children:
ba76bf5
Parents:
f85b2c3 (diff), 0d5bd2c (diff)
Note: this is a merge changeset, the changes displayed below correspond to the merge itself.
Use the (diff) links above to see all the changes relative to each parent.
Message:

Merge GitHub-main (1.7.0) met de VPS-lijn

De twee mains waren een dag gedivergeerd en bevatten elk echt werk. GitHub had 66
commits die nooit langs prutfolio.git zijn gekomen, omdat een parallelle sessie
rechtstreeks naar GitHub pushte vanaf een kloon in /tmp op de VPS. De VPS had twee
commits die GitHub niet had. Geen van beide bevatte de ander, en stable had geen van
de twee.

Bewust een merge en geen rebase: dan blijft beide historie intact en wordt er niets
herschreven waar iemand anders al op voortbouwt.

Drie bestanden raakten beide kanten. Alle drie zijn nagekeken, want dat een merge
automatisch slaagt zegt niets over of hij inhoudelijk klopt:

src/services/ActivityPubService.js

  • de sleutelbinding staat nu boven de nieuwe asSlug-aanroep van 1.7.0, dus de controle komt nog steeds voor de handtekeningcontrole

scripts/klonkt-refresh-updater.sh

  • alleen de opzij-aanpak overleefde; systemctl mask staat nergens meer als code

deploy/MULTI-INSTANCE.md

  • spreekt zichzelf niet tegen: beschrijft opzij zetten, met de reden waarom mask weigert

remarks: het gat dat in de review naar boven kwam staat hiermee ook op de 1.7.0-lijn.
De andere bevindingen uit die review staan nog open en zijn niet in deze merge
opgelost; die horen als beads. Ook nog te doen: dezelfde sleutelbinding op stable
als 1.6.1, want daar is het gat nog open bij self-hosters.

-robo
Co-Authored-By: Claude Opus 4.8 <noreply@…>

File:
1 edited

Legend:

Unmodified
Added
Removed
  • src/routes/guardian.js

    rf85b2c3 r952baf3  
    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.