source: Klonkt/src/middleware/ward-profile.js@ 746d98a

main
Last change on this file since 746d98a was 746d98a, checked in by Robin <roboburr@…>, 6 hours ago

Wardmodus: een besloten Klonkt met alleen het profiel openbaar

Robin, 30-9: "Ward Profile mode gaat actief wanneer ward gekoppeld is, dus
wanneer de persoon guardian(s) heeft. Een besloten Klonkt met enkel het profiel
van de ward publiek. Wij denken dat dit het beste valt bij emancipation om de
functie weer naar normaal Klonkt te brengen."

AFGELEID, NIET OPGESLAGEN. Heeft de site geaccepteerde guardians, dan staat de
modus aan; is de set leeg, dan uit. listGuardians telt alleen geaccepteerde
guardianships, dus een voorstel zet niets dicht, en de set leegmaken is
emancipatie (FEP-633c 3.4) -- precies waar het ontwerp de weg terug legt. Een
schakelaar zou een tweede waarheid naast de guardianship zijn.

STANDAARD DICHT (middleware/ward-profile.js, in server.js direct na de
siteresolver en de schrijfpoort). Een bezoeker ziet op / het profiel en verder
niets: een pagina gaat terug naar het profiel, al het andere is 404, een POST
bereikt zijn handler niet, en een htmx-navigatie krijgt HX-Redirect in plaats
van een brokstuk. Wat niet op de lijst staat komt er niet langs, zodat een
nieuwe route niet vanzelf een kind openzet.

Open blijft: inloggen, account, beheer, het guardianpaneel, /lang, de bestanden
van de pagina zelf, en /audio/stream -- die bewaakt elk bestand zelf, en een
track die de ward op de fediverse opende moet daar speelbaar blijven. De
federatie (ActivityPub, WebFinger, OAuth voor Shaer, /media, /assets, /og) hangt
al VOOR de poort; de paden staan toch op de lijst, voor als de volgorde ooit
omgaat, want dan zou een ward stil van de fediverse verdwijnen.

Wie alles ziet: de ward zelf, en god, kijker en sitebeheer. Een andere
ingelogde gebruiker is gewoon een bezoeker.

DE PROFIELPAGINA (pages/ward-profile.ejs) mag verzorgd, want dit is alles wat
een bezoeker ziet: foto met een ring in de accentkleur, de naam groot, de
handle op een pil, de bio, de links als brede knoppen in hun merkkleur, een
volgknop die de bestaande fediverse-dialoog opent (een volgverzoek aan een ward
gaat eerst langs de guardians), en een neutrale regel dat berichten alleen
voor volgers zijn. Nergens "kind" of "ward": dat de account guardians heeft
staat al openbaar op de fediverse. Dezelfde bronnen als de profielkop, zodat
het profiel op geen twee plekken iets anders kan zeggen. De chrome biedt een
bezoeker niets aan wat dicht is: geen zoekknop, geen archief, geen
Lezen/Grid-schakelaar.

