Changeset 952baf3 in Klonkt for src/config/database.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/config/database.js

    rf85b2c3 r952baf3  
    106106    PRIMARY KEY (guardian_slug, id)
    107107  )`);
     108  // Guardianship Fase 2 (shaer-jdb): een doorgestuurde follow-goedkeuring draagt
     109  // een RICHTING. Bij een inkomende is de follower iemand anders en de ward het
     110  // doel; bij een uitgaande is de ward zelf de follower en staat het doel in het
     111  // Follow-object. Zonder deze twee kolommen werd een uitgaande opgeslagen als
     112  // "deze ward wil deze ward volgen" en viel het doel weg -- dan valt er niets
     113  // zinnigs te tonen, hoe je de wachtrij ook vult.
     114  ensureColumn('ap_follow_reviews', 'direction', "TEXT DEFAULT 'incoming'");
     115  ensureColumn('ap_follow_reviews', 'target_uri', 'TEXT');
     116  ensureColumn('ap_follow_reviews', 'target_handle', 'TEXT');
    108117  ensureColumn('sites', 'profile_photo', 'TEXT');
    109118  ensureColumn('audio_tracks', 'cover_url', 'TEXT');
     
    469478      UNIQUE(slug, actor_uri)
    470479    );
     480    -- Antwoorden van accounts die we volgen komen gewoon binnen, ondertekend
     481    -- door de schrijver, maar horen niet in de Krant (belongsInTimeline) en
     482    -- werden daarna nergens bewaard. Kwam er later een doorgestuurd antwoord OP
     483    -- zo'n bericht, dan kenden we de ouder niet en wezen we het af (shaer-e9g).
     484    -- Alleen de URI, geen inhoud: dit voedt uitsluitend de vraag "kennen wij dit
     485    -- bericht?". Wordt na 30 dagen gesnoeid; doorsturen gebeurt kort na het
     486    -- antwoord, dus langer bewaren levert niets op.
     487    CREATE TABLE IF NOT EXISTS ap_seen_notes (
     488      uri TEXT PRIMARY KEY,
     489      created_at DATETIME DEFAULT CURRENT_TIMESTAMP
     490    );
     491    CREATE INDEX IF NOT EXISTS idx_ap_seen_notes_age ON ap_seen_notes(created_at);
    471492    CREATE TABLE IF NOT EXISTS ap_timeline (
    472493      id TEXT NOT NULL,              -- the remote note's AP id
     
    714735  ensureColumn('ap_outbox', 'to_actors', 'TEXT');   // JSON array of recipient actor URIs for direct notes
    715736  ensureColumn('ap_outbox', 'help_request', 'INTEGER'); // FEP-633c shaer:helpRequest (ward's call for help)
     737  // Wie er op een hulpvraag af is, en wanneer hij is afgesloten (shaer-lgo).
     738  // Los van ap_mentions, want dit is GEDEELDE staat: elke guardian van dit kind
     739  // heeft er een kopie van, en die komt binnen als bericht van een ander. Een
     740  // kolom op de mention zou alleen over onszelf gaan.
     741  //
     742  // OPGEPIKT mag stapelen: twee mensen die tegelijk reageren op een kind dat om
     743  // hulp vraagt is geen probleem. Twee mensen die allebei niets doen omdat de
     744  // ander het "geclaimd" had, wel.
     745  //
     746  // AFGEHANDELD kent geen terugdraai. Sluiten gebeurt met een stevige
     747  // bevestiging, en leeft de vraag daarna nog, dan wordt hij opnieuw gesteld --
     748  // een nieuwe hulpvraag. Zo blijft het verslag eerlijk: er wordt niets
     749  // herschreven, er wordt toegevoegd.
     750  db.exec(`CREATE TABLE IF NOT EXISTS ap_help_state (
     751    note_uri TEXT NOT NULL,
     752    guardian_uri TEXT NOT NULL,
     753    kind TEXT NOT NULL,                 -- pickup | handled
     754    guardian_handle TEXT,
     755    created_at TEXT DEFAULT CURRENT_TIMESTAMP,
     756    PRIMARY KEY (note_uri, guardian_uri, kind)
     757  )`);
     758  db.exec('CREATE INDEX IF NOT EXISTS idx_ap_help_state_note ON ap_help_state(note_uri)');
    716759  ensureColumn('ap_mentions', 'help_request', 'INTEGER'); // inbound ward call-for-help (Guardian PWA message centre)
    717760  ensureColumn('ap_outbox', 'wave', 'INTEGER');    // FEP-633c shaer:wave (guardian -> ward nudge)
     
    759802  ensureColumn('ap_followers', 'handle', 'TEXT');  // @user@host
    760803  ensureColumn('ap_followers', 'icon', 'TEXT');    // avatar URL
     804  feedStateTriggers();
     805}
     806
     807/**
     808 * Wat er met een tijdlijn gebeurd is, op één plek (shaer-n05).
     809 *
     810 * De inbox-lezing voegt vier bronnen samen. De vraag "is er iets veranderd" werd
     811 * eerst beantwoord met MAX(rowid) over die vier -- een TOEVALLIGE eigenschap van
     812 * de tabellen, geen feit dat ergens is opgeschreven. Dat gaf precies de gebreken
     813 * die je van zo'n afleiding verwacht: bewerkingen en verwijderingen bewogen hem
     814 * niet, en hij kon achteruit lopen. Dezelfde fout als reacties uitlezen uit
     815 * ap_timeline.liked (shaer-9e9).
     816 *
     817 * Nu één rij per bericht per tijdlijn, met een oplopende `rev` en `kind`. Dat
     818 * beantwoordt drie vragen die anders drie eigen oplossingen zouden krijgen:
     819 * is er iets veranderd sinds N, wát is er veranderd, en is dit bericht bewerkt.
     820 *
     821 * Bijgehouden door TRIGGERS en niet door de aanroepende code, om dezelfde reden
     822 * dat er geen gebeurtenis-emitter is: een trigger zit in de database, dus geen
     823 * enkel codepad kan hem vergeten. De prijs is onzichtbare logica -- wie alleen de
     824 * JavaScript leest ziet niet waarom deze tabel vult. Vandaar dat ze hier staan,
     825 * bij de tabel, en niet verspreid.
     826 *
     827 * Let op de `UPDATE OF`-kolomlijsten: die zijn niet decoratief. Een like schrijft
     828 * ap_timeline.liked en een 🔁 schrijft .boosted; zonder die afbakening zou je
     829 * eigen like het bericht als BEWERKT merken en elke wachtende client wekken.
     830 */
     831function feedStateTriggers() {
     832  try {
     833    db.exec(`
     834      CREATE TABLE IF NOT EXISTS ap_feed_state (
     835        slug TEXT NOT NULL,
     836        object_uri TEXT NOT NULL,
     837        rev INTEGER NOT NULL,
     838        kind TEXT NOT NULL,             -- new | updated | deleted
     839        at DATETIME DEFAULT CURRENT_TIMESTAMP,
     840        PRIMARY KEY (slug, object_uri)
     841      );
     842      CREATE INDEX IF NOT EXISTS idx_ap_feed_state_rev ON ap_feed_state(slug, rev);
     843      -- Eén doorlopende teller voor de hele instance. Bewust niet MAX(rev) uit de
     844      -- tabel zelf: verdwijnt de hoogste rij, dan zou die teruglopen en denkt een
     845      -- client dat er niets gebeurd is.
     846      CREATE TABLE IF NOT EXISTS ap_feed_rev (n INTEGER NOT NULL);
     847    `);
     848    if (!db.prepare('SELECT COUNT(*) AS n FROM ap_feed_rev').get().n) {
     849      db.prepare('INSERT INTO ap_feed_rev (n) VALUES (0)').run();
     850    }
     851    // slug + object_uri verschillen per bron; de rest is voor alle vier gelijk.
     852    const zet = (naam, gebeurtenis, tabel, slug, uri, kind, extra = '') => `
     853      DROP TRIGGER IF EXISTS ${naam};
     854      CREATE TRIGGER ${naam} AFTER ${gebeurtenis} ON ${tabel} BEGIN
     855        UPDATE ap_feed_rev SET n = n + 1;
     856        INSERT INTO ap_feed_state (slug, object_uri, rev, kind)
     857          ${extra || `VALUES (${slug}, ${uri}, (SELECT n FROM ap_feed_rev), '${kind}')`}
     858          ON CONFLICT(slug, object_uri) DO UPDATE
     859            SET rev = excluded.rev, kind = excluded.kind, at = CURRENT_TIMESTAMP;
     860      END;`;
     861    const joinPosts = (uri, kind) => `
     862          SELECT s.slug, ${uri}, (SELECT n FROM ap_feed_rev), '${kind}'
     863            FROM posts p JOIN sites s ON s.id = p.site_id`;
     864    db.exec([
     865      zet('trg_feed_tl_ins', 'INSERT', 'ap_timeline', 'NEW.slug', 'NEW.id', 'new'),
     866      zet('trg_feed_tl_upd', 'UPDATE OF content, media_json, nsfw, cw, url, poll_json, quote_json, embed_json', 'ap_timeline', 'NEW.slug', 'NEW.id', 'updated'),
     867      zet('trg_feed_tl_del', 'DELETE', 'ap_timeline', 'OLD.slug', 'OLD.id', 'deleted'),
     868      zet('trg_feed_mn_ins', 'INSERT', 'ap_mentions', 'NEW.slug', 'NEW.object_uri', 'new'),
     869      zet('trg_feed_mn_upd', 'UPDATE OF content, media_json, quote_json, embed_json', 'ap_mentions', 'NEW.slug', 'NEW.object_uri', 'updated'),
     870      zet('trg_feed_mn_del', 'DELETE', 'ap_mentions', 'OLD.slug', 'OLD.object_uri', 'deleted'),
     871      zet('trg_feed_ob_ins', 'INSERT', 'ap_outbox', 'NEW.site_slug', 'NEW.id', 'new'),
     872      zet('trg_feed_ob_upd', 'UPDATE OF content, attachments', 'ap_outbox', 'NEW.site_slug', 'NEW.id', 'updated'),
     873      zet('trg_feed_ob_del', 'DELETE', 'ap_outbox', 'OLD.site_slug', 'OLD.id', 'deleted'),
     874      // ap_interactions draagt geen slug: die hangt aan de POST. Vandaar de join,
     875      // en vandaar dat deze drie niet in de gewone vorm passen.
     876      zet('trg_feed_ia_ins', 'INSERT', 'ap_interactions', '', '', '', `${joinPosts('NEW.object_uri', 'new')} WHERE p.id = NEW.post_id`),
     877      zet('trg_feed_ia_upd', 'UPDATE OF content, media_json, quote_json, embed_json', 'ap_interactions', '', '', '', `${joinPosts('NEW.object_uri', 'updated')} WHERE p.id = NEW.post_id`),
     878      zet('trg_feed_ia_del', 'DELETE', 'ap_interactions', '', '', '', `${joinPosts('OLD.object_uri', 'deleted')} WHERE p.id = OLD.post_id`),
     879    ].join('\n'));
     880  } catch (e) {
     881    // Niet fataal: zonder deze tabel valt het wachten terug op "altijd de tijd
     882    // volmaken", en dat is traag maar niet stuk.
     883    console.error('❌ feed-state triggers:', e.message);
     884  }
    761885}
    762886
Note: See TracChangeset for help on using the changeset viewer.