From cd8787b77dadf752826a967d404b718b3ec92601 Mon Sep 17 00:00:00 2001
From: yyamashita <yyamashita@mosquit.one>
Date: Sat, 9 May 2026 11:21:28 +0900
Subject: Add JSON API endpoints and CLI script for band/artist management

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---
 app/routes/api-bands.tsx | 51 ++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 51 insertions(+)
 create mode 100644 app/routes/api-bands.tsx

(limited to 'app/routes/api-bands.tsx')

diff --git a/app/routes/api-bands.tsx b/app/routes/api-bands.tsx
new file mode 100644
index 0000000..64a9269
--- /dev/null
+++ b/app/routes/api-bands.tsx
@@ -0,0 +1,51 @@
+import type { ActionFunctionArgs } from "react-router";
+import { createBand, getIpAddress, listBands, toSlug } from "~/lib/db.server";
+
+export function loader() {
+  return Response.json(listBands());
+}
+
+export async function action({ request }: ActionFunctionArgs) {
+  if (request.method !== "POST") {
+    return Response.json({ error: "Method not allowed" }, { status: 405 });
+  }
+
+  let body: Record<string, unknown>;
+  try {
+    body = await request.json();
+  } catch {
+    return Response.json({ error: "Invalid JSON body" }, { status: 400 });
+  }
+
+  const name = (body.name as string | undefined)?.trim();
+  if (!name) return Response.json({ error: "name is required" }, { status: 400 });
+
+  const slug = (body.slug as string | undefined)?.trim() || toSlug(name);
+  if (!slug) return Response.json({ error: "could not derive slug from name" }, { status: 400 });
+
+  const id = crypto.randomUUID();
+  try {
+    const band = createBand({
+      id,
+      slug,
+      name,
+      area: (body.area as string) || null,
+      description: (body.description as string) || null,
+      status: (body.status as string) || "active",
+      links: (body.links as { label: string; url: string }[]) || [],
+      artists: (body.artists as { id: string; role: string | null }[]) || [],
+      message: (body.message as string) || "API import",
+      ip_address: getIpAddress(request),
+    });
+    return Response.json(band, { status: 201 });
+  } catch (e) {
+    if (e instanceof Error && e.message.includes("UNIQUE constraint failed: bands.slug")) {
+      return Response.json({ error: "slug already in use" }, { status: 409 });
+    }
+    throw e;
+  }
+}
+
+export default function () {
+  return null;
+}
-- 
cgit v1.2.3

