feat(discover): full reset — clear + rebuild suggestions
POST /api/discover/reset sets discover.resetRequested; the worker, at the start of the next sweep, clears all PENDING suggestions + contributions + seed throttles and nulls WatchedArtist.lastDiscoveredAt (keeping followed/wanted/dismissed decisions), then regenerates everything with current logic. A 'Reset' button on /discover (2-step confirm) triggers it. worker 247, web 190 tests. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { prisma } from "@/lib/db";
|
||||
import { POST } from "./route";
|
||||
|
||||
describe("POST /api/discover/reset", () => {
|
||||
it("sets the reset + requested flags so the worker rebuilds on the next sweep", async () => {
|
||||
const res = await POST();
|
||||
expect(res.status).toBe(202);
|
||||
expect((await res.json()).reset).toBe(true);
|
||||
const cfg = Object.fromEntries(
|
||||
(await prisma.config.findMany({ where: { key: { in: ["discover.resetRequested", "discover.requested"] } } }))
|
||||
.map((c) => [c.key, c.value]),
|
||||
);
|
||||
expect(cfg["discover.resetRequested"]).toBe("true");
|
||||
expect(cfg["discover.requested"]).toBe("true");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
import { prisma } from "@/lib/db";
|
||||
|
||||
// POST /api/discover/reset — request a full rebuild: the worker clears the pending
|
||||
// suggestions, contributions, and seed throttles at the start of the next sweep, then
|
||||
// regenerates everything with the current logic. Kicks a sweep immediately.
|
||||
export async function POST() {
|
||||
await Promise.all(
|
||||
["discover.resetRequested", "discover.requested"].map((key) =>
|
||||
prisma.config.upsert({
|
||||
where: { key },
|
||||
create: { key, value: "true", secret: false },
|
||||
update: { value: "true" },
|
||||
}),
|
||||
),
|
||||
);
|
||||
return Response.json({ reset: true }, { status: 202 });
|
||||
}
|
||||
@@ -69,6 +69,7 @@ export function DiscoverClient() {
|
||||
const [query, setQuery] = useState("");
|
||||
const [search, setSearch] = useState<SearchResult | null>(null);
|
||||
const [busy, setBusy] = useState(false);
|
||||
const [confirmReset, setConfirmReset] = useState(false);
|
||||
const [followedSimilar, setFollowedSimilar] = useState<Set<string>>(new Set());
|
||||
const [albumModal, setAlbumModal] = useState<{ a: Album; artistName: string; artistMbid: string } | null>(null);
|
||||
|
||||
@@ -106,6 +107,18 @@ export function DiscoverClient() {
|
||||
setRunning(true);
|
||||
}
|
||||
|
||||
async function resetDiscover() {
|
||||
setConfirmReset(false);
|
||||
const res = await fetch("/api/discover/reset", { method: "POST" }).catch(() => null);
|
||||
if (res && (res.ok || res.status === 202)) {
|
||||
setRows([]); // clears immediately; the rebuild repopulates
|
||||
setRunning(true);
|
||||
toast("Rebuilding suggestions from scratch…");
|
||||
} else {
|
||||
toast("Couldn't reset");
|
||||
}
|
||||
}
|
||||
|
||||
async function runSearch(e: React.FormEvent) {
|
||||
e.preventDefault();
|
||||
if (!query.trim()) return;
|
||||
@@ -169,13 +182,26 @@ export function DiscoverClient() {
|
||||
<button className="btn accent" onClick={discoverNow} disabled={running}>
|
||||
{running ? "Discovering…" : "Discover now"}
|
||||
</button>
|
||||
<span className="rmeta">
|
||||
{running
|
||||
? "Sweeping your followed artists for new suggestions…"
|
||||
: lastRunAt
|
||||
? `Last discovered ${agoLabel(lastRunAt)}${result ? ` · ${result}` : ""}`
|
||||
: "Not yet run — press Discover now to sweep your followed artists"}
|
||||
</span>
|
||||
{confirmReset ? (
|
||||
<>
|
||||
<span className="rmeta">Clear all suggestions and rebuild from scratch?</span>
|
||||
<button className="btn sm accent" onClick={resetDiscover}>Reset</button>
|
||||
<button className="btn sm ghost" onClick={() => setConfirmReset(false)}>Cancel</button>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<button className="btn sm ghost" onClick={() => setConfirmReset(true)} disabled={running}>
|
||||
Reset
|
||||
</button>
|
||||
<span className="rmeta">
|
||||
{running
|
||||
? "Sweeping your followed artists for new suggestions…"
|
||||
: lastRunAt
|
||||
? `Last discovered ${agoLabel(lastRunAt)}${result ? ` · ${result}` : ""}`
|
||||
: "Not yet run — press Discover now to sweep your followed artists"}
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<SectionHeader title="Find similar" />
|
||||
|
||||
Reference in New Issue
Block a user