Changeset 5045c30 in Klonkt


Ignore:
Timestamp:
06/26/2026 12:00:18 PM (2 months ago)
Author:
Robin Genis <roboburr@…>
Branches:
main
Children:
3384e95
Parents:
484adf8
Message:

feat(cirkel): boosted posts also appear in the Cirkel (mixed by date, deduped)

Option B: when you boost (🔁) a timeline post it's marked ap_timeline.boosted and
shows in the Cirkel alongside the featured accounts' posts, sorted by date. One row
per note → a post that's both featured and boosted appears once (no double). The
Cirkel/tab now also shows when you only have boosted posts (no featured accounts).
No fediverse traffic — the boost itself is the existing user action; this only marks
it locally.

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

Location:
src
Files:
5 edited

Legend:

Unmodified
Added
Removed
  • src/config/database.js

    r484adf8 r5045c30  
    400400    CREATE INDEX IF NOT EXISTS idx_ap_delivery_due ON ap_delivery(next_at);
    401401  `);
    402   // Auto-boost a followed account's posts ("feature an artist"): their new
    403   // top-level posts get re-Announced to our followers automatically.
     402  // "Feature" a followed account: its posts show in the local Cirkel.
    404403  ensureColumn('ap_following', 'auto_boost', 'INTEGER DEFAULT 0');
     404  // A timeline post you boosted (🔁) — also shown in the Cirkel (mixed by date).
     405  ensureColumn('ap_timeline', 'boosted', 'INTEGER DEFAULT 0');
    405406}
    406407
  • src/middleware/render.js

    r484adf8 r5045c30  
    125125    // Cirkel = the artists you feature (auto-boost). Shown when AP is on and you
    126126    // auto-boost ≥1 account, or (legacy) on a circle-tenancy site.
    127     hasCirkel: !!(_site && apEnabled() && ActivityPubService.autoBoostCount(_site.slug) > 0),
     127    hasCirkel: !!(_site && apEnabled() && (ActivityPubService.autoBoostCount(_site.slug) > 0 || ActivityPubService.boostedCount(_site.slug) > 0)),
    128128    isViewer: _isViewer,
    129129    canMutate: !_isViewer,
  • src/routes/circle.js

    r484adf8 r5045c30  
    2929router.get('/cirkel', (req, res, next) => {
    3030  const site = res.locals.site;
    31   if (!site || !apEnabled() || ActivityPubService.autoBoostCount(site.slug) === 0) return next();
     31  if (!site || !apEnabled() || (ActivityPubService.autoBoostCount(site.slug) === 0 && ActivityPubService.boostedCount(site.slug) === 0)) return next();
    3232
    3333  const posts = ActivityPubService.getCirkelPosts(site.slug, 80).map((r) => {
  • src/routes/posts.js

    r484adf8 r5045c30  
    627627router.post('/tijdlijn/boost', requireSiteManager, async (req, res) => {
    628628  const site = res.locals.site;
    629   if (site) { try { await ActivityPubService.sendInteraction(site, 'boost', (req.body.note || '').toString(), (req.body.author || '').toString()); } catch (e) { /* ignore */ } }
     629  const note = (req.body.note || '').toString();
     630  if (site && note) {
     631    try { await ActivityPubService.sendInteraction(site, 'boost', note, (req.body.author || '').toString()); } catch (e) { /* ignore */ }
     632    ActivityPubService.markBoosted(site.slug, note); // also surface it in the Cirkel (mixed by date)
     633  }
    630634  res.redirect('/tijdlijn?success=' + encodeURIComponent('Geboost 🔁'));
    631635});
  • src/services/ActivityPubService.js

    r484adf8 r5045c30  
    10561056export function getCirkelPosts(slug, limit) {
    10571057  try {
     1058    // Cirkel = posts from featured (auto_boost) accounts + posts you boosted
     1059    // (t.boosted), mixed by date. One row per note in ap_timeline → no duplicates.
    10581060    if (!_cirkelPosts) _cirkelPosts = db.prepare(`
    10591061      SELECT t.id, t.author_uri, t.author_name, t.author_handle, t.author_icon, t.author_url,
    10601062             t.content, t.url, t.published, t.media_json
    10611063      FROM ap_timeline t
    1062       JOIN ap_following f ON f.slug = t.slug AND f.actor_uri = t.author_uri AND f.auto_boost = 1
    1063       WHERE t.slug = ?
     1064      LEFT JOIN ap_following f ON f.slug = t.slug AND f.actor_uri = t.author_uri
     1065      WHERE t.slug = ? AND (f.auto_boost = 1 OR t.boosted = 1)
    10641066      ORDER BY COALESCE(t.published, t.created_at) DESC, t.rowid DESC
    10651067      LIMIT ?`);
     
    10691071export function getCirkelMembers(slug) {
    10701072  try { if (!_cirkelMembers) _cirkelMembers = db.prepare('SELECT name, url, icon FROM ap_following WHERE slug = ? AND auto_boost = 1 ORDER BY name'); return _cirkelMembers.all(slug); } catch { return []; }
     1073}
     1074// Mark a timeline post as boosted so it shows in the Cirkel (mixed by date).
     1075let _markBoost, _boostedCount;
     1076export function markBoosted(slug, noteId) {
     1077  try { if (!_markBoost) _markBoost = db.prepare('UPDATE ap_timeline SET boosted = 1 WHERE slug = ? AND id = ?'); _markBoost.run(slug, noteId); } catch { /* ignore */ }
     1078}
     1079export function boostedCount(slug) {
     1080  try { if (!_boostedCount) _boostedCount = db.prepare('SELECT COUNT(*) AS n FROM ap_timeline WHERE slug = ? AND boosted = 1'); return _boostedCount.get(slug).n; } catch { return 0; }
    10711081}
    10721082
     
    13461356  listOutbox, deliverOutboxDelete,
    13471357  webfingerResolve, followActor, resolveRemoteActor, unfollowActor, listFollowing, setAutoBoost, getTimeline, sendInteraction,
    1348   autoBoostCount, getCirkelPosts, getCirkelMembers, autoMigrateCircles, selfHealTimeline, boostLatestN,
     1358  autoBoostCount, boostedCount, markBoosted, getCirkelPosts, getCirkelMembers, autoMigrateCircles, selfHealTimeline, boostLatestN,
    13491359  getNotifications, listBlocks, isBlockedAny, blockTarget, unblock,
    13501360  deliverWithRetry, enqueueDelivery, processDeliveryQueue, startDeliveryWorker,
Note: See TracChangeset for help on using the changeset viewer.