summaryrefslogtreecommitdiff
path: root/app/lib/db.server.ts
diff options
context:
space:
mode:
authoryyamashita <yyamashita@hetzner.yyamashita.com>2026-08-15 18:57:59 +0900
committeryyamashita <yyamashita@hetzner.yyamashita.com>2026-08-15 18:57:59 +0900
commit59a9f4984c3acd29bc746a78e30849f50fa555ee (patch)
treeb9dab024c0c5d1d68e023b8f49eb0716b97965bd /app/lib/db.server.ts
parenta100fb28f92fa7c5d2cf701d118c4c7b898c9bf2 (diff)
Add Claude-powered band auto-registration from live info text
Extracts band/artist names from pasted live info via Claude (API or CLI adapter), matches against existing bands, and lets the user review and bulk-register new ones through a polling job queue. Also adds a livehouse events import script and recent-bands/artists home page sections. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Diffstat (limited to 'app/lib/db.server.ts')
-rw-r--r--app/lib/db.server.ts79
1 files changed, 79 insertions, 0 deletions
diff --git a/app/lib/db.server.ts b/app/lib/db.server.ts
index c4b11b4..37d819b 100644
--- a/app/lib/db.server.ts
+++ b/app/lib/db.server.ts
@@ -142,7 +142,22 @@ function initSchema(db: Database.Database) {
CREATE INDEX IF NOT EXISTS idx_artist_revisions_artist_id ON artist_revisions(artist_id);
CREATE INDEX IF NOT EXISTS idx_list_entries_list_id ON list_entries(list_id);
CREATE INDEX IF NOT EXISTS idx_list_revisions_list_id ON list_revisions(list_id);
+
+ CREATE TABLE IF NOT EXISTS parse_jobs (
+ id TEXT PRIMARY KEY,
+ status TEXT NOT NULL DEFAULT 'pending',
+ input_text TEXT NOT NULL,
+ result TEXT,
+ error_message TEXT,
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
+ );
+
+ CREATE INDEX IF NOT EXISTS idx_parse_jobs_status ON parse_jobs(status, created_at);
`);
+
+ // reset stuck jobs from previous server run
+ db.prepare("UPDATE parse_jobs SET status = 'pending', updated_at = datetime('now') WHERE status = 'processing'").run();
}
// ── Interfaces ────────────────────────────────────────────────────────────────
@@ -330,6 +345,14 @@ export function listBands(): Band[] {
return getDb().prepare("SELECT * FROM bands ORDER BY slug").all() as Band[];
}
+export function listRecentBands(limit = 8): Band[] {
+ return getDb().prepare("SELECT * FROM bands ORDER BY created_at DESC LIMIT ?").all(limit) as Band[];
+}
+
+export function listRecentArtists(limit = 8): Artist[] {
+ return getDb().prepare("SELECT * FROM artists ORDER BY created_at DESC LIMIT ?").all(limit) as Artist[];
+}
+
export function getBandById(id: string): Band | null {
return getDb().prepare("SELECT * FROM bands WHERE id = ?").get(id) as Band | null;
}
@@ -796,3 +819,59 @@ export function importDb(data: DbExport): ImportResult {
};
})() as ImportResult;
}
+
+// ── Parse job queue ───────────────────────────────────────────────────────────
+
+export interface ParseJob {
+ id: string;
+ status: "pending" | "processing" | "done" | "error";
+ input_text: string;
+ result: string | null;
+ error_message: string | null;
+ created_at: string;
+ updated_at: string;
+}
+
+export function createParseJob(inputText: string): ParseJob {
+ const id = crypto.randomUUID();
+ getDb().prepare(
+ "INSERT INTO parse_jobs (id, input_text) VALUES (?, ?)"
+ ).run(id, inputText);
+ return getDb().prepare("SELECT * FROM parse_jobs WHERE id = ?").get(id) as ParseJob;
+}
+
+export function getParseJob(id: string): ParseJob | null {
+ return getDb().prepare("SELECT * FROM parse_jobs WHERE id = ?").get(id) as ParseJob | null;
+}
+
+export function listRecentParseJobs(limit = 20): ParseJob[] {
+ return getDb()
+ .prepare("SELECT * FROM parse_jobs ORDER BY created_at DESC LIMIT ?")
+ .all(limit) as ParseJob[];
+}
+
+export function claimNextParseJob(): ParseJob | null {
+ const db = getDb();
+ return db.transaction(() => {
+ const job = db.prepare(
+ "SELECT * FROM parse_jobs WHERE status = 'pending' ORDER BY created_at LIMIT 1"
+ ).get() as ParseJob | null;
+ if (!job) return null;
+ db.prepare(
+ "UPDATE parse_jobs SET status = 'processing', updated_at = datetime('now') WHERE id = ?"
+ ).run(job.id);
+ return { ...job, status: "processing" as const };
+ })();
+}
+
+export function completeParseJob(id: string, result: string): void {
+ getDb().prepare(
+ "UPDATE parse_jobs SET status = 'done', result = ?, updated_at = datetime('now') WHERE id = ?"
+ ).run(result, id);
+}
+
+export function failParseJob(id: string, errorMessage: string): void {
+ getDb().prepare(
+ "UPDATE parse_jobs SET status = 'error', error_message = ?, updated_at = datetime('now') WHERE id = ?"
+ ).run(errorMessage, id);
+}