Changeset 31680c3 in Klonkt for src/routes


Ignore:
Timestamp:
09/30/2026 08:39:24 AM (5 hours ago)
Author:
Robin <roboburr@…>
Branches:
main
Children:
746d98a
Parents:
76bd53c
Message:

[Add to HUB] on the Connect page — the click is the owner's yes

Putting a klonkt on the Klonkt Hub means the hub sends a Follow. With the
owner gate on (the default) that Follow then waits for the owner — the same
person who just asked for it. Without this they would sign up on the hub and
then have to approve their own sign-up on /connect.

The button POSTs to /connect/add-to-hub, which records a one-day invitation
and redirects to the hub's sign-up form with the site's handle filled in
(?add=). A Follow from the hub's host, signed by the hub itself, passes the
owner gate while that invitation holds. It is not used up on the first
Follow: a hub that re-sends after a timeout would otherwise land in the queue
for something already decided.

Narrow on purpose: only the hub's host, only signed by it, only for a day.
Anyone else still waits for the owner, and a WARD's guardians still decide —
their gate sits before this one and this path never reaches it.

If a request from the hub is already waiting (someone signed the klonkt up
earlier), the click is exactly the yes it waits for: it is accepted on the
spot and the button goes to the channel on the hub instead of the form. Once
the hub follows, /connect shows a confirmation instead of the button. Hidden
after a move, where the outgoing side is locked.

KLONKT_HUB_URL lets a self-hoster point the button at another hub; default
​https://hub.klonkt.com.

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

File:
1 edited

Legend:

Unmodified
Added
Removed
  • src/routes/posts.js

    r76bd53c r31680c3  
    2121import ActivityPubService from '../services/ActivityPubService.js';
    2222import * as Guardianship from '../services/guardianship/index.js';
     23import * as HubInvite from '../services/hub-invite.js';
    2324import { premiumUnlocked } from '../services/PatreonService.js';
    2425import { defaultMinCents as paidDefaultMinCents, patreonUrl as paidPatronUrl } from '../services/PaidPatreonService.js';
    … …  
    13401341    connections, myGuardians, followRequests,
    13411342    approveFollowers: !!(site && site.approve_followers),
     1343    // [Add to HUB]: staat hij er al op, dan een bevestiging in plaats van de knop.
     1344    hub: site ? { url: HubInvite.hubUrl(), onHub: HubInvite.onHub(site.slug), ward: myGuardians.length > 0 } : null,
    13421345    // Na een verhuizing staat de uitgaande kant op slot. Dat hoort te blijken
    13431346    // VOORDAT je op een knop drukt, niet daarna uit een foutmelding.
    … …  
    13691372  }
    13701373  return res.redirect(`${base}/connect`);
     1374});
     1375
     1376// [Add to HUB] (Robin, 30-9): zet deze klonkt op de Klonkt Hub.
     1377//
     1378// De knop stuurt je naar het aanmeldformulier van de hub, vooraf ingevuld met
     1379// je eigen handle; aanmelden blijft daar een eigen klik. Hier gebeurt het
     1380// deel dat de hub niet kan: de eigenaar geeft alvast zijn ja voor de Follow
     1381// die daarop volgt, zodat hij straks niet zijn eigen aanmelding hoeft goed te
     1382// keuren (zie services/hub-invite.js).
     1383//
     1384// Staat er al een verzoek van de hub te wachten -- iemand meldde je eerder
     1385// aan -- dan is deze klik precies het ja waar dat verzoek op wacht, en hoef je
     1386// niet langs het formulier. Bij een WARD nooit: daar beslissen de guardians.
     1387router.post('/connect/add-to-hub', requireSiteManager, async (req, res) => {
     1388  const site = res.locals.site;
     1389  const base = res.locals.siteUrlBase || '';
     1390  if (!site) return res.redirect(`${base}/connect`);
     1391  if (ActivityPubService.movedLock(site).movedTo) {
     1392    return res.redirect(`${base}/connect?error=` + encodeURIComponent('Dit account is verhuisd'));
     1393  }
     1394  const pub = (process.env.PUBLIC_BASE_URL || '').replace(/\/+$/, '');
     1395  const handle = ActivityPubService.deriveHandle(ActivityPubService.actorId(pub, site.slug)).replace(/^@/, '');
     1396  const hub = HubInvite.hubUrl();
     1397  const isWard = Guardianship.listGuardians(site.slug).length > 0;
     1398
     1399  if (!isWard) {
     1400    const wacht = Guardianship.follows.listForWard(site.slug)
     1401      .find((f) => f.status === 'pending' && HubInvite.isHubActor(f.follower_uri));
     1402    if (wacht) {
     1403      await ActivityPubService.acceptGatedFollow(wacht);
     1404      Guardianship.follows.remove(wacht.id);
     1405      return res.redirect(303, `${hub}/?klonkt=${encodeURIComponent(handle)}`);
     1406    }
     1407  }
     1408  HubInvite.invite(site.id);
     1409  return res.redirect(303, `${hub}/?add=${encodeURIComponent(handle)}`);
    13711410});
    13721411
Note: See TracChangeset for help on using the changeset viewer.