Changeset 67c1f24 in Klonkt for src/routes


Ignore:
Timestamp:
07/17/2026 12:38:54 AM (8 weeks ago)
Author:
Robin <roboburr@…>
Branches:
main
Children:
92a2c46
Parents:
3778ddb
git-author:
Robin <roboburr@…> (07/17/2026 12:38:34 AM)
git-committer:
Robin <roboburr@…> (07/17/2026 12:38:54 AM)
Message:

Feature: owner moderation of incoming replies (remove + tombstone + report)

The site owner could not remove an incoming reply, and thread-filling would
re-fetch a locally deleted one (crawlThread seeds its dedup set from
ap_interactions). New ap_rejected_objects table: rejectInteraction() (tenancy-
scoped to the owner's site) deletes the row and tombstones the object URI;
handleInbox and crawlThread skip tombstoned URIs, so a removed reply never
returns via re-delivery or thread-filling. A report action feeds sendReport
from the locally stored object/actor URIs, so flagging also works for private
notes that authorize_interaction cannot fetch (401/404). Thread nodes get
owner-only report + remove buttons (confirm via the global data-confirm
handler); i18n NL/EN/DE. Covered by test/reply-moderation.test.js (59 tests
green); full flow verified in a browser: buttons render for the logged-in
owner, remove deletes the reply and writes the tombstone. Beads: klonkt-demo-qul.

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

File:
1 edited

Legend:

Unmodified
Added
Removed
  • src/routes/posts.js

    r3778ddb r67c1f24  
    738738  }
    739739  res.redirect(req.get('Referer') || `${res.locals.siteUrlBase || ''}/fediverse`);
     740});
     741
     742// Moderation: remove an INCOMING reply from your thread (owner only). Tombstones the
     743// object URI so re-delivery and thread-crawling never bring it back. Works for private
     744// notes too (acts on the local copy; no remote fetch involved).
     745router.post('/interactions/:id/remove', requireSiteManager, (req, res) => {
     746  const site = res.locals.site;
     747  if (site) {
     748    const r = ActivityPubService.rejectInteraction(site, parseInt(req.params.id, 10) || 0, 'removed by site owner');
     749    if (r.error) console.warn('[AP] interaction remove failed:', r.error);
     750  }
     751  res.redirect(req.get('Referer') || `${res.locals.siteUrlBase || ''}/`);
     752});
     753
     754// Moderation: report an INCOMING reply to its home instance (owner only). Uses the
     755// locally stored object/actor URIs, so it also works for private notes that
     756// authorize_interaction cannot fetch (401/404).
     757router.post('/interactions/:id/report', requireSiteManager, async (req, res) => {
     758  const site = res.locals.site;
     759  if (site) {
     760    const tgt = ActivityPubService.interactionReportTarget(site, parseInt(req.params.id, 10) || 0);
     761    if (tgt && (tgt.objectUri || tgt.actorUri)) {
     762      try {
     763        const r = await ActivityPubService.sendReport(site, { objectUri: tgt.objectUri, actorUri: tgt.actorUri, reason: (req.body.reason || '').toString().slice(0, 500) });
     764        if (r && r.error) console.warn('[AP] interaction report failed:', r.error);
     765      } catch (e) { console.warn('[AP] interaction report failed:', e.message); }
     766    }
     767  }
     768  res.redirect(req.get('Referer') || `${res.locals.siteUrlBase || ''}/`);
    740769});
    741770
Note: See TracChangeset for help on using the changeset viewer.