Changeset 4407c67 in Klonkt for src/routes


Ignore:
Timestamp:
07/19/2026 03:53:49 PM (7 weeks ago)
Author:
Robin <roboburr@…>
Branches:
main
Children:
2d66d66
Parents:
bf72108
git-author:
Robin <roboburr@…> (07/19/2026 03:53:33 PM)
git-committer:
Robin <roboburr@…> (07/19/2026 03:53:49 PM)
Message:

Feature: C2S owner can read own followers/following (klonkt-demo-6kc)

The followers and following collections stay count-only for the public
(privacy), but a request carrying a C2S bearer scoped to that site (the account
owner) now returns the real actor URIs, so a client (Shaer) can build a friends
list. This was the one gap keeping the Shaer app's orbit empty against a real
Klonkt (it worked against the shaer-daemon, which serves the full lists).

  • buildFollowers/buildFollowing take an optional items array: when present, orderedItems carries the URIs and totalItems reflects them; otherwise count-only as before.
  • The two GET routes verify a bearer (OAuth.verifyBearer) and, when it is scoped to the requested slug, return the full list from ap_followers.actor_uri / ap_following.actor_uri (status=accepted); everyone else gets count-only. A private site's owner can read it even when it is not publicly listed.

3 new builder tests (count-only vs owner items vs empty owner list); 83 green.
Live-verified: owner bearer -> real URIs (alice/bob) in orderedItems; no bearer
-> orderedItems empty with the count intact; a token for another slug does not
unlock it.

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

File:
1 edited

Legend:

Unmodified
Added
Removed
  • src/routes/activitypub.js

    rbf72108 r4407c67  
    8484});
    8585
    86 // ── Followers (count only) ────────────────────────────────────────
     86// ── Followers (count-only public, full for the owner) ─────────────
     87// A C2S bearer scoped to this site (the account owner) gets the real actor
     88// URIs so their own client can build a friends list; everyone else gets the
     89// count only (privacy).
    8790router.get('/ap/users/:slug/followers', (req, res) => {
    88   const site = publicSite(req.params.slug);
    89   if (!site) return res.status(404).end();
     91  const auth = OAuth.verifyBearer(req.headers.authorization);
     92  const owner = auth && auth.site.slug === req.params.slug;
     93  const site = owner ? auth.site : publicSite(req.params.slug);
     94  if (!site) return res.status(404).end();
     95  if (owner) {
     96    const items = db.prepare('SELECT actor_uri FROM ap_followers WHERE slug = ? ORDER BY created_at').all(site.slug).map((r) => r.actor_uri);
     97    return AP.sendAP(res, AP.buildFollowers(baseUrl(req), site, items.length, items));
     98  }
    9099  const n = db.prepare('SELECT COUNT(*) n FROM ap_followers WHERE slug = ?').get(site.slug).n;
    91100  AP.sendAP(res, AP.buildFollowers(baseUrl(req), site, n));
    92101});
    93102
    94 // ── Following (count only) ────────────────────────────────────────
     103// ── Following (count-only public, full for the owner) ─────────────
    95104router.get('/ap/users/:slug/following', (req, res) => {
    96   const site = publicSite(req.params.slug);
    97   if (!site) return res.status(404).end();
     105  const auth = OAuth.verifyBearer(req.headers.authorization);
     106  const owner = auth && auth.site.slug === req.params.slug;
     107  const site = owner ? auth.site : publicSite(req.params.slug);
     108  if (!site) return res.status(404).end();
     109  if (owner) {
     110    let items = [];
     111    try { items = db.prepare("SELECT actor_uri FROM ap_following WHERE slug = ? AND status = 'accepted' ORDER BY created_at").all(site.slug).map((r) => r.actor_uri); } catch { /* table may not exist */ }
     112    return AP.sendAP(res, AP.buildFollowing(baseUrl(req), site, items.length, items));
     113  }
    98114  let n = 0;
    99115  try { n = db.prepare("SELECT COUNT(*) n FROM ap_following WHERE slug = ? AND status = 'accepted'").get(site.slug).n; } catch { /* table may not exist */ }
Note: See TracChangeset for help on using the changeset viewer.