Changeset 9946a68 in Klonkt for src


Ignore:
Timestamp:
09/04/2026 12:12:19 PM (4 days ago)
Author:
Robin <roboburr@โ€ฆ>
Branches:
main
Children:
1d1fdc9
Parents:
8151340
Message:

Tijdstempels, bronkant: een spelling bij het schrijven, plus migratie (shaer-a937)

De leeskant van 8151340 is het vangnet; dit haalt de oorzaak weg. Zolang
er twee vormen binnenkomen blijft elke nieuwe query een kans om het
opnieuw fout te doen.

ZES schrijfwegen, niet vier: naast ap_timeline, ap_interactions,
ap_outbox en ap_mentions bleken ook guardianship/delivery (directe notes)
en de archief-import in dezelfde kolommen te schrijven. Alle zes leveren
nu ISO via NU_ISO uit config/database.js, waar isoSql ook staat -- wat je
schrijft en waarmee je vergelijkt horen bij elkaar en dus op een plek.

MIGRATIE voor wat er al stond, in het bestaande idempotente patroon in
initializeDatabase: alleen rijen in de CURRENT_TIMESTAMP-vorm, alleen als
strftime ze begrijpt. Een ISO-stempel blijft ongemoeid en een onleesbare
waarde wordt niet weggegooid -- die bewaar je, ook al weet je er niets
mee.

VIER VERGELIJKINGEN die het omgekeerde deden: een kolom RAUW tegen
datetime('now') leggen. Dat is dezelfde fout gespiegeld, en hij zou juist
door deze release gaan bijten. Het gaat om de wachtwoord-reset (de bead
noemt shaer-1evq), het opruimen van geziene notes en twee queries in de
sessieopslag -- de bead schreef al dat daar de opruiming en de telling
ernaast zaten. Alle vier nu datetime(kolom) tegen datetime('now'),
hetzelfde patroon dat Scheduler al gebruikt.

BEWUST NIET: de 49 DEFAULT CURRENT_TIMESTAMP in het schema. Die vragen om
een tabel-herbouw per stuk, en ze vuren alleen als een INSERT de kolom
overslaat -- wat bij de tabellen die het betreft niet gebeurt, want daar
staat created_at expliciet in het statement.

Drie toetsen erbij, elk met eigen tegenbewijs. Een ervan moest overnieuw:
hij schreef zijn eigen ISO en kon dus niet falen op wat ik veranderde;
nu gaat hij door upsertBoostedNote, de echte schrijfweg. Onderweg zette
ik SQL met enkele quotes in een JS-string met enkele quotes -- vandaar
NU_ISO als constante in template literals, en node --check op elk
aangeraakt bestand.

Volle suite 1255 groen.

Location:
src
Files:
8 edited

Legend:

