feat(web): DELETE /api/config?field= to clear a credential

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jonathan
2026-07-13 00:23:21 +02:00
parent e048c68f40
commit 789eb2c76a
2 changed files with 37 additions and 1 deletions
+27 -1
View File
@@ -1,5 +1,5 @@
import { describe, it, expect, beforeAll } from "vitest"; import { describe, it, expect, beforeAll } from "vitest";
import { GET, PUT } from "./route"; import { GET, PUT, DELETE } from "./route";
import { prisma } from "@/lib/db"; import { prisma } from "@/lib/db";
beforeAll(() => { beforeAll(() => {
@@ -14,6 +14,10 @@ function putReq(body: unknown) {
}); });
} }
function delReq(field: string) {
return new Request(`http://localhost/api/config?field=${field}`, { method: "DELETE" });
}
describe("config API", () => { describe("config API", () => {
it("stores plain fields verbatim and secret fields encrypted", async () => { it("stores plain fields verbatim and secret fields encrypted", async () => {
await PUT(putReq({ qobuzEmail: "me@example.com", qobuzPassword: "hunter2", slskdUrl: "http://slskd:5030", slskdApiKey: "abc" })); await PUT(putReq({ qobuzEmail: "me@example.com", qobuzPassword: "hunter2", slskdUrl: "http://slskd:5030", slskdApiKey: "abc" }));
@@ -59,3 +63,25 @@ describe("config API", () => {
expect(pw).not.toBeNull(); // still present, not wiped expect(pw).not.toBeNull(); // still present, not wiped
}); });
}); });
describe("config DELETE", () => {
it("removes a stored secret by field name", async () => {
await PUT(putReq({ qobuzPassword: "hunter2" }));
expect(await prisma.config.findUnique({ where: { key: "qobuz.password" } })).not.toBeNull();
const res = await DELETE(delReq("qobuzPassword"));
expect(res.status).toBe(200);
expect(await prisma.config.findUnique({ where: { key: "qobuz.password" } })).toBeNull();
// GET now reports it unset
expect((await (await GET()).json()).qobuzPasswordSet).toBe(false);
});
it("rejects an unknown field with 400 and deletes nothing", async () => {
const res = await DELETE(delReq("monitor.enabled"));
expect(res.status).toBe(400);
});
it("is idempotent — deleting an absent field returns 200", async () => {
const res = await DELETE(delReq("slskdApiKey"));
expect(res.status).toBe(200);
});
});
+10
View File
@@ -45,3 +45,13 @@ export async function GET() {
slskdApiKeySet: byKey.has("slskd.api_key"), slskdApiKeySet: byKey.has("slskd.api_key"),
}); });
} }
export async function DELETE(request: Request) {
const field = new URL(request.url).searchParams.get("field") ?? "";
const entry = FIELDS[field];
if (!entry) {
return Response.json({ error: "unknown field" }, { status: 400 });
}
await prisma.config.deleteMany({ where: { key: entry.key } }); // idempotent
return Response.json({ ok: true });
}