Changeset d8f7d9c in Klonkt


Ignore:
Timestamp:
08/16/2026 01:44:46 PM (3 weeks ago)
Author:
Robin <roboburr@…>
Branches:
main
Children:
d68ee3a
Parents:
21257e4
Message:

Playlists: uitgavedatum en MusicBrainz release-id op een album

Stap 1 van shaer-756s. Het album bestaat al als ding -- een playlist met
kind='album', met eigen id, titel, artiest, jaar, hoes en een eigen
AP-collectie. Wat ontbrak om het als Album uit te geven zijn twee velden.

release_date en niet year: die kolom blijft en wordt gewoon getoond,
maar hun AlbumSerializer leest released als een DateField. Een jaartal
als 2024-01-01 versturen is een dag verzinnen, en dat is precies wat we
bij artiesten en albums niet doen. Volledige datum of niets -- ook
2024-02-31 valt af, want Date rolt die stil door naar 2 maart en dan
bewaren we iets anders dan er ingetypt is.

mb_release_id is de tegenhanger van sites.mb_artist_id: dezelfde
verwijzing naar MusicBrainz, een niveau lager.

ALLEEN BIJ kind='album', en dat wordt in PlaylistService afgedwongen en
niet alleen in het scherm. De API ligt open -- de post-editor gebruikt
hem ook -- en een scherm is geen bewaking. Omschakelen naar afspeellijst
maakt de velden ALTIJD leeg, ook als de aanroeper er niets over zei:
anders houdt een album dat je tot mixtape ombouwt zijn uitgavedatum, en
duikt die weer op zodra iemand hem terugzet. Het leespad geeft ze ook
niet door bij een afspeellijst, zodat een oude rij niet alsnog lekt.

In de editor staan de velden onder Type en verdwijnen ze als je naar
playlist schakelt. Daar hoorde een CSS-regel bij: .pl-field is display:
flex en dat wint van het hidden-attribuut -- zonder .pl-field[hidden]
{ display: none } bleven ze gewoon staan.

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

Files:
1 added
4 edited

Legend:

