source: Klonkt/src/services/PostAccessService.js@ b1b7652

main
Last change on this file since b1b7652 was b1b7652, checked in by Bart <bart@…>, 3 weeks ago

Wie wat van een bericht mag zien, staat nu op één plek

Het besluit zat alleen in de postroute, en werd daar beantwoord door een ANDERE
PAGINA te renderen: paid-gate of fan-gate in plaats van het bericht. Dat werkt
zolang een bericht een pagina is. De tijdlijn gaat hele berichten tonen en heeft
hetzelfde besluit nodig als een stuk tussen de andere berichten.

Twee plekken die allebei zelf bepalen wie een betaalde tekst mag lezen, is een
lek dat op een gelegenheid wacht. Dus PostAccessService: één functie die 'full',
'paid', 'fan' of 'forbidden' zegt, en verder niets over vormgeving. paidTeaser()
verhuist mee, want een tweede kopie daarvan geeft vroeg of laat meer prijs dan
deze.

Gedrag ONGEWIJZIGD overgenomen, inclusief de volgorde: betaald wordt vóór
fan-only getoetst omdat een bericht dat allebei is opengaat met een passkey en
niet met een login. Elf toetsen leggen dat vast, waaronder dat een bewijs voor
een ander bericht dit bericht niet opent.

Nog niet aangesloten: de route blijft voorlopig zijn eigen ifs gebruiken. Eerst
het besluit vastleggen en toetsen, dan pas de aanroepers verplaatsen.

Co-Authored-By: Claude Opus 5 <claude@…>

  • Property mode set to 100644
File size: 3.3 KB
Line 
1/**
2 * Wat mag DEZE bezoeker van DIT bericht zien?
3 *
4 * De vraag stond tot nu toe alleen in de postroute, en daar werd hij beantwoord
5 * door een ANDERE PAGINA te renderen: paid-gate of fan-gate in plaats van het
6 * bericht. Dat werkt zolang een bericht een pagina is. Zodra de tijdlijn hele
7 * berichten toont, moet hetzelfde besluit een STUK opleveren dat tussen de
8 * andere berichten past -- en twee plekken die allebei zelf beslissen wie wat
9 * mag zien, zijn een lek dat wacht op een gelegenheid.
10 *
11 * Dus één functie, en de route en de tijdlijn lezen er allebei uit. Deze module
12 * bepaalt alleen WAT er mag; hoe het eruitziet is aan de sjablonen.
13 */
14import PermissionsService from './PermissionsService.js';
15
16/**
17 * De teaser van een betaald bericht: nooit meer dan de eerste alinea.
18 *
19 * Stond als paidTeaser() in routes/posts.js. Hier neergezet omdat de tijdlijn
20 * hem ook nodig heeft, en een tweede kopie vroeg of laat meer prijsgeeft dan
21 * deze -- precies de fout die je bij een betaalmuur niet wilt maken.
22 */
23export function paidTeaser(post, max = 280) {
24 if (post && post.excerpt && String(post.excerpt).trim()) return String(post.excerpt).trim();
25 const html = String((post && post.content) || '');
26 const firstP = (html.match(/<p[^>]*>([\s\S]*?)<\/p>/i) || [null, html])[1] || '';
27 const text = firstP.replace(/<[^>]+>/g, ' ').replace(/&[a-z#0-9]+;/gi, ' ').replace(/\s+/g, ' ').trim();
28 return text.length > max ? text.slice(0, max).replace(/\s+\S*$/, '') + '…' : text;
29}
30
31/**
32 * Het besluit, als één waarde.
33 *
34 * 'full' → de hele inhoud
35 * 'paid' → betaalmuur: teaser + ontgrendelknop
36 * 'fan' → alleen voor fans: teaser + inloggen
37 * 'forbidden' → niet gepubliceerd en jij mag het niet bewerken
38 *
39 * De VOLGORDE is niet vrijblijvend. Betaald wordt vóór fan-only getoetst, want
40 * een bericht dat allebei is gaat open met een passkey en niet met een
41 * Klonkt-login; andersom belandt een anonieme supporter op het inlogscherm en
42 * ziet hij de ontgrendelknop nooit. Die regel stond als commentaar in de route
43 * en verhuist hier mee, want hij hoort bij het besluit en niet bij de pagina.
44 *
45 * @param post de rij uit `posts`
46 * @param viewer { user, site, unlockedSlug } -- unlockedSlug is de slug uit
47 * een vers ?u=-bewijs van /paid/unlock, al geverifieerd door de
48 * aanroeper. Deze module doet geen crypto.
49 */
50export function postAccess(post, { user = null, site = null, unlockedSlug = null } = {}) {
51 if (!post) return 'forbidden';
52
53 const canEdit = !!(user && PermissionsService.canEditPost(user, post, site));
54
55 // Een concept is van de maker. Dit stond vóór beide poorten in de route en
56 // hoort dat te blijven: over een ongepubliceerd bericht valt niets te kopen
57 // en niets te ontgrendelen.
58 if (post.status !== 'published' && !canEdit) return 'forbidden';
59
60 // De eigenaar/redacteur ziet altijd zijn eigen werk, betaald of niet.
61 if (canEdit) return 'full';
62
63 if (post.paid && String(unlockedSlug || '') !== String(post.slug)) return 'paid';
64 if (post.fan_only && !user) return 'fan';
65 return 'full';
66}
67
68/** Handig voor sjablonen: mag de bezoeker de echte inhoud zien? */
69export function canReadBody(post, viewer) { return postAccess(post, viewer) === 'full'; }
70
71export default { postAccess, canReadBody, paidTeaser };
Note: See TracBrowser for help on using the repository browser.