import { existsSync, statSync } from "node:fs"; import { type Client, createClient } from "@libsql/client"; import dotenv from "dotenv"; import { drizzle } from "drizzle-orm/libsql"; import { log } from "../utils/logger.js"; // Import utilities from db-utils (side-effect-free) import { ensureDataDirectory, ensureDefaultUser, getDbPaths, repairOrphanedDoseIds, repairTrailingHyphenDoseIds, runAlterMigrations, runDrizzleMigrations, } from "./db-utils.js"; // Re-export all utilities so existing imports from client.ts keep working export { buildDbUrl, ensureDataDirectory, ensureDefaultUser, getDataDir, getDbPaths, repairOrphanedDoseIds, repairTrailingHyphenDoseIds, runAlterMigrations, runDrizzleMigrations, } from "./db-utils.js"; // Load .env: try cwd first, then parent dir (for local dev running from backend/) const envPath = process.env.DOTENV_PATH || (existsSync(".env") ? ".env" : "../.env"); dotenv.config({ path: envPath }); // ============================================================================= // Database initialization (runs on import) // ============================================================================= // Use absolute path to ensure it works in Docker const { dataDir, dbPath, url } = getDbPaths(); log.debug(`[DB] Data directory: ${dataDir}`); log.debug(`[DB] Database path: ${dbPath}`); log.debug(`[DB] Database URL: ${url}`); // Ensure data directory exists and is writable const dirResult = ensureDataDirectory(dataDir); if (!dirResult.success) { log.error(`[DB] ERROR: Cannot access data directory: ${dirResult.error}`); log.error(`[DB] Please ensure the volume mount has correct permissions.`); log.error(`[DB] Try running on host: sudo chown -R 1000:1000 ${dataDir}`); process.exit(1); } else { log.debug(`[DB] Data directory is writable`); // Log directory stats const stats = statSync(dataDir); log.debug(`[DB] Directory permissions: ${stats.mode.toString(8)}`); log.debug(`[DB] Directory UID: ${stats.uid}, GID: ${stats.gid}`); log.debug(`[DB] Write test successful`); } let client: Client; try { client = createClient({ url }); log.debug(`[DB] Database client created successfully`); } catch (err: unknown) { log.error(`[DB] ERROR: Failed to create database client: ${(err as Error).message}`); log.error(`[DB] Database path: ${dbPath}`); process.exit(1); } export const db = drizzle(client); // Auto-run migrations (self-healing database) async function runMigrations() { // Run drizzle-kit generated migrations log.info(`[DB] Running migrations...`); const migrateResult = await runDrizzleMigrations(db); if (!migrateResult.success) { log.error(`[DB] Migration error: ${migrateResult.error}`); } // Run ALTER TABLE migrations for backward compatibility const alterResult = await runAlterMigrations(client); if (alterResult.errors.length > 0) { alterResult.errors.forEach((err) => log.error(`[DB] ALTER migration error: ${err}`)); } log.debug(`[DB] Tables verified/created`); // Repair dose IDs with trailing hyphens (from frontend takenBy bug) const trailingResult = await repairTrailingHyphenDoseIds(client); if (trailingResult.repaired > 0) { log.info(`[DB] Repaired ${trailingResult.repaired} dose IDs with trailing hyphens`); } if (trailingResult.errors.length > 0) { trailingResult.errors.forEach((err) => log.error(`[DB] Trailing-hyphen repair error: ${err}`)); } // Repair orphaned dose tracking IDs from past schedule changes const repairResult = await repairOrphanedDoseIds(client); if (repairResult.repaired > 0) { log.info(`[DB] Repaired ${repairResult.repaired} orphaned dose tracking IDs`); } if (repairResult.errors.length > 0) { repairResult.errors.forEach((err) => log.error(`[DB] Dose repair error: ${err}`)); } // If auth is disabled, ensure a default user exists (ID=1) const authEnabled = process.env.AUTH_ENABLED === "true"; const created = await ensureDefaultUser(client, authEnabled); if (created) { log.info(`[DB] Created default user for auth-disabled mode`); } } // Export promise so server can await it before starting export const migrationsReady = runMigrations();