Changeset bac4bf3 in Klonkt for src/routes/activitypub.js


Ignore:
Timestamp:
08/13/2026 11:55:04 AM (4 weeks ago)
Author:
Robin <roboburr@…>
Branches:
main
Children:
783b9ff
Parents:
7e9d0ea
git-author:
Robin <roboburr@…> (08/13/2026 11:54:36 AM)
git-committer:
Robin <roboburr@…> (08/13/2026 11:55:04 AM)
Message:

Paginering die echt pagineert (shaer-sk4)

Robin zag dat elke ?page= dezelfde inhoud gaf. Dat klopte, en het was geen halve
implementatie maar een omhulsel: pagedCollection veranderde alleen de VORM en
sneed nooit. first en last wezen allebei naar ?page=1, en de routes lazen
!!req.query.page -- of de parameter er STAAT, niet welke. Live gaf ?page=2 en
?page=99 dezelfde acht items, en noemden zichzelf pagina 1.

Nu: echt snijden, met next en prev, en een pagina die zijn eigen nummer
draagt. Een pagina voorbij het einde is LEEG en zegt dat ook -- hem naar de
laatste terugbuigen zou opnieuw een antwoord zijn dat over zichzelf liegt.

DE WORTEL HOUDT ZIJN ITEMS INLINE, en dat is geen slordigheid maar de reden dat
dit veilig is. Shaer leest een document en volgt next niet (KlonktClient.swift
orderedItems). Werd de wortel nu leeg, dan kreeg elke draaiende app nul items en
geen foutmelding -- dezelfde stille val waar ik op 10 augustus bij Funkwhale zelf
in trapte. Eerst de clients leren pagineren, dan pas de wortel afslanken.

WAT WEL EN NIET GEPAGINEERD IS. Volgers en following pagineren nu volledig: die
lijsten zitten al in het geheugen, dus dat kost niets extra. De outbox krijgt de
juiste VORM maar niet meer diepte: de route kapt al op twintig rijen in SQL.
Echt doorbladeren vraagt daar een LIMIT/OFFSET, en dat is lastiger dan bij
volgers omdat posts en tracks op datum door elkaar gevlochten worden en uit twee
tabellen komen -- een UNION met een offset erover, geen tweede slice. Dat staat
als naad in de code en op de bead, niet weggemoffeld.

Zeven tests, waaronder de klacht zelf: pagina 2 geeft ANDERE items dan pagina 1.

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

File:
1 edited

Legend:

Unmodified
Added
Removed
  • src/routes/activitypub.js

    r7e9d0ea rbac4bf3  
    2828
    2929const router = express.Router();
     30
     31/**
     32 * Welke pagina vraagt de lezer? (shaer-sk4)
     33 *
     34 * Hier stond `!!req.query.page` -- of de parameter er STAAT, niet welke. Daardoor
     35 * gaf ?page=2 en ?page=99 allemaal pagina 1, en noemde het antwoord zichzelf ook
     36 * nog pagina 1. Onleesbaar getal of geen parameter: dan de wortel.
     37 */
     38function paginaNr(req) {
     39  if (req.query.page === undefined) return false;
     40  const n = Math.floor(Number(req.query.page));
     41  return Number.isFinite(n) && n > 0 ? n : 1;
     42}
    3043// The whole fediverse layer can be turned off (solo "no federation" mode):
    3144// then /ap/*, WebFinger and NodeInfo are simply gone — the site is undiscoverable
     
    182195  });
    183196  if (audience === 'blocked') {
    184     return AP.sendAP(res, AP.buildOutbox(baseUrl(req), site, [], [], { page: !!req.query.page }), 'private, no-store');
     197    return AP.sendAP(res, AP.buildOutbox(baseUrl(req), site, [], [], { page: paginaNr(req) }), 'private, no-store');
    185198  }
    186199  const fanClause = audience === 'friend' ? '' : "AND (fan_only IS NULL OR fan_only = 0)";
     
    192205  // De tracks gaan mee voor iedereen die de deur door mag; de blocked-tak
    193206  // hierboven levert bewust een outbox ZONDER posts en zonder tracks.
    194   const ob = AP.buildOutbox(baseUrl(req), site, posts, AP.siteOpenTracks(site.id), { page: !!req.query.page });
     207  const ob = AP.buildOutbox(baseUrl(req), site, posts, AP.siteOpenTracks(site.id), { page: paginaNr(req) });
    195208  if (audience === 'friend') {
    196209    // The owner's app builds its feed from this leg, and every note here is
     
    986999    // Default = bare references; enrich only when the client asks (FEP-9876).
    9871000    const items = wantsEnriched(req, res) ? uris.map((u) => AP.buildActorRef(site.slug, u)) : uris;
    988     return AP.sendAP(res, AP.buildFollowers(baseUrl(req), site, items.length, items));
     1001    return AP.sendAP(res, AP.buildFollowers(baseUrl(req), site, items.length, items, { page: paginaNr(req) }));
    9891002  }
    9901003  const n = db.prepare('SELECT COUNT(*) n FROM ap_followers WHERE slug = ?').get(site.slug).n;
    991   AP.sendAP(res, AP.buildFollowers(baseUrl(req), site, n));
     1004  AP.sendAP(res, AP.buildFollowers(baseUrl(req), site, n, null, { page: paginaNr(req) }));
    9921005});
    9931006
     
    10051018      items = enrich ? uris.map((u) => AP.buildActorRef(site.slug, u)) : uris;
    10061019    } catch { /* table may not exist */ }
    1007     return AP.sendAP(res, AP.buildFollowing(baseUrl(req), site, items.length, items));
     1020    return AP.sendAP(res, AP.buildFollowing(baseUrl(req), site, items.length, items, { page: paginaNr(req) }));
    10081021  }
    10091022  let n = 0;
    10101023  try { n = db.prepare("SELECT COUNT(*) n FROM ap_following WHERE slug = ? AND status = 'accepted'").get(site.slug).n; } catch { /* table may not exist */ }
    1011   AP.sendAP(res, AP.buildFollowing(baseUrl(req), site, n));
     1024  AP.sendAP(res, AP.buildFollowing(baseUrl(req), site, n, null, { page: paginaNr(req) }));
    10121025});
    10131026
Note: See TracChangeset for help on using the changeset viewer.