source: Klonkt/src/services/BlocklistService.js@ 6b5d7da

main
Last change on this file since 6b5d7da was 6b5d7da, checked in by Robin Genis <roboburr@…>, 7 weeks ago

Guardianship als eigen module + gedeelde BlocklistService

FEP-633c (Guardians) lag verspreid door ActivityPubService; nu is het één
cohesief onderdeel in src/services/guardianship/ met submodules. De
blocklist staat er bewust NAAST (BlocklistService): die wordt gedeeld met
Klonkt zelf (Block-tab) en is niet guardianship-specifiek.

De module importeert ActivityPubService nooit terug: de AP-helpers gaan er
één keer in via wireDelivery/wireHandshake, en de service delegeert met
dunne wrappers zodat elke bestaande aanroep blijft werken.

Naast de verhuizing ook de serverkant die nog miste (shaer-bh1): de
ap_guardianships-relaties, shaer:guardians/isGuardian/queues op het
actor-doc, en de adoptie-handshake Offer/Accept/Reject over C2S en S2S,
met het contract van de Shaer test-daemon zodat de iOS/Android-clients het
ongewijzigd spreken. Een inkomend hulpverzoek (shaer:helpRequest op een
directe mention) krijgt een eigen vlag in ap_mentions en pusht als
'help'-type richting de Guardian-PWA (volgende commit).

Changed files:
src/services/ActivityPubService.js

  • shaer-context, actor-props en helpRequest uit de module gespread
  • blocklist-functies zijn delegaties naar BlocklistService
  • c2sVisibility/deliverDirectNote re-export uit guardianship/delivery
  • C2S: Offer/Accept/Reject eerst langs de handshake-module
  • S2S: Offer aan GATED (signature-eis) + handshake-routering
  • inbound mention: help_request-vlag + 'help'/'guardian'-push-events

src/config/database.js

  • tabel ap_guardianships (slug, role, other_uri, status, offer_id)
  • kolom ap_mentions.help_request

test/activitypub-as2.test.js

  • shaer:queues/offers/follows/wards in de AS2-allowlist

New file:
src/services/BlocklistService.js

  • ap_blocks-opslag, blockTarget/unblock/listBlocks/isBlockedAny, purge; handle-resolver via injectie (geen circulaire import)

src/services/guardianship/index.js

  • de publieke API van het onderdeel

src/services/guardianship/context.js

  • shaer-namespace + Relationship-vocabulaire

src/services/guardianship/relations.js

  • ap_guardianships-API + actor-props (FEP-633c paragraaf 2)

src/services/guardianship/handshake.js

src/services/guardianship/queues.js

src/services/guardianship/notes.js

  • shaer:helpRequest lezen/schrijven

src/services/guardianship/delivery.js

  • de directe-note-route (call-for-help), gedrag ongewijzigd

remarks: alle 158 tests groen. push-teksten (push.n_help_*, push.n_guard_*)
en de queue-routes + Guardian-PWA volgen in de volgende commits.

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

  • Property mode set to 100644
File size: 3.7 KB
Line 
1/**
2 * The instance blocklist (ap_blocks): actors and whole domains a site has
3 * blocked. Lives NEXT TO the guardianship module, not inside it, because it
4 * is shared: Klonkt's own Block tab uses it, and Shaer's "in Orbit" reads it
5 * as the source of truth (AP §5.6 blocked collection, owner-only).
6 *
7 * Extracted from ActivityPubService (guardianship refactor); behavior is
8 * unchanged. ActivityPubService re-exports these under the old names so
9 * existing callers keep working.
10 */
11import db from '../config/database.js';
12
13let _insBl, _delBl, _listBl;
14function blStmts() {
15 if (!_insBl) {
16 _insBl = db.prepare('INSERT OR IGNORE INTO ap_blocks (slug, target, kind, label, created_at) VALUES (?,?,?,?,CURRENT_TIMESTAMP)');
17 _delBl = db.prepare('DELETE FROM ap_blocks WHERE slug = ? AND target = ?');
18 _listBl = db.prepare('SELECT * FROM ap_blocks WHERE slug = ? ORDER BY created_at DESC');
19 }
20 return { ins: _insBl, del: _delBl, list: _listBl };
21}
22
23export function listBlocks(slug) { return blStmts().list.all(slug); }
24
25// True if an actor (or its whole domain) is blocked anywhere on this instance.
26export function isBlockedAny(actorUri) {
27 if (!actorUri) return false;
28 let domain = ''; try { domain = new URL(actorUri).host; } catch { /* ignore */ }
29 try { return !!db.prepare("SELECT 1 FROM ap_blocks WHERE (kind='actor' AND target=?) OR (kind='domain' AND target=?) LIMIT 1").get(actorUri, domain); }
30 catch { return false; }
31}
32
33function purgeBlocked(kind, target) {
34 try {
35 if (kind === 'domain') {
36 // Exact host match (a URL LIKE over-/under-matches: it misses bare-domain or :port
37 // actor URIs and can catch look-alikes). Filter by parsed host, same as isBlockedAny.
38 const purge = (table, col) => {
39 let rows = [];
40 try { rows = db.prepare(`SELECT DISTINCT ${col} AS u FROM ${table} WHERE ${col} IS NOT NULL AND ${col} != ''`).all(); } catch { return; }
41 const del = db.prepare(`DELETE FROM ${table} WHERE ${col} = ?`);
42 for (const r of rows) { let h = ''; try { h = new URL(r.u).host; } catch { /* skip */ } if (h === target) { try { del.run(r.u); } catch { /* ignore */ } } }
43 };
44 purge('ap_interactions', 'actor_uri');
45 purge('ap_timeline', 'author_uri');
46 purge('ap_followers', 'actor_uri');
47 } else {
48 db.prepare('DELETE FROM ap_interactions WHERE actor_uri = ?').run(target);
49 db.prepare('DELETE FROM ap_timeline WHERE author_uri = ?').run(target);
50 db.prepare('DELETE FROM ap_followers WHERE actor_uri = ?').run(target);
51 }
52 } catch { /* best-effort */ }
53}
54
55// Block an actor (@handle or actor URL) or a whole domain; purges their content.
56// `resolveHandle` (async handle → actor URL) is injected by the caller so this
57// service needs nothing from ActivityPubService (no circular import).
58export async function blockTarget(site, input, resolveHandle) {
59 const raw = String(input || '').trim();
60 if (!site || !site.slug || !raw) return { error: 'empty' };
61 let kind, target, label;
62 if (/^https?:\/\//i.test(raw)) { kind = 'actor'; target = raw; label = raw; }
63 else if (raw.includes('@')) {
64 const actorUrl = resolveHandle ? await resolveHandle(raw) : null;
65 if (!actorUrl) return { error: 'not_found' };
66 kind = 'actor'; target = actorUrl; label = raw.startsWith('@') ? raw : ('@' + raw);
67 } else { kind = 'domain'; target = raw.toLowerCase(); label = raw.toLowerCase(); }
68 blStmts().ins.run(site.slug, target, kind, label);
69 purgeBlocked(kind, target);
70 console.log('[AP] block', site.slug, kind, target);
71 return { ok: true, label };
72}
73
74export function unblock(site, target) { blStmts().del.run(site.slug, target); return { ok: true }; }
75
76export default { listBlocks, isBlockedAny, blockTarget, unblock };
Note: See TracBrowser for help on using the repository browser.