Ignore:
File:
1 edited

Legend:

Unmodified
Added
Removed
  • src/config/database.js

    ra29c8c8 rfa33214  
    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');
    117108  ensureColumn('sites', 'profile_photo', 'TEXT');
    118109  ensureColumn('audio_tracks', 'cover_url', 'TEXT');
     
    478469      UNIQUE(slug, actor_uri)
    479470    );
    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);
    492471    CREATE TABLE IF NOT EXISTS ap_timeline (
    493472      id TEXT NOT NULL,              -- the remote note's AP id
     
    735714  ensureColumn('ap_outbox', 'to_actors', 'TEXT');   // JSON array of recipient actor URIs for direct notes
    736715  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)');
    759716  ensureColumn('ap_mentions', 'help_request', 'INTEGER'); // inbound ward call-for-help (Guardian PWA message centre)
    760717  ensureColumn('ap_outbox', 'wave', 'INTEGER');    // FEP-633c shaer:wave (guardian -> ward nudge)
     
    802759  ensureColumn('ap_followers', 'handle', 'TEXT');  // @user@host
    803760  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  */
    831 function 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   }
    885761}
    886762
Note: See TracChangeset for help on using the changeset viewer.