Files
Lyra/web/src/app/api/discover/config/route.ts
T
Jonathan e04c1c9795 feat(discover): also seed discovery from owned-album artists
Discovery seeded only from followed artists. Now it also seeds from artists
you own albums by (LibraryItem.artistMbid, captured since the MBID own-state
work) but don't follow — so your library shapes suggestions, not just your
follows.

- New DiscoverySeed throttle table (migration add_discovery_seed) mirroring
  WatchedArtist.lastDiscoveredAt, so each library-derived seed is swept once
  per interval (WatchedArtist seeds keep their own throttle).
- _seed_artists merges both seed sources least-recently-swept first, capped at
  chunk_size; run_discovery stamps the right throttle per seed and evicts
  contributions from seeds that are neither followed nor owned.
- Artists you already have (followed OR owned) are excluded from the suggestion
  candidates — discovery surfaces only genuinely new artists.
- discover.seedFromLibrary config (default true) + a Discovery settings toggle.

NOTE: existing LibraryItem rows have null artistMbid (populated on new
import/scan), so a re-scan is needed before old albums seed discovery.
worker 236, web 187 tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 22:39:52 +02:00

42 lines
1.3 KiB
TypeScript

import { prisma } from "@/lib/db";
const DEFAULTS: Record<string, string> = {
"discover.enabled": "false",
"discover.intervalHours": "168",
"discover.similarPerSeed": "20",
"discover.albumsPerArtist": "1",
"discover.albumsOnly": "true",
"discover.seedFromLibrary": "true",
"discover.minScore": "0",
"discover.chunkSize": "5",
"discover.listenBrainzUrl": "",
};
export async function GET() {
const rows = await prisma.config.findMany({ where: { key: { in: Object.keys(DEFAULTS) } } });
const stored = Object.fromEntries(rows.map((r) => [r.key, r.value]));
return Response.json(
Object.fromEntries(Object.entries(DEFAULTS).map(([k, d]) => [k, stored[k] ?? d])),
);
}
export async function PATCH(request: Request) {
let body: unknown;
try {
body = await request.json();
} catch {
return Response.json({ error: "invalid JSON" }, { status: 400 });
}
const entries = Object.entries((body ?? {}) as Record<string, unknown>).filter(
([k]) => Object.prototype.hasOwnProperty.call(DEFAULTS, k),
);
for (const [key, value] of entries) {
await prisma.config.upsert({
where: { key },
create: { key, value: String(value), secret: false },
update: { value: String(value) },
});
}
return Response.json({ updated: entries.length });
}