import { Database } from 'bun:sqlite'; import { normalizeWhatsAppId } from './utils/whatsapp'; // Import the utility // Function to get a database instance. Defaults to 'tasks.db' export function getDb(filename: string = 'tasks.db'): Database { return new Database(filename); } // Default export for the main application database export const db = getDb(); // Initialize function now accepts a database instance export function initializeDatabase(instance: Database) { // Enable foreign key constraints instance.exec(`PRAGMA foreign_keys = ON;`); // Create users table first as others depend on it // Use TEXT for timestamps to store higher precision ISO8601 format easily instance.exec(` CREATE TABLE IF NOT EXISTS users ( id TEXT PRIMARY KEY, -- WhatsApp user ID (normalized) first_seen TEXT DEFAULT (strftime('%Y-%m-%d %H:%M:%f', 'now')), last_seen TEXT DEFAULT (strftime('%Y-%m-%d %H:%M:%f', 'now')) ); `); // Create groups table instance.exec(` CREATE TABLE IF NOT EXISTS groups ( id TEXT PRIMARY KEY, -- Group ID (normalized) community_id TEXT NOT NULL, name TEXT, last_verified TEXT DEFAULT (strftime('%Y-%m-%d %H:%M:%f', 'now')), active BOOLEAN DEFAULT TRUE ); `); // Create tasks table instance.exec(` CREATE TABLE IF NOT EXISTS tasks ( id INTEGER PRIMARY KEY AUTOINCREMENT, description TEXT NOT NULL, created_at TEXT DEFAULT (strftime('%Y-%m-%d %H:%M:%f', 'now')), due_date TEXT NULL, -- Store dates as ISO8601 strings or YYYY-MM-DD completed BOOLEAN DEFAULT FALSE, completed_at TEXT NULL, group_id TEXT NULL, -- Normalized group ID created_by TEXT NOT NULL, -- Normalized user ID FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE CASCADE, FOREIGN KEY (group_id) REFERENCES groups(id) ON DELETE SET NULL -- Optional: Link task to group ); `); // Create task_assignments table instance.exec(` CREATE TABLE IF NOT EXISTS task_assignments ( task_id INTEGER NOT NULL, user_id TEXT NOT NULL, -- Normalized user ID assigned_by TEXT NOT NULL, -- Normalized user ID assigned_at TEXT DEFAULT (strftime('%Y-%m-%d %H:%M:%f', 'now')), PRIMARY KEY (task_id, user_id), FOREIGN KEY (task_id) REFERENCES tasks(id) ON DELETE CASCADE, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, FOREIGN KEY (assigned_by) REFERENCES users(id) ON DELETE CASCADE ); `); } /** * 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; } }