feat: Zeitleiste "Neu von deinen Künstlern" (Web) + Backend-Endpunkt

Eigener Punkt in der Seitenleiste: jüngste Veröffentlichungen aller
gefolgten Künstler, nach Monat gruppiert, neueste zuerst, umschaltbar auf
30/90/365 Tage. Was schon in der Bibliothek liegt, ist markiert (Vergleich
über Künstler + Albumname, akzent- und zeichenunabhängig); alles andere
bekommt einen Laden-Knopf zum vertrauten Download-Dialog.

Neuer Endpunkt POST /api/artist-releases (backend/lib/releases.js): die
Deezer-Zuordnung passiert serverseitig, weil die Künstler-Pins dort
ohnehin liegen. Gepinnte nutzen die feste ID, ungepinnte werden über den
Namen gesucht und nach Fan-Zahl sortiert; ohne Treffer kommt der Name als
ohneZuordnung zurück und die Oberfläche rät zum Pinnen.

Cache im Backend statt im Client — bei 30 gefolgten Künstlern wären das
sonst 30 Deezer-Aufrufe pro Gerät und App-Start. 6 h für Alben, 24 h für
die Namenszuordnung, max. 4 Anfragen parallel, bei Deezer-Fehlern wird
der abgelaufene Stand ausgeliefert statt einer leeren Liste.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
marcandClaude Opus 5 committed 2026-09-18 13:50:33 +02:00
1 parent 19f241e493
commit 908488a337
8 files changed
+345 -2

No files matched your search

+30
View File
@@ -266,6 +266,36 @@ app.post('/api/artist-overrides', (req, res) => {
res.json({ ok: true });
});
// ─── NEUE VERÖFFENTLICHUNGEN GEFOLGTER KÜNSTLER ─────────────────────────────
// POST /api/artist-releases { artists: [{id, name}], days }
// Die Deezer-Zuordnung passiert HIER, weil die Pins ohnehin serverseitig liegen — der Client
// schickt also nur seine Navidrome-Künstler und muss nichts über Deezer wissen.
app.post('/api/artist-releases', async (req, res) => {
const artists = Array.isArray(req.body?.artists) ? req.body.artists : [];
const days = Math.min(Math.max(parseInt(req.body?.days, 10) || 90, 1), 365);
if (!artists.length) return res.json({ releases: [], ohneZuordnung: [] });
// Obergrenze, damit ein kaputter Client nicht hunderte Deezer-Aufrufe auslöst.
if (artists.length > 300) return res.status(400).json({ error: 'Zu viele Künstler (max. 300)' });
const pins = _loadArtistOverrides();
const mitPin = artists
.filter(a => a && a.id && a.name)
.map(a => ({
id: String(a.id),
name: String(a.name),
deezerArtistId: pins[String(a.id)]?.deezerArtistId || null,
}));
try {
const { neueReleases } = require('./lib/releases');
const ergebnis = await neueReleases(mitPin, days);
res.json(ergebnis);
} catch (e) {
console.error('[releases]', e.message);
res.status(502).json({ error: e.message });
}
});
// DELETE /api/artist-overrides/:ndArtistId — remove a pin
app.delete('/api/artist-overrides/:ndArtistId', (req, res) => {
const id = decodeURIComponent(req.params.ndArtistId);