diff options
| author | yyamashita <yyamashita@mosquit.one> | 2026-05-09 11:21:28 +0900 |
|---|---|---|
| committer | yyamashita <yyamashita@mosquit.one> | 2026-05-09 11:21:28 +0900 |
| commit | cd8787b77dadf752826a967d404b718b3ec92601 (patch) | |
| tree | a1e5471ba59404caf5c3c7684a4cfc08027a5a4b /scripts | |
| parent | 08c410c28eeb3d7a4c41014d8926b765441546c4 (diff) | |
Add JSON API endpoints and CLI script for band/artist management
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'scripts')
| -rw-r--r-- | scripts/add.ts | 159 |
1 files changed, 159 insertions, 0 deletions
diff --git a/scripts/add.ts b/scripts/add.ts new file mode 100644 index 0000000..ca53dda --- /dev/null +++ b/scripts/add.ts @@ -0,0 +1,159 @@ +#!/usr/bin/env npx tsx +/** + * CLI for adding bands/artists directly to the DB (no server required). + * + * Usage: + * npx tsx scripts/add.ts band --name "Name" [--slug x] [--area X] [--status active|hiatus|disbanded] [--description X] [--message X] + * npx tsx scripts/add.ts artist --name "Name" [--slug x] [--message X] + * npx tsx scripts/add.ts import --file data.json + * + * JSON import format (data.json): + * { + * "artists": [{ "name": "...", "links": [{ "label": "Twitter", "url": "..." }] }], + * "bands": [{ + * "name": "...", "area": "...", "status": "active", + * "artists": [{ "name": "...", "role": "Vocal" }], + * "links": [{ "label": "Twitter", "url": "..." }] + * }] + * } + * + * Artist references in bands can use { "id": "<uuid>" } or { "name": "..." } (looked up by name). + * Artists listed under "artists" are created first so band references by name work. + * Run from the project root so whois.db is found automatically. + */ + +import { readFileSync } from "fs"; +import { createArtist, createBand, listArtists, toSlug } from "../app/lib/db.server"; + +type LinkInput = { label: string; url: string }; +type ArtistRef = { id?: string; name?: string; role?: string | null }; + +function parseFlags(argv: string[]): Record<string, string> { + const flags: Record<string, string> = {}; + for (let i = 0; i < argv.length; i++) { + if (argv[i].startsWith("--")) { + const key = argv[i].slice(2); + flags[key] = argv[i + 1] ?? "true"; + i++; + } + } + return flags; +} + +const [, , command, ...rest] = process.argv; + +if (!command || command === "--help" || command === "-h") { + console.log(`Usage: + npx tsx scripts/add.ts band --name "Name" [--slug x] [--area X] [--status active|hiatus|disbanded] [--description X] [--message X] + npx tsx scripts/add.ts artist --name "Name" [--slug x] [--message X] + npx tsx scripts/add.ts import --file data.json`); + process.exit(0); +} + +if (command === "artist") { + const flags = parseFlags(rest); + const name = flags.name?.trim(); + if (!name) { console.error("Error: --name is required"); process.exit(1); } + const slug = flags.slug?.trim() || toSlug(name); + const id = crypto.randomUUID(); + const artist = createArtist({ + id, slug, name, + links: flags.links ? (JSON.parse(flags.links) as LinkInput[]) : [], + message: flags.message || "CLI import", + ip_address: "cli", + }); + console.log(`Created artist: ${artist.name}`); + console.log(` id: ${artist.id}`); + console.log(` url: /artists/of/${artist.id}`); + +} else if (command === "band") { + const flags = parseFlags(rest); + const name = flags.name?.trim(); + if (!name) { console.error("Error: --name is required"); process.exit(1); } + const slug = flags.slug?.trim() || toSlug(name); + const id = crypto.randomUUID(); + try { + const band = createBand({ + id, slug, name, + area: flags.area || null, + description: flags.description || null, + status: flags.status || "active", + links: flags.links ? (JSON.parse(flags.links) as LinkInput[]) : [], + artists: flags.artists ? (JSON.parse(flags.artists) as { id: string; role: string | null }[]) : [], + message: flags.message || "CLI import", + ip_address: "cli", + }); + console.log(`Created band: ${band.name}`); + console.log(` id: ${band.id}`); + console.log(` url: /bands/of/${band.id}`); + } catch (e) { + if (e instanceof Error && e.message.includes("UNIQUE constraint failed: bands.slug")) { + console.error(`Error: slug "${slug}" is already in use`); + process.exit(1); + } + throw e; + } + +} else if (command === "import") { + const flags = parseFlags(rest); + if (!flags.file) { console.error("Error: --file is required"); process.exit(1); } + + const data = JSON.parse(readFileSync(flags.file, "utf-8")) as { + artists?: { name: string; slug?: string; links?: LinkInput[]; message?: string }[]; + bands?: { name: string; slug?: string; area?: string; description?: string; status?: string; links?: LinkInput[]; artists?: ArtistRef[]; message?: string }[]; + }; + + // Build nameāid map from existing artists, then create new ones + const nameToId = new Map<string, string>(listArtists().map((a) => [a.name, a.id])); + + for (const a of data.artists ?? []) { + const name = a.name?.trim(); + if (!name) continue; + if (nameToId.has(name)) { console.log(`Skip existing artist: ${name}`); continue; } + const id = crypto.randomUUID(); + createArtist({ + id, slug: a.slug?.trim() || toSlug(name), name, + links: a.links || [], + message: a.message || "JSON import", + ip_address: "cli", + }); + nameToId.set(name, id); + console.log(`Created artist: ${name} (${id})`); + } + + for (const b of data.bands ?? []) { + const name = b.name?.trim(); + if (!name) continue; + const id = crypto.randomUUID(); + const artists = (b.artists ?? []) + .map((ar) => { + const artistId = ar.id ?? (ar.name ? nameToId.get(ar.name) : undefined); + if (!artistId) { console.warn(` Warning: artist not found: "${ar.name}"`); return null; } + return { id: artistId, role: ar.role ?? null }; + }) + .filter((x): x is { id: string; role: string | null } => x !== null); + try { + createBand({ + id, slug: b.slug?.trim() || toSlug(name), name, + area: b.area || null, + description: b.description || null, + status: b.status || "active", + links: b.links || [], + artists, + message: b.message || "JSON import", + ip_address: "cli", + }); + console.log(`Created band: ${name} (${id})`); + } catch (e) { + if (e instanceof Error && e.message.includes("UNIQUE constraint failed: bands.slug")) { + console.error(`Skip band "${name}": slug already in use`); + } else { + throw e; + } + } + } + +} else { + console.error(`Unknown command: "${command}". Use band, artist, or import.`); + process.exit(1); +} |
