Changeset 952baf3 in Klonkt for src/routes/posts.js


Ignore:
Timestamp:
08/07/2026 05:15:52 PM (5 weeks ago)
Author:
Robin <roboburr@…>
Branches:
main
Children:
ba76bf5
Parents:
f85b2c3 (diff), 0d5bd2c (diff)
Note: this is a merge changeset, the changes displayed below correspond to the merge itself.
Use the (diff) links above to see all the changes relative to each parent.
Message:

Merge GitHub-main (1.7.0) met de VPS-lijn

De twee mains waren een dag gedivergeerd en bevatten elk echt werk. GitHub had 66
commits die nooit langs prutfolio.git zijn gekomen, omdat een parallelle sessie
rechtstreeks naar GitHub pushte vanaf een kloon in /tmp op de VPS. De VPS had twee
commits die GitHub niet had. Geen van beide bevatte de ander, en stable had geen van
de twee.

Bewust een merge en geen rebase: dan blijft beide historie intact en wordt er niets
herschreven waar iemand anders al op voortbouwt.

Drie bestanden raakten beide kanten. Alle drie zijn nagekeken, want dat een merge
automatisch slaagt zegt niets over of hij inhoudelijk klopt:

src/services/ActivityPubService.js

  • de sleutelbinding staat nu boven de nieuwe asSlug-aanroep van 1.7.0, dus de controle komt nog steeds voor de handtekeningcontrole

scripts/klonkt-refresh-updater.sh

  • alleen de opzij-aanpak overleefde; systemctl mask staat nergens meer als code

deploy/MULTI-INSTANCE.md

  • spreekt zichzelf niet tegen: beschrijft opzij zetten, met de reden waarom mask weigert

remarks: het gat dat in de review naar boven kwam staat hiermee ook op de 1.7.0-lijn.
De andere bevindingen uit die review staan nog open en zijn niet in deze merge
opgelost; die horen als beads. Ook nog te doen: dezelfde sleutelbinding op stable
als 1.6.1, want daar is het gat nog open bij self-hosters.

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

File:
1 edited

Legend:

