Changeset 279ca0b in Klonkt
- Timestamp:
- 08/06/2026 12:42:50 PM (5 weeks ago)
- Branches:
- main
- Children:
- 32a4ffb
- Parents:
- bedd7a6
- git-author:
- Robin <roboburr@…> (08/06/2026 12:42:40 PM)
- git-committer:
- roboburr <roboburr@…> (08/06/2026 12:42:50 PM)
- Files:
-
- 1 added
- 4 edited
-
scripts/backfill-reactions.mjs (modified) (4 diffs)
-
src/routes/posts.js (modified) (1 diff)
-
src/services/ActivityPubService.js (modified) (6 diffs)
-
test/interaction-reactions.test.js (added)
-
test/reactions-characterization.test.js (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
scripts/backfill-reactions.mjs
rbedd7a6 r279ca0b 25 25 // niet-gereageerd -- en klikt iemand opnieuw, met een tweede 26 26 // Like de fediverse in als gevolg. 27 // UIT REACTIES De derde bron (shaer-ipb): ap_interactions.acted_like/_boost, 28 // wat jij deed met een reactie ONDER je eigen post. Zelfde 29 // gevolg als hierboven, alleen op een ander oppervlak. 27 30 // 28 31 // Wat het NIET kan: bij AANVULLEN de oorspronkelijke reactiedatum herstellen. … … 45 48 AND NOT EXISTS (SELECT 1 FROM ap_my_reactions r 46 49 WHERE r.site_slug = t.slug AND r.target_uri = t.id)`).get().n, 50 // Een reactie op een COMMENT hoort geen tijdlijnrij te hebben, dus die telt 51 // hier niet als wees -- anders slaat de controle hieronder alarm op precies 52 // wat stap 3 net goed heeft gezet. 47 53 wees: db.prepare(` 48 54 SELECT COUNT(*) AS n FROM ap_my_reactions r 49 55 WHERE NOT EXISTS (SELECT 1 FROM ap_timeline t 50 WHERE t.slug = r.site_slug AND t.id = r.target_uri)`).get().n, 56 WHERE t.slug = r.site_slug AND t.id = r.target_uri) 57 AND NOT EXISTS (SELECT 1 FROM ap_interactions i WHERE i.object_uri = r.target_uri)`).get().n, 58 scheefActed: db.prepare(` 59 SELECT COUNT(*) AS n FROM ap_interactions i 60 JOIN posts p ON p.id = i.post_id 61 JOIN sites s ON s.id = p.site_id 62 WHERE (i.acted_like = 1 OR i.acted_boost = 1) AND IFNULL(i.object_uri, '') <> '' 63 AND NOT EXISTS (SELECT 1 FROM ap_my_reactions r 64 WHERE r.site_slug = s.slug AND r.target_uri = i.object_uri)`).get().n, 51 65 }); 52 66 … … 60 74 } 61 75 if (dryRun) { 62 console.log(`\n--dry-run: zou ${uit.hersleuteld} rij(en) hersleutelen en ${uit.aangevuld} aanvullen. Niets geschreven.`); 76 console.log(`\n--dry-run: zou ${uit.hersleuteld} rij(en) hersleutelen, ${uit.aangevuld} aanvullen` 77 + ` en ${uit.reacties} uit reacties overnemen. Niets geschreven.`); 63 78 process.exit(0); 64 79 } 65 80 66 81 const na = meet(); 67 console.log('hersleuteld:', uit.hersleuteld, ' aangevuld:', uit.aangevuld );82 console.log('hersleuteld:', uit.hersleuteld, ' aangevuld:', uit.aangevuld, ' uit reacties:', uit.reacties); 68 83 console.log('achteraf :', JSON.stringify(na)); 69 84 … … 75 90 if (na.scheef !== 0) { 76 91 console.error(`\nFOUT: nog ${na.scheef} rij(en) met een vlag zonder tegenhanger.`); 92 process.exit(1); 93 } 94 if (na.scheefActed !== 0) { 95 console.error(`\nFOUT: nog ${na.scheefActed} reactie(s) onder je eigen posts met acted_* zonder tegenhanger.`); 77 96 process.exit(1); 78 97 } -
src/routes/posts.js
rbedd7a6 r279ca0b 1557 1557 const kind = req.body.kind === 'boost' ? 'boost' : 'like'; 1558 1558 if (parent && parent.post_id === post.id && parent.object_uri) { 1559 if (kind === 'boost') { 1560 // Toggle: boost an unboosted comment, or retract it (Undo Announce) if already boosted. 1561 const on = !parent.acted_boost; 1562 ActivityPubService.sendInteraction(site, on ? 'boost' : 'unboost', parent.object_uri, parent.actor_uri) 1563 .catch((e) => console.warn('[AP] reaction failed:', e.message)); 1564 ActivityPubService.setInteractionBoosted(parent.id, on); 1565 } else { 1566 // Toggle: like an unliked comment, or un-favourite (Undo Like) if already liked. 1567 const on = !parent.acted_like; 1568 ActivityPubService.sendInteraction(site, on ? 'like' : 'unlike', parent.object_uri, parent.actor_uri) 1569 .catch((e) => console.warn('[AP] reaction failed:', e.message)); 1570 ActivityPubService.setInteractionLiked(parent.id, on); 1571 } 1559 // Toggle: react, or retract it (Undo Announce / Undo Like) if already on. 1560 // De stand komt uit dezelfde bron als de knop die je zag; leest de toggle uit 1561 // de kolom en de knop uit de tussentabel, dan draait een divergentie de 1562 // richting om en stuur je een Undo voor iets dat nooit is verstuurd. 1563 const ik = ActivityPubService.getReaction(site.slug, parent.object_uri); 1564 const on = kind === 'boost' ? !ik.boosted : !ik.liked; 1565 ActivityPubService.sendInteraction(site, on ? kind : `un${kind}`, parent.object_uri, parent.actor_uri) 1566 .catch((e) => console.warn('[AP] reaction failed:', e.message)); 1567 // De tussentabel is de waarheid (shaer-ipb), gesleuteld op object_uri -- net 1568 // als de Like die hierboven de fediverse in gaat. acted_* blijft voorlopig 1569 // als afgeleide meelopen, hetzelfde vangnet dat ap_timeline.liked na 1570 // shaer-9e9 is: pas weghalen als deze migratie een release heeft ingelopen. 1571 ActivityPubService.setReaction(site.slug, parent.object_uri, kind, on); 1572 if (kind === 'boost') ActivityPubService.setInteractionBoosted(parent.id, on); 1573 else ActivityPubService.setInteractionLiked(parent.id, on); 1572 1574 } 1573 1575 res.redirect(`${res.locals.siteUrlBase || ''}/${post.slug}#fediverse`); -
src/services/ActivityPubService.js
rbedd7a6 r279ca0b 1186 1186 const siteIcon = (site && site.profile_photo) || null; 1187 1187 1188 // Wat JIJ met deze reacties deed komt uit de tussentabel, niet meer uit 1189 // acted_* (shaer-ipb). Eén batch-lookup, want een drukke thread zou anders een 1190 // N+1 worden. De sleutel loopt door canonicalReactionUri, precies zoals aan de 1191 // schrijfkant -- staat dezelfde note toevallig ook in je tijdlijn, dan is het 1192 // één feit en niet twee knoppen die los van elkaar aan kunnen staan. 1193 const mijnSleutel = new Map(); 1194 for (const r of rows) { 1195 if (r.kind === 'reply' && r.object_uri) mijnSleutel.set(r.object_uri, canonicalReactionUri(site && site.slug, r.object_uri)); 1196 } 1197 const mijn = getReactionsFor(site && site.slug, [...mijnSleutel.values()]); 1198 const mijnReactie = (uri) => mijn.get(mijnSleutel.get(uri)) || { liked: false, boosted: false }; 1199 1188 1200 const nodes = []; 1189 1201 for (const r of rows) { 1190 1202 if (r.kind !== 'reply') continue; 1203 const ik = mijnReactie(r.object_uri); 1191 1204 nodes.push({ 1192 1205 noteId: r.object_uri, parent: r.parent_uri || null, mine: false, id: r.id, … … 1195 1208 actor_icon: r.actor_icon, content: stripLeadingMentions(r.content), created_at: r.published || r.created_at, 1196 1209 emoji_json: r.emoji_json, actor_emoji_json: r.actor_emoji_json, // FEP-9098 (thread render) 1197 acted_boost: !!r.acted_boost, acted_like: !!r.acted_like,1210 acted_boost: ik.boosted, acted_like: ik.liked, 1198 1211 children: [], 1199 1212 }); … … 3646 3659 // stap naar nieuwe code, en een handmatig script per instance wordt vergeten -- 3647 3660 // terwijl het falen stil is (een reactie die niemand meer ziet geeft geen fout). 3648 const REACTIONS_MIGRATION_VERSION = 1; 3661 // v2 haalt de derde bron erbij: ap_interactions.acted_* (shaer-ipb). Een bump 3662 // laat alle stappen opnieuw lopen, en dat mag -- ze zijn alle drie idempotent. 3663 const REACTIONS_MIGRATION_VERSION = 2; 3649 3664 3650 3665 /** … … 3666 3681 */ 3667 3682 export function migrateReactions(opts = {}) { 3668 const uit = { hersleuteld: 0, aangevuld: 0, overgeslagen: false };3683 const uit = { hersleuteld: 0, aangevuld: 0, reacties: 0, overgeslagen: false }; 3669 3684 try { 3670 3685 if (!opts.force) { … … 3684 3699 AND NOT EXISTS (SELECT 1 FROM ap_my_reactions r 3685 3700 WHERE r.site_slug = t.slug AND r.target_uri = t.id AND r.kind = '${kind}')`; 3701 // 3. De derde bron: wat JIJ deed met een reactie onder je eigen post. De slug 3702 // hangt hier niet aan de rij maar aan de post; vandaar de twee joins. Een 3703 // rij zonder object_uri kan nooit een reactie dragen (fedi-react eist hem), 3704 // dus die uitsluiting verliest per constructie niets. 3705 const acted = (kind, kolom) => ` 3706 FROM ap_interactions i 3707 JOIN posts p ON p.id = i.post_id 3708 JOIN sites s ON s.id = p.site_id 3709 WHERE i.${kolom} = 1 AND IFNULL(i.object_uri, '') <> '' 3710 AND NOT EXISTS (SELECT 1 FROM ap_my_reactions r 3711 WHERE r.site_slug = s.slug AND r.target_uri = i.object_uri AND r.kind = '${kind}')`; 3686 3712 3687 3713 if (opts.dryRun) { 3688 try { 3689 uit.hersleuteld = db.prepare(`SELECT COUNT(*) AS n ${wees}`).get().n; 3690 uit.aangevuld = db.prepare(`SELECT COUNT(*) AS n ${scheef('like', 'liked')}`).get().n 3691 + db.prepare(`SELECT COUNT(*) AS n ${scheef('boost', 'boosted')}`).get().n; 3692 } catch { /* laat de nullen staan */ } 3714 const tel = (sql) => { try { return db.prepare(`SELECT COUNT(*) AS n ${sql}`).get().n; } catch { return 0; } }; 3715 uit.hersleuteld = tel(wees); 3716 uit.aangevuld = tel(scheef('like', 'liked')) + tel(scheef('boost', 'boosted')); 3717 uit.reacties = tel(acted('like', 'acted_like')) + tel(acted('boost', 'acted_boost')); 3693 3718 return uit; 3694 3719 } … … 3710 3735 SELECT t.slug, t.id, '${kind}' ${scheef(kind, kolom)}`).run().changes; 3711 3736 } 3737 3738 // 3. En vanuit acted_* op de reacties onder je eigen posts. 3739 for (const [kind, kolom] of [['like', 'acted_like'], ['boost', 'acted_boost']]) { 3740 uit.reacties += db.prepare(` 3741 INSERT OR IGNORE INTO ap_my_reactions (site_slug, target_uri, kind) 3742 SELECT s.slug, i.object_uri, '${kind}' ${acted(kind, kolom)}`).run().changes; 3743 } 3712 3744 })(); 3713 if (uit.hersleuteld || uit.aangevuld ) {3714 console.log(`[AP] reaction migration v${REACTIONS_MIGRATION_VERSION}: ${uit.hersleuteld} re-keyed, ${uit.aangevuld} backfilled `);3745 if (uit.hersleuteld || uit.aangevuld || uit.reacties) { 3746 console.log(`[AP] reaction migration v${REACTIONS_MIGRATION_VERSION}: ${uit.hersleuteld} re-keyed, ${uit.aangevuld} backfilled, ${uit.reacties} from comments`); 3715 3747 } 3716 3748 if (!opts.force) { -
test/reactions-characterization.test.js
rbedd7a6 r279ca0b 362 362 assert.equal(tweede.aangevuld, 0, 'niets meer aan te vullen'); 363 363 assert.ok(eerste.hersleuteld >= 0); 364 // Zonder force draait hij niet nog eens zodra de vlag staat. 365 db.prepare('INSERT OR REPLACE INTO app_settings (key, value) VALUES (?,?)').run('reactions_migration_version', '1'); 366 assert.equal(AP.migrateReactions().overgeslagen, true); 364 // De vlag is VERSIEgebonden, en niet aan één vast getal: een oudere vlag laat 365 // de migratie opnieuw lopen -- zo doet een bump als v2 zijn werk -- en een 366 // gelijke of hogere slaat hem over. Een hardgecodeerd versienummer hier zou bij 367 // elke bump omvallen zonder dat er iets stuk is. 368 const vlag = (v) => db.prepare('INSERT OR REPLACE INTO app_settings (key, value) VALUES (?,?)').run('reactions_migration_version', v); 369 vlag('1'); 370 assert.equal(AP.migrateReactions().overgeslagen, false, 'een oudere vlag laat hem opnieuw lopen'); 371 vlag('999'); 372 assert.equal(AP.migrateReactions().overgeslagen, true, 'een gelijke of hogere vlag slaat over'); 367 373 }); 368 374
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)