feat(web): Library Management — manual album import (drag-and-drop)
Second Library Management feature. An "Add album" button on the Library page opens a modal: drag-drop (or pick) the audio files (+ optional cover image), enter artist/album/year, and Lyra writes the album into the library. - POST /api/library/import (auth-gated, multipart): sanitizes the folder name like the worker's safe_name, assembles into a sibling ".importing" temp dir then renames into place (never a half-written album), writes a cover.jpg from any image, and records a source="manual" LibraryItem with the on-disk trackNames + a rough quality class from the extension (a later Scan re-probes / MB-resolves for cover art + hi-res detection). Guards: 400 (missing artist/album or no audio), 409 (folder exists / already in library). - Library page: ImportAlbum drop-zone modal wired to refresh the grid. web 161 tests (write-to-disk + trackNames + guards + filename sanitization). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Also: DELETE now prunes an emptied artist folder (best-effort).
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { rm } from "node:fs/promises";
|
||||
import { resolve, sep } from "node:path";
|
||||
import { rm, rmdir } from "node:fs/promises";
|
||||
import { dirname, resolve, sep } from "node:path";
|
||||
import { prisma } from "@/lib/db";
|
||||
|
||||
// DELETE /api/library/[id] — remove an owned album: delete its folder on disk, drop the
|
||||
@@ -18,6 +18,9 @@ export async function DELETE(_request: Request, { params }: { params: Promise<{
|
||||
|
||||
try {
|
||||
await rm(target, { recursive: true, force: true });
|
||||
// prune the now-possibly-empty artist folder (best-effort; rmdir fails if it has other albums)
|
||||
const parent = dirname(target);
|
||||
if (parent !== root) await rmdir(parent).catch(() => {});
|
||||
} catch {
|
||||
return Response.json({ error: "failed to delete files on disk" }, { status: 500 });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user