feat(library): bulk operations (delete / ignore)
Add a Select mode on /library: album cards become multi-select, with a bulk
bar to Delete selected (2-step confirm) or Ignore selected. POST
/api/library/bulk { action, ids } applies the op per id and reports ok/failed
counts.
Extracted the delete + ignore logic into lib/library-ops.ts
(removeLibraryItem, ignoreLibraryItem); the single DELETE route now reuses
removeLibraryItem. web 185 tests.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
import { rm, rmdir } from "node:fs/promises";
|
||||
import { dirname, resolve, sep } from "node:path";
|
||||
import { prisma } from "@/lib/db";
|
||||
|
||||
export type OpResult = { ok: boolean; status: number; error?: string };
|
||||
|
||||
/** Delete an owned album: remove its folder on disk (guarded to inside the library root),
|
||||
* drop the LibraryItem, and stop monitoring the matching release so auto-monitor won't
|
||||
* immediately re-grab it. Shared by the single DELETE route and bulk operations. */
|
||||
export async function removeLibraryItem(id: string): Promise<OpResult> {
|
||||
const item = await prisma.libraryItem.findUnique({ where: { id } });
|
||||
if (!item) return { ok: false, status: 404, error: "not found" };
|
||||
|
||||
const root = resolve(process.env.LYRA_LIBRARY_ROOT ?? "/music");
|
||||
const target = resolve(item.path);
|
||||
if (target === root || !target.startsWith(root + sep)) {
|
||||
return { ok: false, status: 400, error: "refusing to delete a path outside the library" };
|
||||
}
|
||||
|
||||
try {
|
||||
await rm(target, { recursive: true, force: true });
|
||||
const parent = dirname(target);
|
||||
if (parent !== root) await rmdir(parent).catch(() => {}); // prune emptied artist folder
|
||||
} catch {
|
||||
return { ok: false, status: 500, error: "failed to delete files on disk" };
|
||||
}
|
||||
|
||||
await prisma.$transaction([
|
||||
prisma.libraryItem.delete({ where: { id } }),
|
||||
prisma.monitoredRelease.updateMany({
|
||||
where: { artistName: item.artist, album: item.album },
|
||||
data: { monitored: false, currentQualityClass: null },
|
||||
}),
|
||||
]);
|
||||
return { ok: true, status: 200 };
|
||||
}
|
||||
|
||||
/** Mark the release(s) matching an owned album do-not-monitor, so it drops off Wanted and is
|
||||
* never enqueued. No-op (still ok) if the album has no matching release. */
|
||||
export async function ignoreLibraryItem(id: string): Promise<OpResult> {
|
||||
const item = await prisma.libraryItem.findUnique({ where: { id } });
|
||||
if (!item) return { ok: false, status: 404, error: "not found" };
|
||||
await prisma.monitoredRelease.updateMany({
|
||||
where: item.rgMbid
|
||||
? { rgMbid: item.rgMbid }
|
||||
: { artistName: item.artist, album: item.album },
|
||||
data: { ignored: true },
|
||||
});
|
||||
return { ok: true, status: 200 };
|
||||
}
|
||||
Reference in New Issue
Block a user