Changeset 58cfe5f in Klonkt


Ignore:
Timestamp:
08/06/2026 02:11:18 PM (5 weeks ago)
Author:
roboburr <roboburr@…>
Branches:
main
Children:
812fea9
Parents:
712b161
git-author:
Robin <roboburr@…> (08/06/2026 02:10:48 PM)
git-committer:
roboburr <roboburr@…> (08/06/2026 02:11:18 PM)
Message:

De importer (shaer-pmr)

ArchiveImportService leest het formaat terug, plus een CLI
(scripts/import-archive.mjs) met droogloop en --overwrite. De vier eisen uit het
document staan expliciet in de code:

VERSIE EERST een hogere onbekende formatVersion wordt in zijn GEHEEL

geweigerd; half lezen ziet eruit alsof het gelukt is

IDENTITEIT gelijke origin -> AP-ids behouden, anders nieuwe ids met een

waarschuwing. De importer stelt dat zelf vast; een verkeerd
antwoord publiceert objecten onder een id dat je niet beheert

NIETS STILS ontbrekende media worden geteld en gemeld, en een archief dat

zijn eigen bestand mist is een APARTE melding -- dat is een
kapot archief, geen verdwenen media

GEEN UITZENDING geen Update de fediverse in

Botsingsregel, gedocumenteerd en niet per post gegokt: een bestaande post met
hetzelfde id of dezelfde slug wordt OVERGESLAGEN. Dat maakt de import idempotent
en zorgt dat je nooit per ongeluk vernietigt wat er al staat. --overwrite doet
het wel, expliciet.

Media gaan terug naar hun oorspronkelijke pad onder /media, want de content van
de post wijst daarheen; dat pad is site-relatief en werkt dus ook op een ander
domein. Checksum wordt gecontroleerd voordat er iets wordt weggeschreven, en een
pad buiten MEDIA_ROOT nooit. Gehoste audio krijgt zijn media- en audio_tracks-rij
terug, anders valt [[track:]] op niets terug -- daarvoor draagt shaer:audio nu
een shaer:media-verwijzing, die in het formaat ontbrak.

readable/ wordt nooit gelezen, en daar is een test voor: vul het met onzin en er
verandert niets.

De zip-lezer kent ook deflate. Een lezer die alleen onze eigen store-only zip
aankan is geen uitwisselformaat maar een prive-doosje.

DERDE VONDST UIT ECHTE DATA vandaag: bij de rondgang op beta bleek poll_json.closed
weg te vallen. Een poll die je vroegtijdig sluit met een einddatum in de toekomst
stond na een herstel weer open. AS2 kent daar "closed" voor op een Question; nu
beide kanten op.

Rondgang op echte beta-data: 4 posts, 3 media, 1 antwoordenarchief, ids behouden,
en na de fix geen enkel kolomverschil meer. 19 tests. Suite 512/512.

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

Files:
3 added
2 edited

Legend:

Unmodified
Added
Removed
  • docs/EXPORT-FORMAT.md

    r712b161 r58cfe5f  
    116116| `shaer:coverAlt` | `posts.cover_alt` | |
    117117| `shaer:viewCount` | `posts.view_count` | optional; a restore without it loses history |
    118 | `shaer:audio` | `audio_tracks` rows referenced from the content | title, artist, duration, credit, license, links, and a `media` reference |
     118| `shaer:audio` | `audio_tracks` rows referenced from the content | title, artist, duration, credit, license, external links, `shaer:ref` (the `[[track:id]]` it belongs to) and `shaer:media` (which attachment holds its file) |
    119119
    120120**Deliberately not exported:** `yjs_binary` (collaborative editor state — large,
  • src/services/ArchiveExportService.js

    r712b161 r58cfe5f  
    109109}
    110110
    111 /** De audio-metadata die alleen in de database staat en nergens anders uit te halen is. */
    112 function audioOf(post) {
     111/**
     112 * De audio-metadata die alleen in de database staat en nergens anders uit te
     113 * halen is: titel, artiest, credit, licentie, externe links.
     114 *
     115 * `shaer:media` koppelt de track aan zijn bestand in het archief. Zonder die
     116 * verwijzing weet een importer wel dát er een track was en hoe hij heette, maar
     117 * niet wélk van de bijlagen erbij hoort -- en dan valt [[track:]] bij een
     118 * herstel op niets terug.
     119 */
     120function audioOf(post, attachments) {
    113121  const uit = [];
    114122  for (const m of String(post.content || '').matchAll(/\[\[track:([A-Za-z0-9_-]+)\]\]/g)) {
    115123    try {
    116       const t = db.prepare('SELECT * FROM audio_tracks WHERE id = ?').get(m[1]);
     124      const t = db.prepare('SELECT t.*, md.storage_path FROM audio_tracks t LEFT JOIN media md ON md.id = t.media_id WHERE t.id = ?').get(m[1]);
    117125      if (!t) continue;
     126      let bestand;
     127      if (t.storage_path) {
     128        const rel = `/media/${path.relative(path.resolve(MEDIA_ROOT), path.resolve(t.storage_path))}`;
     129        const bij = attachments.find((a) => String(a['shaer:originalUrl'] || '').endsWith(rel));
     130        bestand = bij ? bij.url : undefined;
     131      }
    118132      uit.push({
    119133        'shaer:ref': `[[track:${t.id}]]`,
     134        'shaer:media': bestand,
    120135        name: t.title, artist: t.artist || undefined, album: t.album || undefined,
    121136        duration: t.duration || undefined, credit: t.credit || undefined, license: t.license || undefined,
     
    137152      if (!d || !Array.isArray(d.options) || d.options.length < 2) return null;
    138153      const opties = d.options.map((o) => ({ type: 'Note', name: String(o && o.name != null ? o.name : o) }));
    139       return { multiple: !!d.multiple, opties, endTime: d.endTime || null };
     154      return { multiple: !!d.multiple, opties, endTime: d.endTime || null, closed: !!d.closed };
    140155    } catch { return null; }
    141156  })();
     
    164179    ...(poll ? (poll.multiple ? { anyOf: poll.opties } : { oneOf: poll.opties }) : {}),
    165180    endTime: poll ? (poll.endTime || undefined) : undefined,
     181    // AS2 kent `closed` op een Question. Zonder dit staat een poll die vroegtijdig
     182    // is gesloten na een herstel weer open -- gevonden op echte beta-data.
     183    closed: (poll && poll.closed) ? true : undefined,
    166184    quoteUrl: post.quote_uri || undefined,
    167185    'shaer:quoteActor': post.quote_actor || undefined,
     
    179197    'shaer:coverAlt': post.cover_alt || undefined,
    180198    'shaer:viewCount': post.view_count || undefined,
    181     'shaer:audio': audioOf(post),
     199    'shaer:audio': audioOf(post, attachments),
    182200  };
    183201}
Note: See TracChangeset for help on using the changeset viewer.