summaryrefslogtreecommitdiff
path: root/scripts/add.ts
blob: 947fd284a59f1918b43539ce82ccdffe41bcfe73 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
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",
 *       "members": [{ "name": "...", "role": "Vocal", "since": "2020", "until": "", "note": "" }],
 *       "links": [{ "label": "Twitter", "url": "..." }]
 *     }]
 *   }
 *
 * Artist references in members 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, type MemberInput } from "../app/lib/db.server";

type LinkInput = { label: string; url: string };
type MemberRef = { id?: string; name?: string; role?: string | null; since?: string; until?: string; note?: string };

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[]) : [],
      members: flags.members ? (JSON.parse(flags.members) as MemberInput[]) : [],
      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[]; members?: MemberRef[]; 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 members = (b.members ?? [])
      .map((mr) => {
        const artistId = mr.id ?? (mr.name ? nameToId.get(mr.name) : undefined);
        if (!artistId) { console.warn(`  Warning: artist not found: "${mr.name}"`); return null; }
        return { artist_id: artistId, role: mr.role ?? null, since: mr.since ?? "", until: mr.until ?? "", note: mr.note ?? "" };
      })
      .filter((x): x is MemberInput => 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 || [],
        members,
        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);
}