Changeset 2a76184 in Klonkt for src/routes


Ignore:
Timestamp:
07/28/2026 09:11:34 AM (6 weeks ago)
Author:
Robin Genis <roboburr@…>
Branches:
main
Children:
65abc85
Parents:
bdb78e4
Message:

Guardians kunnen linkvoorbeelden aan- en uitzetten per ward

De eerste echte gated feature met een knop erbij. De gate bestond al server-side
(sites.external_embeds, uit voor een ward), maar er was geen enkele manier om
hem te bewegen: een ward zag dus nooit een linkvoorbeeld, en daar was niets aan
te doen behalve in de database duiken. Dat is precies de verkeerde plek voor een
beslissing die volgens het ontwerp bij de guardians hoort.

In de Guardian-PWA staat nu per ward een knop die de huidige stand toont en
omzet. Het endpoint accepteert het alleen van een COMMITTED guardian van die
ward, en alleen voor een ward die wij hosten: de instelling van een ward
elders hoort op de server van die ward thuis, dus die tonen we als
niet-instelbaar in plaats van te doen alsof.

Changed files:
src/routes/guardian.js

  • POST /guardian/wards/embeds, met controle op guardian-van-deze-ward en lokale ward
  • wardEmbedSetting(): huidige stand mee in de wards-lijst (null = niet van ons)

src/assets/js/guardian.js

  • knop per ward, toont de stand en zet hem om

src/services/i18n.js

  • guardian.embeds_on / embeds_off in nl, en, de

remarks: 219 tests groen. Federeren van deze instelling naar een remote ward is
een volgende stap (hoort bij shaer-3kp).

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

File:
1 edited

Legend:

Unmodified
Added
Removed
  • src/routes/guardian.js

    rbdb78e4 r2a76184  
    3939  const keys = ['sent', 'sent_retry', 'sending', 'not_found', 'failed', 'network',
    4040    'pending', 'active', 'retract', 'release', 'release_confirm', 'open', 'push_unavailable',
     41    'embeds_on', 'embeds_off',
    4142    'accept', 'reject', 'complete', 'awaiting_others', 'coguard'];
    4243  const s = Object.fromEntries(keys.map((k) => [k, i18nT(L, `guardian.${k}`)]));
     
    5657    site: site.slug,
    5758    me,
    58     wards: Guardianship.listWards(site.slug),               // committed wards
     59    // Committed wards, each carrying the gated settings a guardian may change.
     60    // `embeds` is null for a ward we do not host: that setting lives on the
     61    // ward's own server, so we show it as not-adjustable rather than lying.
     62    wards: Guardianship.listWards(site.slug).map((w) => ({ ...w, embeds: wardEmbedSetting(w.other_uri) })),
    5963    offers: Guardianship.offersCollection(`${me}/queues/offers`, site.slug, me).orderedItems,
    6064    help,
     
    266270  Guardianship.removeRelation(site.slug, 'guardian', uri);
    267271  res.json({ ok: true });
     272});
     273
     274/**
     275 * The external-embeds setting of a ward we host: true/false when a guardian has
     276 * decided, null when it is still on auto (which means off for a ward) or when
     277 * the ward lives elsewhere and the setting is not ours to show.
     278 */
     279function wardEmbedSetting(uri) {
     280  const base = (process.env.PUBLIC_BASE_URL || '').replace(/\/+$/, '');
     281  if (!base || !String(uri || '').startsWith(`${base}/`)) return null;
     282  const slug = String(uri).trim().replace(/\/+$/, '').split('/').pop();
     283  const row = slug ? db.prepare('SELECT external_embeds FROM sites WHERE slug = ?').get(slug) : null;
     284  if (!row) return null;
     285  return row.external_embeds === null || row.external_embeds === undefined ? false : row.external_embeds === 1;
     286}
     287
     288// ── Gated feature: may this ward see external (non-fediverse) embeds? ──
     289// The first real gated setting (FEP-633c §5-style). The gate itself is applied
     290// server-side when the feed is serialised, so this endpoint is the only way it
     291// can move, and only a committed guardian of THAT ward may move it.
     292router.post('/wards/embeds', requireAuth, express.json({ limit: '4kb' }), (req, res) => {
     293  const site = siteForUser(req);
     294  if (!site) return res.status(404).json({ error: 'no_site' });
     295  const uri = String(req.body?.uri || '').trim();
     296  const allow = req.body?.allow === true;
     297  if (!uri) return res.status(400).json({ error: 'empty_uri' });
     298  // Only a guardian of this ward, and only for a ward we host: a setting on a
     299  // remote ward belongs to that ward's own server (federating it is Fase 4).
     300  const isMyWard = Guardianship.listWards(site.slug).some((w) => w.other_uri === uri);
     301  if (!isMyWard) return res.status(403).json({ error: 'not_your_ward' });
     302  const base = (process.env.PUBLIC_BASE_URL || '').replace(/\/+$/, '');
     303  const slug = (base && uri.startsWith(`${base}/`)) ? uri.trim().replace(/\/+$/, '').split('/').pop() : null;
     304  const ward = slug ? db.prepare('SELECT id, slug FROM sites WHERE slug = ?').get(slug) : null;
     305  if (!ward) return res.status(400).json({ error: 'remote_ward_not_supported' });
     306  db.prepare('UPDATE sites SET external_embeds = ? WHERE id = ?').run(allow ? 1 : 0, ward.id);
     307  res.json({ ok: true, allow });
    268308});
    269309
Note: See TracChangeset for help on using the changeset viewer.