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