Changeset f2796d3 in Klonkt for src/routes


Ignore:
Timestamp:
06/29/2026 10:19:54 PM (2 months ago)
Author:
roboburr <roboburr@…>
Branches:
main
Children:
3f9f71d
Parents:
fb6819d
Message:

fix(federation): AP conformance round — actor enrichment, following collection, webfinger, unfollow fix

Audited the AP surface against the W3C spec + fediverse pioneers (Mastodon/Hubzilla/
Friendica/WordPress-ActivityPub/PeerTube). Additive enrichment toward non-Mastodon
receivers + one real correctness fix; the proven Mastodon happy-path (actor core, Note,
Create, Delete, Update) is left untouched.

  • src/services/ActivityPubService.js — buildActor now emits following, published (site created date) and attachment PropertyValue rows from profile_links (rel=me, HTML-escaped) so profile metadata federates; new buildFollowing (count-only collection); unfollowActor now sends Undo(Follow) with the stored real follow id via the retry queue (the old ${me}#follow fallback never matched, so unfollow silently failed on the remote), and skips the network Undo for legacy rows with no stored follow id
  • src/routes/activitypub.js — WebFinger adds aliases + a profile-page link; new GET /ap/users/:slug/following (count only); NodeInfo users.total now counts public sites (AP actors) instead of the users/account-rows table

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

File:
1 edited

Legend:

Unmodified
Added
Removed
  • src/routes/activitypub.js

    rfb6819d rf2796d3  
    4646  res.type('application/jrd+json; charset=utf-8');
    4747  res.set('Cache-Control', 'public, max-age=300');
     48  const actorUri = AP.actorId(baseUrl(req), site.slug);
     49  const profileUrl = baseUrl(req) + (site.slug === primarySlug() ? '/' : `/user/${encodeURIComponent(site.slug)}`);
    4850  res.send(JSON.stringify({
    4951    subject: `acct:${site.slug}@${hostOf(req)}`,
    50     links: [{ rel: 'self', type: 'application/activity+json', href: AP.actorId(baseUrl(req), site.slug) }],
     52    aliases: [actorUri, profileUrl],
     53    links: [
     54      { rel: 'self', type: 'application/activity+json', href: actorUri },
     55      { rel: 'http://webfinger.net/rel/profile-page', type: 'text/html', href: profileUrl },
     56    ],
    5157  }));
    5258});
     
    8389  const n = db.prepare('SELECT COUNT(*) n FROM ap_followers WHERE slug = ?').get(site.slug).n;
    8490  AP.sendAP(res, AP.buildFollowers(baseUrl(req), site, n));
     91});
     92
     93// ── Following (count only) ────────────────────────────────────────
     94router.get('/ap/users/:slug/following', (req, res) => {
     95  const site = publicSite(req.params.slug);
     96  if (!site) return res.status(404).end();
     97  let n = 0;
     98  try { n = db.prepare("SELECT COUNT(*) n FROM ap_following WHERE slug = ? AND status = 'accepted'").get(site.slug).n; } catch { /* table may not exist */ }
     99  AP.sendAP(res, AP.buildFollowing(baseUrl(req), site, n));
    85100});
    86101
     
    151166router.get('/nodeinfo/2.1', (req, res) => {
    152167  let users = 0; let posts = 0;
    153   try { users = db.prepare('SELECT COUNT(*) c FROM users').get().c; } catch { /* */ }
     168  // "users" = public AP actors (sites), not the admin/member account rows.
     169  try { users = db.prepare('SELECT COUNT(*) c FROM sites WHERE (is_public IS NULL OR is_public = 1)').get().c; } catch { /* */ }
    154170  try { posts = db.prepare("SELECT COUNT(*) c FROM posts WHERE status = 'published'").get().c; } catch { /* */ }
    155171  res.type('application/json; charset=utf-8');
Note: See TracChangeset for help on using the changeset viewer.