diff --git a/scripts/init-db.ts b/scripts/init-db.ts index 8b07344..bd1b289 100644 --- a/scripts/init-db.ts +++ b/scripts/init-db.ts @@ -1,29 +1,101 @@ /** * Database initialization script - * Creates all required tables in PostgreSQL + * Creates database and all required tables in PostgreSQL */ import postgres from "postgres"; import { readFileSync } from "fs"; import { join } from "path"; +/** + * Parse DATABASE_URL and extract components + */ +function parseDatabaseUrl(url: string): { + protocol: string; + user: string; + password: string; + host: string; + port: string; + database: string; +} { + const match = url.match( + /^postgresql:\/\/([^:]+):([^@]+)@([^:]+):(\d+)\/(.+)$/ + ); + if (!match) { + throw new Error( + "Invalid DATABASE_URL format. Expected: postgresql://user:password@host:port/database" + ); + } + return { + protocol: "postgresql", + user: match[1], + password: match[2], + host: match[3], + port: match[4], + database: match[5], + }; +} + async function initDatabase() { const dbUrl = process.env.DATABASE_URL; - + if (!dbUrl) { console.error("Error: DATABASE_URL environment variable is required"); console.error("Please set it in your .env file or export it:"); - console.error(" export DATABASE_URL=postgresql://user:password@host:port/database"); + console.error( + " export DATABASE_URL=postgresql://user:password@host:port/database" + ); process.exit(1); } - console.log("Connecting to PostgreSQL..."); + const dbConfig = parseDatabaseUrl(dbUrl); + const targetDatabase = dbConfig.database; + + console.log(`Target database: ${targetDatabase}`); + console.log( + `Connecting to PostgreSQL server (${dbConfig.host}:${dbConfig.port})...` + ); + + // Connect to postgres database (default database) to create target database + const adminUrl = `postgresql://${dbConfig.user}:${dbConfig.password}@${dbConfig.host}:${dbConfig.port}/postgres`; + const adminSql = postgres(adminUrl); + + try { + // Check if database exists + const dbExists = await adminSql` + SELECT 1 FROM pg_database WHERE datname = ${targetDatabase} + `; + + if (dbExists.length === 0) { + console.log(`Database "${targetDatabase}" does not exist. Creating...`); + // Create database (escape database name to prevent SQL injection) + // PostgreSQL identifiers are case-insensitive unless quoted, so we quote it + const escapedDbName = `"${targetDatabase.replace(/"/g, '""')}"`; + await (adminSql as any).unsafe(`CREATE DATABASE ${escapedDbName}`); + console.log(`✓ Database "${targetDatabase}" created successfully`); + } else { + console.log(`✓ Database "${targetDatabase}" already exists`); + } + } catch (error) { + const errorMessage = (error as Error).message; + if (errorMessage.includes("already exists")) { + console.log(`✓ Database "${targetDatabase}" already exists`); + } else { + console.error(`✗ Failed to create database: ${errorMessage}`); + throw error; + } + } finally { + await adminSql.end(); + } + + // Now connect to target database and create tables + console.log(`\nConnecting to database "${targetDatabase}"...`); const sql = postgres(dbUrl); try { // Test connection await sql`SELECT 1`; - console.log("✓ Connected to PostgreSQL"); + console.log("✓ Connected to database"); // Read schema file const schemaPath = join(process.cwd(), "src", "storage", "schema.sql"); @@ -47,8 +119,13 @@ async function initDatabase() { } catch (error) { // Check if it's a "already exists" error (which is OK) const errorMessage = (error as Error).message; - if (errorMessage.includes("already exists") || errorMessage.includes("duplicate")) { - console.log(`⚠ Skipped (already exists): ${statement.substring(0, 50)}...`); + if ( + errorMessage.includes("already exists") || + errorMessage.includes("duplicate") + ) { + console.log( + `⚠ Skipped (already exists): ${statement.substring(0, 50)}...` + ); } else { console.error(`✗ Error executing statement: ${errorMessage}`); console.error(` Statement: ${statement.substring(0, 100)}...`);