Unmodified
Added
Removed
  • src/config/database.js

    r8151340 r9946a68  
    10021002    db.prepare("UPDATE posts SET cover_image_url = NULL WHERE cover_image_url LIKE '/media/reply-media/%' AND instr(content, cover_image_url) > 0").run();
    10031003  } catch { /* posts table absent on fresh init */ }
     1004  // Bestaande rijen naar EEN spelling (shaer-a937). De schrijfwegen leveren
     1005  // sinds deze release ISO; dit haalt na wat er in SQL-notatie is blijven
     1006  // staan, zodat de sortering ook zonder de isoSql-wikkel klopt.
     1007  //
     1008  // Alleen rijen met een spatie op positie 11 en geen 'T': dat is precies de
     1009  // CURRENT_TIMESTAMP-vorm. Idempotent -- een tweede keer draaien vindt niets
     1010  // meer -- en het raakt een ISO-stempel nooit aan.
     1011  //
     1012  // strftime geeft NULL op iets dat het niet als tijd herkent; de WHERE laat
     1013  // zulke rijen met rust, want een onleesbare stempel vervangen door NULL is
     1014  // gegevens weggooien. Ze blijven staan zoals ze stonden.
     1015  {
     1016    const kolommen = [
     1017      ['ap_timeline', 'created_at'], ['ap_timeline', 'published'],
     1018      ['ap_mentions', 'created_at'], ['ap_mentions', 'published'],
     1019      ['ap_interactions', 'created_at'], ['ap_interactions', 'published'],
     1020      ['ap_outbox', 'created_at'],
     1021    ];
     1022    let veranderd = 0;
     1023    for (const [tabel, kolom] of kolommen) {
     1024      try {
     1025        const r = db.prepare(
     1026          `UPDATE ${tabel} SET ${kolom} = strftime('%Y-%m-%dT%H:%M:%SZ', ${kolom})
     1027            WHERE ${kolom} IS NOT NULL AND ${kolom} LIKE '____-__-__ %'
     1028              AND strftime('%Y-%m-%dT%H:%M:%SZ', ${kolom}) IS NOT NULL`).run();
     1029        veranderd += r.changes;
     1030      } catch { /* tabel bestaat nog niet op een verse installatie */ }
     1031    }
     1032    if (veranderd) console.log(`๐Ÿ•’ tijdstempels genormaliseerd: ${veranderd} rijen`);
     1033  }
    10041034  ensureColumn('ap_mentions', 'wave', 'INTEGER');  // inbound guardian wave
    10051035  // FEP-633c ยง2.2: object hint that the author is a ward. Register-only for now;
  • src/routes/auth.js

    r8151340 r9946a68  
    223223  const row = db.prepare(`
    224224    SELECT id, username FROM users
    225     WHERE reset_token = ? AND reset_token_expires > datetime('now')
     225    WHERE reset_token = ? AND datetime(reset_token_expires) > datetime('now')
    226226  `).get(hashToken(req.params.token));
    227227  renderPage(req, res, 'pages/auth-reset', {
     
    237237  const row = db.prepare(`
    238238    SELECT id, username FROM users
    239     WHERE reset_token = ? AND reset_token_expires > datetime('now')
     239    WHERE reset_token = ? AND datetime(reset_token_expires) > datetime('now')
    240240  `).get(hashToken(req.params.token));
    241241
  • src/services/ActivityPubService.js

    r8151340 r9946a68  
    1919import fs from 'fs';
    2020import path from 'path';
    21 import db from '../config/database.js';
     21import db, { NU_ISO } from '../config/database.js';
    2222import HtmlSanitizerService from './HtmlSanitizerService.js';
    2323import AudioEmbedService from './AudioEmbedService.js';
     
    14821482function iStmts() {
    14831483  if (!_insI) {
    1484     _insI = db.prepare('INSERT OR IGNORE INTO ap_interactions (kind, post_id, object_uri, actor_uri, actor_name, actor_handle, actor_url, actor_icon, content, published, parent_uri, visibility, emoji_json, actor_emoji_json, created_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,CURRENT_TIMESTAMP)');
     1484    _insI = db.prepare(`INSERT OR IGNORE INTO ap_interactions (kind, post_id, object_uri, actor_uri, actor_name, actor_handle, actor_url, actor_icon, content, published, parent_uri, visibility, emoji_json, actor_emoji_json, created_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,${NU_ISO})`);
    14851485    _delLA = db.prepare('DELETE FROM ap_interactions WHERE kind = ? AND post_id = ? AND actor_uri = ?');
    14861486    _delReply = db.prepare("DELETE FROM ap_interactions WHERE kind = 'reply' AND object_uri = ?");
    14871487    _listI = db.prepare('SELECT id, kind, object_uri, parent_uri, actor_uri, actor_name, actor_handle, actor_url, actor_icon, content, published, created_at, acted_boost, acted_like, visibility, emoji_json, actor_emoji_json FROM ap_interactions WHERE post_id = ? ORDER BY created_at ASC');
    14881488    _getI = db.prepare('SELECT * FROM ap_interactions WHERE id = ?');
    1489     _insO = db.prepare('INSERT INTO ap_outbox (id, site_slug, post_id, post_slug, in_reply_to, to_actor, to_handle, content, language, attachments, created_at) VALUES (?,?,?,?,?,?,?,?,?,?,CURRENT_TIMESTAMP)');
     1489    _insO = db.prepare(`INSERT INTO ap_outbox (id, site_slug, post_id, post_slug, in_reply_to, to_actor, to_handle, content, language, attachments, created_at) VALUES (?,?,?,?,?,?,?,?,?,?,${NU_ISO})`);
    14901490    _listO = db.prepare('SELECT * FROM ap_outbox WHERE post_id = ? ORDER BY created_at ASC');
    14911491    _getO = db.prepare('SELECT * FROM ap_outbox WHERE id = ?');
  • src/services/ArchiveImportService.js

    r8151340 r9946a68  
    2323import crypto from 'crypto';
    2424import { randomUUID } from 'crypto';
    25 import db from '../config/database.js';
     25import db, { NU_ISO } from '../config/database.js';
    2626import { MEDIA_ROOT, AUDIO_ROOT } from '../config/paths.js';
    2727import { FORMAT_VERSION, parseFollowingCsv } from './ArchiveExportService.js';
     
    413413  const insReply = db.prepare(`INSERT OR IGNORE INTO ap_interactions
    414414    (kind, post_id, object_uri, actor_uri, actor_name, actor_handle, content, published, parent_uri, created_at)
    415     VALUES ('reply', ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)`);
     415    VALUES ('reply', ?, ?, ?, ?, ?, ?, ?, ?, ${NU_ISO})`);
    416416
    417417  db.transaction(() => {
  • src/services/SqliteSessionStore.js

    r8151340 r9946a68  
    3535    this._stmtDestroy = db.prepare('DELETE FROM sessions WHERE sid = ?');
    3636    this._stmtTouch = db.prepare('UPDATE sessions SET expiresAt = ? WHERE sid = ?');
    37     this._stmtCount = db.prepare("SELECT COUNT(*) AS c FROM sessions WHERE expiresAt > datetime('now')");
     37    this._stmtCount = db.prepare("SELECT COUNT(*) AS c FROM sessions WHERE datetime(expiresAt) > datetime('now')");
    3838    this._stmtClear = db.prepare('DELETE FROM sessions');
    39     this._stmtAll = db.prepare("SELECT sid, data FROM sessions WHERE expiresAt > datetime('now')");
    40     this._stmtGc = db.prepare("DELETE FROM sessions WHERE expiresAt <= datetime('now')");
     39    this._stmtAll = db.prepare("SELECT sid, data FROM sessions WHERE datetime(expiresAt) > datetime('now')");
     40    this._stmtGc = db.prepare("DELETE FROM sessions WHERE datetime(expiresAt) <= datetime('now')");
    4141  }
    4242
  • src/services/ap-inbox.js

    r8151340 r9946a68  
    1717 * guardian-broers in de dienst, zoals gateOutgoingFollow bij stap 7.
    1818 */
    19 import db from '../config/database.js';
     19import db, { NU_ISO } from '../config/database.js';
    2020import HtmlSanitizerService from './HtmlSanitizerService.js';
    2121import * as Guardianship from './guardianship/index.js';
     
    130130    if (++_seenSinceSnoei >= 500) {
    131131      _seenSinceSnoei = 0;
    132       const r = db.prepare(`DELETE FROM ap_seen_notes WHERE created_at < datetime('now', '-${SEEN_NOTES_DAYS} days')`).run();
     132      const r = db.prepare(`DELETE FROM ap_seen_notes WHERE datetime(created_at) < datetime('now', '-${SEEN_NOTES_DAYS} days')`).run();
    133133      if (r.changes) console.log(`[AP] seen notes: ${r.changes} pruned`);
    134134    }
     
    751751            const ouder = safeUrl(typeof o.inReplyTo === 'string' ? o.inReplyTo : (o.inReplyTo && o.inReplyTo.id)) || null;
    752752            const r = db.prepare(`INSERT OR IGNORE INTO ap_mentions (slug, object_uri, note_url, actor_uri, actor_name, actor_handle, actor_icon, actor_url, content, published, in_reply_to, help_request, wave, has_guardians, emoji_json, actor_emoji_json, media_json, created_at)
    753                                   VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,CURRENT_TIMESTAMP)`)
     753                                  VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,${NU_ISO})`)
    754754              .run(slug, o.id, safeUrl(o.url) || null, actorUri, ai.name, ai.handle, ai.icon, ai.url, html, o.published || null, ouder, help ? 1 : 0, wave ? 1 : 0, hasG ? 1 : 0,
    755755                extractEmojiTags(o.tag), emojiJsonOf(ai.emojis), mediaFromNote(o));
  • src/services/ap-timeline.js

    r8151340 r9946a68  
    1313 * wireTimeline, hetzelfde injectiepatroon als guardianship en ap-c2s.
    1414 */
    15 import db, { isoSql } from '../config/database.js';
     15import db, { isoSql, NU_ISO } from '../config/database.js';
    1616
    1717// De helper woont sinds shaer-a937 in config/database.js: elke plek die
     
    3030export function tlStmts() {
    3131  if (!_insTl) {
    32     _insTl = db.prepare('INSERT OR IGNORE INTO ap_timeline (id, slug, author_uri, author_name, author_handle, author_icon, author_url, content, url, published, media_json, nsfw, cw, created_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,CURRENT_TIMESTAMP)');
     32    _insTl = db.prepare(`INSERT OR IGNORE INTO ap_timeline (id, slug, author_uri, author_name, author_handle, author_icon, author_url, content, url, published, media_json, nsfw, cw, created_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,${NU_ISO})`);
    3333    _listTl = db.prepare(`SELECT * FROM ap_timeline WHERE slug = ? ORDER BY ${STEMPEL('COALESCE(published, created_at)')} DESC LIMIT ? OFFSET ?`);
    3434    _delTl = db.prepare('DELETE FROM ap_timeline WHERE id = ?');
  • src/services/guardianship/delivery.js

    r8151340 r9946a68  
    1212 */
    1313import crypto from 'crypto';
    14 import db from '../../config/database.js';
     14import db, { NU_ISO } from '../../config/database.js';
    1515import { carriesGuardians } from './context.js';
    1616
     
    124124  const id = crypto.randomUUID();
    125125  db.prepare(`INSERT INTO ap_outbox (id, site_slug, post_id, post_slug, in_reply_to, to_actor, to_handle, content, language, attachments, visibility, to_actors, help_request, wave, away_until, created_at)
    126               VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,CURRENT_TIMESTAMP)`)
     126              VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,${NU_ISO})`)
    127127    .run(id, site.slug, '', null, inReplyTo || null, resolved[0].uri, resolved[0].handle, content, lang, media.length ? JSON.stringify(media) : null, 'direct', JSON.stringify(resolved.map((r) => r.uri)), helpRequest ? 1 : 0, wave ? 1 : 0, awayUntil || null);
    128128  const row = getOutboxRow(id);
Note: See TracChangeset for help on using the changeset viewer.