DE PROFIELLINKS verhuizen van Appearance naar het account (/account#links),
bij naam, bio en foto. Twee vallen onderweg, allebei getoetst:

  • Appearance schreef profile_links altijd, uit de formuliervelden. Met de velden weg zou elke keer opslaan daar de links wissen. Die pagina schrijft ze niet meer.
  • ownedSite haalde profile_links niet op, dus de accountpagina toonde nul links en opslaan wiste ze. Gevonden bij het bekijken, niet door een toets; nu wel.

buildProfileLinks staat in PlatformIcons.js, en de rijen-knoppen in een eigen
module (mod/profile-links.js).

Getoetst: gewone Klonkt zonder guardians, een voorstel zet niets dicht,
gekoppeld alleen het profiel, pagina's terug, 404 voor wat geen pagina is, geen
handler voor een POST, HX-Redirect, wat open moet blijft open, ward en beheer
zien alles, een andere ingelogde is een bezoeker, emancipatie maakt het weer
gewoon, de links bij het account en Appearance wist ze niet. Tegenproef per
zaak. Volledige suite 1325 groen.

Nagekeken in de browser op 375 en 1280 pixels: het profiel past op een
telefoonscherm, knoppen van 55 pixels, geen overloop, de volgknop opent de
dialoog; de accountpagina toont de bestaande links en voegt er een toe.

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

  • Property mode set to 100644
File size: 4.8 KB
Line 
1/**
2 * De wardmodus: een besloten Klonkt waarvan alleen het profiel openbaar is
3 * (Robins ontwerp, 30-9).
4 *
5 * AAN ZOLANG DE SITE GUARDIANS HEEFT, en dat wordt niet opgeslagen maar
6 * afgeleid. listGuardians telt alleen GEACCEPTEERDE guardianships: de modus gaat
7 * aan zodra een koppeling rond is (niet bij een voorstel), en uit zodra de set
8 * leeg is -- en de set leegmaken is emancipatie (FEP-633c 3.4). Dat is precies
9 * waar het ontwerp de terugweg naar een gewone Klonkt legt. Een schakelaar zou
10 * een tweede waarheid naast de guardianship zijn, die kan vergeten worden of
11 * uit de pas kan lopen.
12 *
13 * WAT EEN BEZOEKER ZIET: de voorpagina wordt het profiel (naam, foto, bio,
14 * links), en verder niets. Elke andere pagina stuurt terug naar dat profiel;
15 * feeds, zoeken en alles wat geen pagina is geeft 404. STANDAARD DICHT: wat hier
16 * niet op de lijst staat komt er niet langs, zodat een nieuwe route niet vanzelf
17 * een kind openzet.
18 *
19 * WAT GEWOON DOORGAAT, en waarom:
20 * - de federatie (ActivityPub, WebFinger, OAuth voor de Shaer-app) en /media,
21 * /assets, /og: die hangen in server.js VOOR deze poort. Het kind post en
22 * volgt via Shaer, en de volgpoort van de guardians regelt wie er meeleest;
23 * - inloggen, het account, beheer en het guardianpaneel: die hebben hun eigen
24 * bewaking, en de ward moet erbij kunnen;
25 * - /audio/stream/: bewaakt elk bestand zelf, en een track die de ward op de
26 * fediverse opende moet daar speelbaar blijven.
27 *
28 * WIE ALLES ZIET: de ward zelf (eigenaar), en wie de site mag beheren of
29 * bekijken (god, kijker, sitebeheer) -- dezelfde grens als elders.
30 */
31
32import * as Guardianship from '../services/guardianship/index.js';
33import PermissionsService from '../services/PermissionsService.js';
34import { parseProfileLinks } from '../services/PlatformIcons.js';
35import { renderPage } from './render.js';
36
37// Wat een bezoeker in de wardmodus WEL bereikt. Een map telt met alles eronder.
38const OPEN_DIRS = ['/auth', '/account', '/admin', '/guardian', '/audio/stream', '/lang',
39 // De federatie hangt in server.js al VOOR deze poort en komt hier dus nooit
40 // langs. Ze staan er toch, voor als iemand de volgorde ooit omgooit: dan zou
41 // een ward stil van de fediverse verdwijnen, en precies dat mag een
42 // opruimactie niet kunnen veroorzaken.
43 '/ap', '/.well-known', '/nodeinfo', '/oauth'];
44const OPEN_FILES = new Set(['/manifest.webmanifest', '/favicon.svg', '/favicon.ico', '/sw.js', '/robots.txt']);
45
46function isOpenPath(p) {
47 if (OPEN_FILES.has(p)) return true;
48 return OPEN_DIRS.some((d) => p === d || p.startsWith(d + '/'));
49}
50
51/** Heeft deze site guardians? Dan is het een ward, en staat de modus aan. */
52export function isWardSite(site) {
53 if (!site || !site.slug) return false;
54 try { return Guardianship.listGuardians(site.slug).length > 0; } catch { return false; }
55}
56
57/** Ziet deze gebruiker de hele site, ook in de wardmodus? */
58export function seesWholeSite(user, site) {
59 if (!user) return false;
60 if (user.role === 'god' || user.role === 'kijker') return true;
61 try { return !!PermissionsService.canAdminSite(user, site); } catch { return false; }
62}
63
64/** De fediverse-handle van de site: @slug@host. */
65function handleOf(req, site) {
66 let host = '';
67 try { host = new URL(process.env.PUBLIC_BASE_URL || '').host; } catch { /* terugval hieronder */ }
68 if (!host) host = req.get('host') || '';
69 return host ? `@${site.slug}@${host}` : `@${site.slug}`;
70}
71
72function renderWardProfile(req, res) {
73 const site = res.locals.site;
74 return renderPage(req, res, 'pages/ward-profile', {
75 pageTitle: site.profile_name || site.title || site.slug,
76 bodyClass: 'on-ward-profile',
77 wardHandle: handleOf(req, site),
78 wardLinks: parseProfileLinks(site.profile_links),
79 });
80}
81
82export function wardProfileGate(req, res, next) {
83 const site = res.locals.site;
84 res.locals.wardProfile = false;
85 if (!isWardSite(site)) return next();
86 if (seesWholeSite(req.session && req.session.user, site)) return next();
87
88 // Vanaf hier: een bezoeker van een ward-site. De views lezen deze vlag om
89 // niets aan te bieden wat toch dicht is (de zoekknop bijvoorbeeld).
90 res.locals.wardProfile = true;
91 const p = req.path;
92 if (isOpenPath(p)) return next();
93
94 const isRead = req.method === 'GET' || req.method === 'HEAD';
95 if (isRead && p === '/') return renderWardProfile(req, res);
96
97 const base = res.locals.siteUrlBase || '';
98 if (isRead && req.get('hx-request') === 'true') {
99 // Een htmx-navigatie zou het profiel als brokstuk in de pagina zetten.
100 // HX-Redirect laat de browser echt naar het profiel gaan.
101 res.set('HX-Redirect', base + '/');
102 return res.status(204).end();
103 }
104 if (isRead && req.accepts(['html', 'json']) === 'html') return res.redirect(302, base + '/');
105 return res.status(404).end();
106}
107
108export default { wardProfileGate, isWardSite, seesWholeSite };
Note: See TracBrowser for help on using the repository browser.