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.

116 lines
4.1 KiB
TypeScript

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