Unmodified
Added
Removed
  • src/routes/posts.js

    rf85b2c3 r952baf3  
    256256
    257257  renderPage(req, res, 'pages/post-edit', {
     258    // post-edit neemt de playlist-editor op.
     259    pageJs: 'post-edit playlist-editor',
    258260    post: {
    259261      id: uuid(),
     
    795797  }
    796798  renderPage(req, res, 'pages/authorize-interaction', {
     799    pageJs: 'authorize-interaction',
    797800    pageTitleKey: 'fedi.remote_interact', // i18n: was hardcoded Dutch on non-NL sites
    798801    bodyClass: 'on-special',
     
    806809    liked: !!req.query.liked,
    807810    boosted: !!req.query.boosted,
    808     reacted: (site && uri) ? ActivityPubService.getMyReactions(site.slug, uri) : { liked: false, boosted: false },
     811    reacted: (site && uri) ? ActivityPubService.getReaction(site.slug, uri) : { liked: false, boosted: false },
    809812    siteTitle: site ? site.title : '',
    810813  });
     
    839842  let on = false;
    840843  if (site && uri) {
    841     on = !ActivityPubService.getMyReactions(site.slug, uri).liked;
     844    on = !ActivityPubService.getReaction(site.slug, uri).liked;
    842845    ActivityPubService.resolveRemoteNote(uri)
    843846      .then((note) => note && ActivityPubService.sendInteraction(site, on ? 'like' : 'unlike', note.object_uri || uri, note.actor_uri))
    844847      .catch((e) => console.warn('[AP] remote like failed:', e.message));
    845     ActivityPubService.setMyReaction(site.slug, uri, 'like', on);
     848    // Eén schrijfpad (shaer-9e9): tussentabel + afgeleide vlag.
     849    ActivityPubService.setReaction(site.slug, uri, 'like', on);
    846850  }
    847851  if (req.get('X-Requested-With') === 'fetch') return res.json({ ok: true, on });
     
    856860  let on = false;
    857861  if (site && uri) {
    858     on = !ActivityPubService.getMyReactions(site.slug, uri).boosted;
     862    on = !ActivityPubService.getReaction(site.slug, uri).boosted;
    859863    ActivityPubService.resolveRemoteNote(uri)
    860864      .then((note) => {
     
    862866        const id = note.object_uri || uri;
    863867        return Promise.resolve(ActivityPubService.sendInteraction(site, on ? 'boost' : 'unboost', id, note.actor_uri))
    864           // Boost → store the post in the timeline (even if you don't follow the author) so it
    865           // surfaces in the Cirkel; unboost → just clear the flag.
    866           .then(() => on ? ActivityPubService.upsertBoostedNote(site.slug, note) : ActivityPubService.unmarkBoosted(site.slug, id));
     868          // De note gaat mee: een boost zet niet alleen een vlag maar trekt de
     869          // post je tijdlijn in, ook als je de auteur niet volgt, zodat hij in
     870          // de Cirkel verschijnt.
     871          .then(() => ActivityPubService.setReaction(site.slug, uri, 'boost', on, { flagUri: id, note: on ? note : null }));
    867872      })
    868873      .catch((e) => console.warn('[AP] remote boost failed:', e.message));
    869     ActivityPubService.setMyReaction(site.slug, uri, 'boost', on);
     874    // Meteen zetten, zodat de knop klopt voordat de resolve terug is. Via
     875    // setReaction en niet via setMyReaction: ook dit korte moment mag geen
     876    // halve schrijfactie zijn. De resolve hierboven werkt hem daarna bij met de
     877    // note, zodat de post ook in je tijdlijn belandt.
     878    ActivityPubService.setReaction(site.slug, uri, 'boost', on);
    870879  }
    871880  if (req.get('X-Requested-With') === 'fetch') return res.json({ ok: true, on });
     
    930939    : []).filter((o) => o['shaer:ward'] === gMe && o['shaer:needsMyAccept']);
    931940  renderPage(req, res, 'pages/messages', {
    932     pageTitleKey: 'msg.title', bodyClass: 'on-special', items, seenAt,
     941    pageTitleKey: 'msg.title', bodyClass: 'on-special', pageJs: 'messages', items, seenAt,
    933942    hasMore, nextOffset: offset + FEED_PAGE, moreBase, guardianOffers,
    934943    success: req.query.success || null, error: req.query.error || null,
     
    965974  } catch { /* fall through */ }
    966975  res.redirect(back + '?error=quickreply');
     976});
     977
     978// Antwoorden vanuit een gesprek in Berichten. Twee paden, en welke het wordt
     979// bepaalt de draad zelf (zie groupConversations → replyTo):
     980//   - hangt de draad aan een post van jou, dan is dit een gewone reply op het
     981//     nieuwste ontvangen bericht erin: deliverReply, publiek zoals de thread;
     982//   - hangt hij aan een persoon, dan is het een direct bericht terug.
     983// Rijk in beide gevallen: `content` is de HTML uit de reply-editor, `text` de
     984// platte versie die de editor er altijd bij levert (en die het no-JS-formulier
     985// als enige stuurt).
     986router.post('/messages/reply', requireSiteManager, async (req, res) => {
     987  const site = res.locals.site;
     988  const back = `${res.locals.siteUrlBase || ''}/messages`;
     989  if (!site) return res.status(404).send('Site required');
     990  const text = String(req.body.text || '');
     991  const html = String(req.body.content || '');
     992  let attachments = [];
     993  try { attachments = JSON.parse(req.body.attachments || '[]'); } catch { /* geen media */ }
     994  let mentions;
     995  try { if (req.body.mentions !== undefined) mentions = JSON.parse(req.body.mentions || '[]'); } catch { mentions = undefined; }
     996  const language = String(req.body.language || '');
     997  // Leeg is leeg: een bericht zonder tekst EN zonder media is geen bericht.
     998  if (!text.trim() && !html.trim() && !attachments.length) return res.redirect(back + '?error=reply_empty');
     999
     1000  const interactionId = parseInt(req.body.interaction_id, 10) || 0;
     1001  const postSlug = String(req.body.post_slug || '');
     1002  const toActor = String(req.body.to || '');
     1003  try {
     1004    if (interactionId && postSlug) {
     1005      const post = db.prepare('SELECT id, slug FROM posts WHERE site_id = ? AND slug = ?').get(site.id, postSlug);
     1006      const parent = ActivityPubService.getInteractionById(interactionId);
     1007      // De parent MOET bij deze post horen: anders zou een gemanipuleerd
     1008      // formulier een antwoord onder andermans draad kunnen hangen.
     1009      if (!post || !parent || parent.post_id !== post.id) return res.redirect(back + '?error=reply_target');
     1010      await ActivityPubService.deliverReply(site, {
     1011        postId: post.id, postSlug: post.slug, parent, text, html, attachments, mentions, language,
     1012      });
     1013    } else if (/^https?:\/\//i.test(toActor)) {
     1014      const r = await Guardianship.deliverDirectNote(site, { recipients: [toActor], text, html, language, attachments });
     1015      if (!r) return res.redirect(back + '?error=reply_failed');
     1016    } else {
     1017      return res.redirect(back + '?error=reply_target');
     1018    }
     1019  } catch (e) {
     1020    console.warn('[AP] reply from Berichten failed:', e.message);
     1021    return res.redirect(back + '?error=reply_failed');
     1022  }
     1023  res.redirect(back + '?success=reply_sent');
    9671024});
    9681025
     
    11391196  }
    11401197  renderPage(req, res, 'pages/news', {
     1198    pageJs: 'news',
    11411199    pageTitle: 'News', bodyClass: 'on-special',
    11421200    timeline, hasMore, nextOffset: offset + FEED_PAGE, moreBase,
     
    12291287  let on = false;
    12301288  if (site && note) {
    1231     on = !ActivityPubService.getTimelineReaction(site.slug, note).liked;
     1289    on = !ActivityPubService.getReaction(site.slug, note).liked;
    12321290    try { await ActivityPubService.sendInteraction(site, on ? 'like' : 'unlike', note, (req.body.author || '').toString()); } catch (e) { /* ignore */ }
    1233     if (on) ActivityPubService.markLiked(site.slug, note); else ActivityPubService.unmarkLiked(site.slug, note);
     1291    ActivityPubService.setReaction(site.slug, note, 'like', on);
    12341292  }
    12351293  if (req.get('X-Requested-With') === 'fetch') return res.json({ ok: true, on });
     
    12431301  let on = false;
    12441302  if (site && note) {
    1245     on = !ActivityPubService.getTimelineReaction(site.slug, note).boosted;
     1303    on = !ActivityPubService.getReaction(site.slug, note).boosted;
    12461304    try { await ActivityPubService.sendInteraction(site, on ? 'boost' : 'unboost', note, (req.body.author || '').toString()); } catch (e) { /* ignore */ }
     1305    ActivityPubService.setReaction(site.slug, note, 'boost', on); // instant UI state
    12471306    if (on) {
    1248       ActivityPubService.markBoosted(site.slug, note); // instant UI state
    12491307      // Fire-and-forget: re-resolve the note so the cached row is refreshed
    12501308      // (cover/content) — boosting again heals a stale copy from EVERY boost
    12511309      // path, not just the interact page.
    12521310      ActivityPubService.resolveRemoteNote(note)
    1253         .then((n) => { if (n) ActivityPubService.upsertBoostedNote(site.slug, n); })
     1311        .then((n) => { if (n) ActivityPubService.setReaction(site.slug, note, 'boost', true, { note: n }); })
    12541312        .catch(() => { /* best-effort */ });
    1255     } else {
    1256       ActivityPubService.unmarkBoosted(site.slug, note);
    12571313    }
    12581314  }
     
    13381394    const { newerPost, olderPost } = postNeighbors(site, post);
    13391395    return renderPage(req, res, 'pages/paid-gate', {
     1396    pageJs: 'paid-gate',
    13401397      pageTitle: post.title || 'Voor supporters',
    13411398      bodyClass: 'on-special',
     
    14541511
    14551512  renderPage(req, res, 'pages/post', {
     1513    pageJs: 'post',
    14561514    post,
    14571515    poll: ActivityPubService.ownPollView(post),
     
    15051563  const kind = req.body.kind === 'boost' ? 'boost' : 'like';
    15061564  if (parent && parent.post_id === post.id && parent.object_uri) {
    1507     if (kind === 'boost') {
    1508       // Toggle: boost an unboosted comment, or retract it (Undo Announce) if already boosted.
    1509       const on = !parent.acted_boost;
    1510       ActivityPubService.sendInteraction(site, on ? 'boost' : 'unboost', parent.object_uri, parent.actor_uri)
    1511         .catch((e) => console.warn('[AP] reaction failed:', e.message));
    1512       ActivityPubService.setInteractionBoosted(parent.id, on);
    1513     } else {
    1514       // Toggle: like an unliked comment, or un-favourite (Undo Like) if already liked.
    1515       const on = !parent.acted_like;
    1516       ActivityPubService.sendInteraction(site, on ? 'like' : 'unlike', parent.object_uri, parent.actor_uri)
    1517         .catch((e) => console.warn('[AP] reaction failed:', e.message));
    1518       ActivityPubService.setInteractionLiked(parent.id, on);
    1519     }
     1565    // Toggle: react, or retract it (Undo Announce / Undo Like) if already on.
     1566    // De stand komt uit dezelfde bron als de knop die je zag; leest de toggle uit
     1567    // de kolom en de knop uit de tussentabel, dan draait een divergentie de
     1568    // richting om en stuur je een Undo voor iets dat nooit is verstuurd.
     1569    const ik = ActivityPubService.getReaction(site.slug, parent.object_uri);
     1570    const on = kind === 'boost' ? !ik.boosted : !ik.liked;
     1571    ActivityPubService.sendInteraction(site, on ? kind : `un${kind}`, parent.object_uri, parent.actor_uri)
     1572      .catch((e) => console.warn('[AP] reaction failed:', e.message));
     1573    // De tussentabel is de waarheid (shaer-ipb), gesleuteld op object_uri -- net
     1574    // als de Like die hierboven de fediverse in gaat. acted_* blijft voorlopig
     1575    // als afgeleide meelopen, hetzelfde vangnet dat ap_timeline.liked na
     1576    // shaer-9e9 is: pas weghalen als deze migratie een release heeft ingelopen.
     1577    ActivityPubService.setReaction(site.slug, parent.object_uri, kind, on);
     1578    if (kind === 'boost') ActivityPubService.setInteractionBoosted(parent.id, on);
     1579    else ActivityPubService.setInteractionLiked(parent.id, on);
    15201580  }
    15211581  res.redirect(`${res.locals.siteUrlBase || ''}/${post.slug}#fediverse`);
Note: See TracChangeset for help on using the changeset viewer.