import { Database } from 'bun:sqlite'; import { normalizeWhatsAppId } from './utils/whatsapp'; import { mkdirSync } from 'fs'; import { join } from 'path'; import { Migrator } from './db/migrator'; function applyDefaultPragmas(instance: Database): void { try { instance.exec(`PRAGMA busy_timeout = 5000;`); // Intentar activar WAL (si no es soportado, SQLite devolverá 'memory' u otro modo) instance.query(`PRAGMA journal_mode = WAL`).get(); instance.exec(`PRAGMA synchronous = NORMAL;`); instance.exec(`PRAGMA wal_autocheckpoint = 1000;`); // Asegurar claves foráneas siempre activas instance.exec(`PRAGMA foreign_keys = ON;`); } catch (e) { console.warn('[db] No se pudieron aplicar PRAGMAs (WAL, busy_timeout...):', e); } } // Function to get a database instance. Defaults to 'data/tasks.db' export function getDb(filename: string = 'tasks.db'): Database { // Try to create data directory if it doesn't exist (ignore if already exists) try { mkdirSync('data', { recursive: true }); } catch (err) { if (err.code !== 'EEXIST') throw err; // Only ignore "already exists" errors } const instance = new Database(join('data', filename)); applyDefaultPragmas(instance); return instance; } // Default export for the main application database export const db = getDb(); // Initialize function now accepts a database instance export function initializeDatabase(instance: Database) { // Aplicar PRAGMAs por defecto (WAL, busy_timeout, FK, etc.) applyDefaultPragmas(instance); // Ejecutar migraciones up-only (sin baseline por defecto). Evitar backup duplicado aquí. try { Migrator.migrateToLatest(instance, { withBackup: false, allowBaseline: false }); } catch (e) { console.error('[initializeDatabase] Error al aplicar migraciones:', e); throw e; } } /** * Ensures a user exists in the database based on their raw WhatsApp ID. * If the user exists, updates their last_seen timestamp. * If the user does not exist, creates them. * Uses the normalizeWhatsAppId utility. * Stores timestamps with millisecond precision. * * @param rawUserId The raw WhatsApp ID (e.g., '12345@s.whatsapp.net'). * @param instance The database instance to use (defaults to the main db). * @returns The normalized user ID if successful, otherwise null. */ export function ensureUserExists(rawUserId: string | null | undefined, instance: Database = db): string | null { const normalizedId = normalizeWhatsAppId(rawUserId); if (!normalizedId) { console.error(`[ensureUserExists] Could not normalize or invalid user ID provided: ${rawUserId}`); return null; } try { // Use strftime for millisecond precision timestamps const insertStmt = instance.prepare(` INSERT INTO users (id, first_seen, last_seen) VALUES (?, strftime('%Y-%m-%d %H:%M:%f', 'now'), strftime('%Y-%m-%d %H:%M:%f', 'now')) ON CONFLICT(id) DO NOTHING; `); const updateStmt = instance.prepare(` UPDATE users SET last_seen = strftime('%Y-%m-%d %H:%M:%f', 'now') WHERE id = ?; `); // Run as transaction for atomicity instance.transaction(() => { insertStmt.run(normalizedId); // Update last_seen even if the user was just inserted or already existed updateStmt.run(normalizedId); })(); // Immediately invoke the transaction return normalizedId; } catch (error) { console.error(`[ensureUserExists] Database error for user ID ${normalizedId}:`, error); return null; } }