Unmodified
Added
Removed
  • src/assets/js/mod/playlist-editor.js

    r21257e4 rd8f7d9c  
    6363
    6464    // Existing playlist data when editing
    65     let initial = { title: '', artist: '', year: '', cover: '', kind: 'album', track_ids: [] };
     65    let initial = { title: '', artist: '', year: '', cover: '', kind: 'album', release_date: '', mb_release_id: '', track_ids: [] };
    6666    if (isEdit && opts.id) {
    6767      try {
     
    102102                    <option value="playlist" ${initial.kind === 'playlist' ? 'selected' : ''}>📃 Playlist (track-covers)</option>
    103103                  </select>
     104                </label>
     105                <label class="pl-field pl-uitgave">
     106                  <span>Uitgavedatum</span>
     107                  <input type="date" id="pli-release-date" value="${esc(initial.release_date || '')}">
     108                </label>
     109                <label class="pl-field pl-uitgave">
     110                  <span>MusicBrainz release-id</span>
     111                  <input type="text" id="pli-mb-release" maxlength="36" spellcheck="false"
     112                         placeholder="00000000-0000-0000-0000-000000000000"
     113                         value="${esc(initial.mb_release_id || '')}">
    104114                </label>
    105115                <div class="pl-field pl-field-full">
     
    157167    const yearEl = $('#pli-year');
    158168    const kindEl = $('#pli-kind');
     169    const releaseEl = $('#pli-release-date');
     170    const mbReleaseEl = $('#pli-mb-release');
    159171    const coverEl = $('#pli-cover');
     172
     173    // Uitgavedatum en release-id horen bij een ALBUM, niet bij een
     174    // afspeellijst -- dat is wat de keuze album/playlist betekent. Ze
     175    // verdwijnen dus als je omschakelt, en de server maakt ze dan ook leeg;
     176    // dit scherm is de uitleg, niet de bewaking.
     177    const toonUitgave = () => {
     178      const album = kindEl.value !== 'playlist';
     179      for (const el of backdrop.querySelectorAll('.pl-uitgave')) el.hidden = !album;
     180    };
     181    kindEl.addEventListener('change', toonUitgave);
     182    toonUitgave();
    160183    const coverThumb = $('#pli-cover-thumb');
    161184    const saveBtn = $('#pli-save');
     
    376399        cover:  coverEl.value.trim(),
    377400        kind:   kindEl.value === 'playlist' ? 'playlist' : 'album',
     401        release_date:  releaseEl.value.trim(),
     402        mb_release_id: mbReleaseEl.value.trim(),
    378403        tracks: selected.slice(),
    379404      };
  • src/config/database.js

    r21257e4 rd8f7d9c  
    140140  ensureColumn('sites', 'mb_artist_id', 'TEXT');
    141141  ensureColumn('sites', 'mb_artist_name', 'TEXT');
     142  // Een UITGAVE heeft twee dingen die een afspeellijst niet heeft (shaer-756s).
     143  //
     144  // release_date en niet `year`: die kolom bestaat al en blijft, maar hun
     145  // AlbumSerializer leest `released` als een DateField. Een jaartal als
     146  // 2024-01-01 versturen is een dag verzinnen, en dat is precies wat we bij
     147  // artiesten en albums niet doen. Volledige datum of niets.
     148  //
     149  // mb_release_id is de tegenhanger van sites.mb_artist_id: dezelfde soort
     150  // verwijzing naar MusicBrainz, een niveau lager.
     151  //
     152  // Ze horen ALLEEN bij kind='album'. PlaylistService dwingt dat af bij het
     153  // opslaan -- zie daar waarom dat niet alleen in het scherm mag zitten.
     154  ensureColumn('playlists', 'release_date', 'TEXT');
     155  ensureColumn('playlists', 'mb_release_id', 'TEXT');
    142156  ensureColumn('audio_tracks', 'cover_url', 'TEXT');
    143157  ensureColumn('audio_tracks', 'album', 'TEXT');
  • src/services/PlaylistService.js

    r21257e4 rd8f7d9c  
    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      // Leeg als het een afspeellijst is -- de opslag houdt ze daar al leeg,
     158      // maar dit is de plek waar de editor leest en die mag niet afhangen van
     159      // wat er toevallig in de kolom stond.
     160      release_date: p.kind === 'playlist' ? '' : (p.release_date || ''),
     161      mb_release_id: p.kind === 'playlist' ? '' : (p.mb_release_id || ''),
    127162      tracks: mappedTracks,
    128163    };
     
    141176    const kind = data.kind === 'playlist' ? 'playlist' : 'album';
    142177
     178    // Alleen een UITGAVE draagt deze twee. Een afspeellijst heeft geen
     179    // uitgavedatum en geen release-id, en dat onderscheid is precies wat de
     180    // keuze album/playlist betekent (shaer-cyg). Het afdwingen gebeurt HIER en
     181    // niet alleen in het scherm: een scherm kun je omzeilen -- de API ligt open
     182    // voor de post-editor -- en dan staat er stille rommel op een mixtape die
     183    // later als Album de deur uit gaat.
     184    const uitgave = kind === 'album';
     185    const releaseDate = uitgave ? normDatum(data.release_date) : null;
     186    const mbRelease = uitgave ? normMbid(data.mb_release_id) : null;
     187
    143188    const tx = db.transaction(() => {
    144189      db.prepare(`
    145         INSERT INTO playlists (id, site_id, title, artist, year, cover_url, kind, created_at, updated_at)
    146         VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
     190        INSERT INTO playlists (id, site_id, title, artist, year, cover_url, kind, release_date, mb_release_id, created_at, updated_at)
     191        VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
    147192      `).run(
    148193        id, siteId, title,
     
    150195        Number.isFinite(+data.year) && +data.year > 0 ? +data.year : null,
    151196        String(data.cover || '').trim() || null,
    152         kind, now, now,
     197        kind, releaseDate, mbRelease, now, now,
    153198      );
    154199      this._writeTracks(id, siteId, data.tracks);
     
    171216    if (!id) return false;
    172217    const existing = db.prepare(
    173       'SELECT id FROM playlists WHERE site_id = ? AND id = ?'
     218      'SELECT id, kind FROM playlists WHERE site_id = ? AND id = ?'
    174219    ).get(siteId, id);
    175220    if (!existing) return false;
     
    177222    const fields = [];
    178223    const values = [];
     224    // Wat wordt het NA deze wijziging? `kind` hoeft niet in data te staan, dus
     225    // val terug op wat er ligt.
     226    const nieuwKind = Object.prototype.hasOwnProperty.call(data, 'kind')
     227      ? (data.kind === 'playlist' ? 'playlist' : 'album')
     228      : (existing.kind || 'album');
    179229    if (Object.prototype.hasOwnProperty.call(data, 'title')) {
    180230      const v = String(data.title || '').trim();
     
    193243    }
    194244    if (Object.prototype.hasOwnProperty.call(data, 'kind')) {
    195       fields.push('kind = ?'); values.push(data.kind === 'playlist' ? 'playlist' : 'album');
     245      fields.push('kind = ?'); values.push(nieuwKind);
     246    }
     247    // De uitgavevelden. Wordt dit een afspeellijst, dan gaan ze ALTIJD leeg --
     248    // ook als de aanroeper er niets over zei. Anders houdt een album dat je tot
     249    // mixtape ombouwt zijn uitgavedatum en zijn release-id, en die duiken dan
     250    // weer op zodra iemand hem terugzet. Een veld dat niet meer mag bestaan
     251    // hoort weg te zijn, niet te wachten.
     252    if (nieuwKind !== 'album') {
     253      fields.push('release_date = ?'); values.push(null);
     254      fields.push('mb_release_id = ?'); values.push(null);
     255    } else {
     256      if (Object.prototype.hasOwnProperty.call(data, 'release_date')) {
     257        fields.push('release_date = ?'); values.push(normDatum(data.release_date));
     258      }
     259      if (Object.prototype.hasOwnProperty.call(data, 'mb_release_id')) {
     260        fields.push('mb_release_id = ?'); values.push(normMbid(data.mb_release_id));
     261      }
    196262    }
    197263    fields.push('updated_at = ?'); values.push(new Date().toISOString());
  • src/views/partials/playlist-editor.ejs

    r21257e4 rd8f7d9c  
    9999}
    100100.pl-field { display: flex; flex-direction: column; gap: .25rem; }
     101/* `display: flex` wint van het hidden-attribuut, dus zonder deze regel doet
     102   [hidden] op een .pl-field NIETS -- het veld blijft gewoon staan. De
     103   uitgavevelden schakelen daarop (album versus afspeellijst). */
     104.pl-field[hidden] { display: none; }
    101105.pl-field-full { grid-column: 1 / -1; }
    102106.pl-field > span {
Note: See TracChangeset for help on using the changeset viewer.