Changeset 749605a in Klonkt for src/services/PlaylistService.js


Ignore:
Timestamp:
08/16/2026 03:06:18 PM (3 weeks ago)
Author:
Robin <roboburr@…>
Branches:
main
Children:
a5caa73
Parents:
a5515b4 (diff), 9026de5 (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:

Bibliotheek en album-federatie terug op main (shaer-0nh, shaer-756s)

Achttien commits, in drie brokken.

DE BIBLIOTHEEK. Een Library-skelet met een eigen volgerssoort: wie de
bibliotheek volgt krijgt de muziek en met opzet niet de gewone posts, en
die staan daarom in een eigen tabel -- een vlag die iemand vergeet te
filteren bezorgt stilletjes de Krant aan mensen die er niet om vroegen.
Daarna wat er nodig bleek om werkelijk gelezen te worden: audience
(zonder dat gaf hun create() een KeyError en dus een 500 op onze URL),
de ongeordende collectievorm, en paginering op elke collectie die first
belooft -- die laatste vond meteen dat featured hetzelfde gat had.

HET VOCABULAIRE. fw:track in de kleine vorm die Emissary ook stuurt,
artist_credit met de site-actor als Artist (mogelijk geworden door de
MusicBrainz-koppeling), en het album als ingesloten fw:Album met
released, musicbrainzId en een eigen artist_credit. Daarvoor kwamen er
twee kolommen op playlists, alleen bij kind='album' en afgedwongen in
PlaylistService -- niet in het scherm, want de API ligt open.

HET BEHEER. De vijf mediapagina's stonden op drie verschillende
ontwerpen; nu op een gedeelde ax-romp, met de primitieven op een plek in
plaats van in acht kopieen. De playlist-modal ging alsnog door i18n: die
teksten stonden hard in het script, wat niet opviel omdat het toevallig
de brontaal was.

WAT ER NIET IN ZIT: bitrate en size top-level als fw:. Dat is bewust
(shaer-3f8a). Hun eigen outbox zet ze op de Link, net als wij, en het
wordt een gesprek in plaats van een aanpassing -- briefing in
~/bewaard/funkwhale-briefing-en.md.

Gereedschap dat meekomt: scripts/funkwhale-gat.mjs meet het gat naar hun
ingest, admin-css.mjs en admin-css-gelijk.mjs ontleden en vergelijken de
beheer-CSS, render-admin-views.mjs rendert de views gevuld en leeg. Die
laatste drie omdat de testsuite geen enkele view aanraakt.

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

File:
1 edited

Legend:

Unmodified
Added
Removed
  • src/services/PlaylistService.js

    ra5515b4 r749605a  
    1515import { v4 as uuid } from 'uuid';
    1616
     17/**
     18 * Een volledige datum of niets (shaer-756s).
     19 *
     20 * STRIKT, en dat is de hele functie. `year` bestaat al en blijft; dit veld
     21 * bestaat juist omdat een jaartal geen uitgavedatum is. Zou hij "2024"
     22 * doorlaten en er 2024-01-01 van maken, dan stond er straks een dag op de
     23 * federatie die niemand ooit heeft ingevoerd -- en dan hadden we het veld net
     24 * zo goed niet kunnen toevoegen.
     25 *
     26 * Ook 2024-02-31 valt af: dat is geen strengheid om de strengheid, Date rolt
     27 * hem stilletjes door naar 2 maart en dan slaan we iets anders op dan er
     28 * ingetypt is.
     29 */
     30function normDatum(v) {
     31  const s = String(v == null ? '' : v).trim();
     32  if (!s) return null;
     33  if (!/^\d{4}-\d{2}-\d{2}$/.test(s)) return null;
     34  const d = new Date(`${s}T00:00:00Z`);
     35  return Number.isNaN(d.getTime()) || d.toISOString().slice(0, 10) !== s ? null : s;
     36}
     37
     38/** Een MusicBrainz-id of niets. Zelfde vorm als sites.mb_artist_id. */
     39function normMbid(v) {
     40  const s = String(v == null ? '' : v).trim().toLowerCase();
     41  return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(s) ? s : null;
     42}
     43
    1744class PlaylistService {
    1845
     
    5279    const rows = db.prepare(`
    5380      SELECT p.id, p.title, p.artist, p.year, p.cover_url, p.kind,
     81             p.release_date, p.mb_release_id,
    5482             p.created_at, p.updated_at,
    5583             (SELECT COUNT(*) FROM playlist_tracks WHERE playlist_id = p.id) AS track_count
     
    6593      cover: r.cover_url || '',
    6694      kind: r.kind || 'album',
     95      release_date: r.release_date || '',
     96      mb_release_id: r.mb_release_id || '',
    6797      track_count: r.track_count,
    6898      created_at: r.created_at,
     
    86116    if (!id) return null;
    87117    const p = db.prepare(`
    88       SELECT id, title, artist, year, cover_url, kind, created_at, updated_at
     118      SELECT id, title, artist, year, cover_url, kind, release_date, mb_release_id, created_at, updated_at
    89119      FROM playlists WHERE site_id = ? AND id = ?
    90120    `).get(siteId, id);
     
    125155      cover: p.cover_url || fallbackCover,
    126156      kind: (p.kind === 'playlist') ? 'playlist' : 'album',
     157      // created_at hoort erbij omdat de AP-kant er `published` van maakt. Zonder
     158      // dit veld viel buildAlbumObject terug op 1970, en dat stond op 16-8
     159      // gewoon op de federatie.
     160      created_at: p.created_at,
     161      // Leeg als het een afspeellijst is -- de opslag houdt ze daar al leeg,
     162      // maar dit is de plek waar de editor leest en die mag niet afhangen van
     163      // wat er toevallig in de kolom stond.
     164      release_date: p.kind === 'playlist' ? '' : (p.release_date || ''),
     165      mb_release_id: p.kind === 'playlist' ? '' : (p.mb_release_id || ''),
    127166      tracks: mappedTracks,
    128167    };
     
    141180    const kind = data.kind === 'playlist' ? 'playlist' : 'album';
    142181
     182    // Alleen een UITGAVE draagt deze twee. Een afspeellijst heeft geen
     183    // uitgavedatum en geen release-id, en dat onderscheid is precies wat de
     184    // keuze album/playlist betekent (shaer-cyg). Het afdwingen gebeurt HIER en
     185    // niet alleen in het scherm: een scherm kun je omzeilen -- de API ligt open
     186    // voor de post-editor -- en dan staat er stille rommel op een mixtape die
     187    // later als Album de deur uit gaat.
     188    const uitgave = kind === 'album';
     189    const releaseDate = uitgave ? normDatum(data.release_date) : null;
     190    const mbRelease = uitgave ? normMbid(data.mb_release_id) : null;
     191
    143192    const tx = db.transaction(() => {
    144193      db.prepare(`
    145         INSERT INTO playlists (id, site_id, title, artist, year, cover_url, kind, created_at, updated_at)
    146         VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
     194        INSERT INTO playlists (id, site_id, title, artist, year, cover_url, kind, release_date, mb_release_id, created_at, updated_at)
     195        VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
    147196      `).run(
    148197        id, siteId, title,
     
    150199        Number.isFinite(+data.year) && +data.year > 0 ? +data.year : null,
    151200        String(data.cover || '').trim() || null,
    152         kind, now, now,
     201        kind, releaseDate, mbRelease, now, now,
    153202      );
    154203      this._writeTracks(id, siteId, data.tracks);
     
    171220    if (!id) return false;
    172221    const existing = db.prepare(
    173       'SELECT id FROM playlists WHERE site_id = ? AND id = ?'
     222      'SELECT id, kind FROM playlists WHERE site_id = ? AND id = ?'
    174223    ).get(siteId, id);
    175224    if (!existing) return false;
     
    177226    const fields = [];
    178227    const values = [];
     228    // Wat wordt het NA deze wijziging? `kind` hoeft niet in data te staan, dus
     229    // val terug op wat er ligt.
     230    const nieuwKind = Object.prototype.hasOwnProperty.call(data, 'kind')
     231      ? (data.kind === 'playlist' ? 'playlist' : 'album')
     232      : (existing.kind || 'album');
    179233    if (Object.prototype.hasOwnProperty.call(data, 'title')) {
    180234      const v = String(data.title || '').trim();
     
    193247    }
    194248    if (Object.prototype.hasOwnProperty.call(data, 'kind')) {
    195       fields.push('kind = ?'); values.push(data.kind === 'playlist' ? 'playlist' : 'album');
     249      fields.push('kind = ?'); values.push(nieuwKind);
     250    }
     251    // De uitgavevelden. Wordt dit een afspeellijst, dan gaan ze ALTIJD leeg --
     252    // ook als de aanroeper er niets over zei. Anders houdt een album dat je tot
     253    // mixtape ombouwt zijn uitgavedatum en zijn release-id, en die duiken dan
     254    // weer op zodra iemand hem terugzet. Een veld dat niet meer mag bestaan
     255    // hoort weg te zijn, niet te wachten.
     256    if (nieuwKind !== 'album') {
     257      fields.push('release_date = ?'); values.push(null);
     258      fields.push('mb_release_id = ?'); values.push(null);
     259    } else {
     260      if (Object.prototype.hasOwnProperty.call(data, 'release_date')) {
     261        fields.push('release_date = ?'); values.push(normDatum(data.release_date));
     262      }
     263      if (Object.prototype.hasOwnProperty.call(data, 'mb_release_id')) {
     264        fields.push('mb_release_id = ?'); values.push(normMbid(data.mb_release_id));
     265      }
    196266    }
    197267    fields.push('updated_at = ?'); values.push(new Date().toISOString());
Note: See TracChangeset for help on using the changeset viewer.