import { Inject, Injectable, Logger, OnModuleDestroy, OnModuleInit, Optional } from '@nestjs/common' import { ConfigService } from '@nestjs/config' import Database from 'better-sqlite3' import { mkdirSync } from 'node:fs' import { dirname, join } from 'node:path' import { EXPECTED_TABLES, listUserTables, runSchemaMigrations } from './migration.service' @Injectable() export class DatabaseService implements OnModuleInit, OnModuleDestroy { private readonly logger = new Logger(DatabaseService.name) private db: Database.Database | null = null private readonly dbPath: string constructor(@Optional() @Inject(ConfigService) private readonly config?: ConfigService) { const configured = this.config?.get('DB_PATH') ?? process.env.DB_PATH this.dbPath = configured ?? join(process.cwd(), 'data', 'app.db') } onModuleInit(): void { mkdirSync(dirname(this.dbPath), { recursive: true }) this.db = new Database(this.dbPath) this.db.pragma('journal_mode = WAL') runSchemaMigrations(this.db) this.logger.log(`SQLite ready at ${this.dbPath}`) } onModuleDestroy(): void { if (this.db) { this.db.close() this.db = null this.logger.log('SQLite connection closed') } } getDbPath(): string { return this.dbPath } getDb(): Database.Database { if (!this.db) { throw new Error('Database is not initialized') } return this.db } transaction(fn: () => T): T { return this.getDb().transaction(fn)() } verifyMigrationsApplied(): { ok: boolean; tables: string[]; missing: string[] } { const tables = listUserTables(this.getDb()) const missing = EXPECTED_TABLES.filter((name) => !tables.includes(name)) return { ok: missing.length === 0, tables, missing } } }