You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

98 lines
3.4 KiB
TypeScript

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;
}
}