import db from "./db.js"; export default function initializeDatabase() { db.exec(` -- Table: contracts CREATE TABLE IF NOT EXISTS contracts ( contract_id INTEGER PRIMARY KEY AUTOINCREMENT, contract_number TEXT NOT NULL, contract_name TEXT, customer_contract_number TEXT, customer TEXT, investor TEXT, date_signed TEXT, finish_date TEXT ); -- Table: projects CREATE TABLE IF NOT EXISTS projects ( project_id INTEGER PRIMARY KEY AUTOINCREMENT, contract_id INTEGER, project_name TEXT NOT NULL, project_number TEXT NOT NULL, address TEXT, plot TEXT, district TEXT, unit TEXT, city TEXT, investment_number TEXT, finish_date TEXT, wp TEXT, contact TEXT, notes TEXT, project_type TEXT CHECK(project_type IN ('design', 'construction', 'design+construction')) DEFAULT 'design', project_status TEXT CHECK(project_status IN ('registered', 'in_progress_design', 'in_progress_construction', 'fulfilled', 'cancelled')) DEFAULT 'registered', FOREIGN KEY (contract_id) REFERENCES contracts(contract_id) ); -- Table: tasks CREATE TABLE IF NOT EXISTS tasks ( task_id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, max_wait_days INTEGER DEFAULT 0, is_standard INTEGER NOT NULL DEFAULT 0 ); -- Table: project_tasks CREATE TABLE IF NOT EXISTS project_tasks ( id INTEGER PRIMARY KEY AUTOINCREMENT, project_id INTEGER NOT NULL, task_template_id INTEGER, custom_task_name TEXT, custom_max_wait_days INTEGER DEFAULT 0, status TEXT NOT NULL DEFAULT 'pending', date_added TEXT DEFAULT CURRENT_TIMESTAMP, priority TEXT DEFAULT 'normal', FOREIGN KEY (project_id) REFERENCES projects(project_id), FOREIGN KEY (task_template_id) REFERENCES tasks(task_id) ); -- Table: notes CREATE TABLE IF NOT EXISTS notes ( note_id INTEGER PRIMARY KEY AUTOINCREMENT, project_id INTEGER, task_id INTEGER, note TEXT NOT NULL, note_date TEXT DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (project_id) REFERENCES projects(project_id), FOREIGN KEY (task_id) REFERENCES project_tasks(id) ); `); // Migration: Add custom task columns if they don't exist try { db.exec(` ALTER TABLE project_tasks ADD COLUMN custom_task_name TEXT; `); } catch (e) { // Column already exists, ignore error } try { db.exec(` ALTER TABLE project_tasks ADD COLUMN custom_max_wait_days INTEGER DEFAULT 0; `); } catch (e) { // Column already exists, ignore error } // Migration: Add description column to tasks table try { db.exec(` ALTER TABLE tasks ADD COLUMN description TEXT; `); } catch (e) { // Column already exists, ignore error } // Migration: Add description column to project_tasks table try { db.exec(` ALTER TABLE project_tasks ADD COLUMN custom_description TEXT; `); } catch (e) { // Column already exists, ignore error } // Migration: Add project_type column to projects table try { db.exec(` ALTER TABLE projects ADD COLUMN project_type TEXT CHECK(project_type IN ('design', 'construction', 'design+construction')) DEFAULT 'design'; `); } catch (e) { // Column already exists, ignore error } // Migration: Add project_status column to projects table try { db.exec(` ALTER TABLE projects ADD COLUMN project_status TEXT CHECK(project_status IN ('registered', 'in_progress_design', 'in_progress_construction', 'fulfilled', 'cancelled')) DEFAULT 'registered'; `); } catch (e) { // Column already exists, ignore error } // Migration: Add coordinates column to projects table try { db.exec(` ALTER TABLE projects ADD COLUMN coordinates TEXT; `); } catch (e) { // Column already exists, ignore error } // Migration: Copy data from geo_info to coordinates and drop geo_info try { db.exec(` UPDATE projects SET coordinates = geo_info WHERE geo_info IS NOT NULL; `); db.exec(` ALTER TABLE projects DROP COLUMN geo_info; `); } catch (e) { // Column migration already done or geo_info doesn't exist, ignore error } // Migration: Add date_started column to project_tasks table try { db.exec(` ALTER TABLE project_tasks ADD COLUMN date_started TEXT; `); } catch (e) { // Column already exists, ignore error } // Migration: Add is_system column to notes table try { db.exec(` ALTER TABLE notes ADD COLUMN is_system INTEGER DEFAULT 0; `); } catch (e) { // Column already exists, ignore error } // Migration: Add date_completed column to project_tasks table try { db.exec(` ALTER TABLE project_tasks ADD COLUMN date_completed TEXT; `); } catch (e) { // Column already exists, ignore error } // Migration: Add user tracking columns to projects table try { db.exec(` ALTER TABLE projects ADD COLUMN created_by TEXT; `); } catch (e) { // Column already exists, ignore error } try { db.exec(` ALTER TABLE projects ADD COLUMN assigned_to TEXT; `); } catch (e) { // Column already exists, ignore error } try { db.exec(` ALTER TABLE projects ADD COLUMN created_at TEXT; `); } catch (e) { // Column already exists, ignore error } try { db.exec(` ALTER TABLE projects ADD COLUMN updated_at TEXT; `); } catch (e) { // Column already exists, ignore error } // Migration: Add user tracking columns to project_tasks table try { db.exec(` ALTER TABLE project_tasks ADD COLUMN created_by TEXT; `); } catch (e) { // Column already exists, ignore error } try { db.exec(` ALTER TABLE project_tasks ADD COLUMN assigned_to TEXT; `); } catch (e) { // Column already exists, ignore error } try { db.exec(` ALTER TABLE project_tasks ADD COLUMN created_at TEXT; `); } catch (e) { // Column already exists, ignore error } try { db.exec(` ALTER TABLE project_tasks ADD COLUMN updated_at TEXT; `); } catch (e) { // Column already exists, ignore error } // Create indexes for project_tasks user tracking try { db.exec(` CREATE INDEX IF NOT EXISTS idx_project_tasks_created_by ON project_tasks(created_by); CREATE INDEX IF NOT EXISTS idx_project_tasks_assigned_to ON project_tasks(assigned_to); `); } catch (e) { // Index already exists, ignore error } // Migration: Add user tracking columns to notes table try { db.exec(` ALTER TABLE notes ADD COLUMN created_by TEXT; `); } catch (e) { // Column already exists, ignore error } try { db.exec(` ALTER TABLE notes ADD COLUMN is_system INTEGER DEFAULT 0; `); } catch (e) { // Column already exists, ignore error } // Create indexes for notes user tracking try { db.exec(` CREATE INDEX IF NOT EXISTS idx_notes_created_by ON notes(created_by); CREATE INDEX IF NOT EXISTS idx_notes_project_id ON notes(project_id); CREATE INDEX IF NOT EXISTS idx_notes_task_id ON notes(task_id); `); } catch (e) { // Index already exists, ignore error } // Authorization tables db.exec(` -- Users table CREATE TABLE IF NOT EXISTS users ( id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))), name TEXT NOT NULL, username TEXT UNIQUE NOT NULL, password_hash TEXT NOT NULL, role TEXT CHECK(role IN ('admin', 'project_manager', 'user', 'read_only')) DEFAULT 'user', created_at TEXT DEFAULT CURRENT_TIMESTAMP, updated_at TEXT DEFAULT CURRENT_TIMESTAMP, is_active INTEGER DEFAULT 1, last_login TEXT, failed_login_attempts INTEGER DEFAULT 0, locked_until TEXT ); -- NextAuth.js sessions table (simplified for custom implementation) CREATE TABLE IF NOT EXISTS sessions ( id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))), session_token TEXT UNIQUE NOT NULL, user_id TEXT NOT NULL, expires TEXT NOT NULL, created_at TEXT DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE ); -- Audit log table for security tracking CREATE TABLE IF NOT EXISTS audit_logs ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id TEXT, action TEXT NOT NULL, resource_type TEXT, resource_id TEXT, ip_address TEXT, user_agent TEXT, timestamp TEXT DEFAULT CURRENT_TIMESTAMP, details TEXT, FOREIGN KEY (user_id) REFERENCES users(id) ); -- Create indexes for performance CREATE INDEX IF NOT EXISTS idx_users_username ON users(username); CREATE INDEX IF NOT EXISTS idx_sessions_token ON sessions(session_token); CREATE INDEX IF NOT EXISTS idx_sessions_user ON sessions(user_id); CREATE INDEX IF NOT EXISTS idx_audit_user_timestamp ON audit_logs(user_id, timestamp); `); // Migration: Add username column and migrate from email if needed try { // Check if username column exists const columns = db.prepare("PRAGMA table_info(users)").all(); const hasUsername = columns.some(col => col.name === 'username'); const hasEmail = columns.some(col => col.name === 'email'); if (!hasUsername && hasEmail) { // Add username column db.exec(`ALTER TABLE users ADD COLUMN username TEXT;`); // Migrate existing email data to username (for development/testing) // In production, you might want to handle this differently db.exec(`UPDATE users SET username = email WHERE username IS NULL;`); // Create unique index on username db.exec(`CREATE UNIQUE INDEX IF NOT EXISTS idx_users_username_unique ON users(username);`); console.log("✅ Migrated users table from email to username"); } else if (!hasUsername) { // If neither username nor email exists, something is wrong console.warn("⚠️ Users table missing both username and email columns"); } } catch (e) { console.warn("Migration warning:", e.message); } // Generic file attachments table db.exec(` CREATE TABLE IF NOT EXISTS file_attachments ( file_id INTEGER PRIMARY KEY AUTOINCREMENT, entity_type TEXT NOT NULL CHECK(entity_type IN ('contract', 'project', 'task')), entity_id INTEGER NOT NULL, original_filename TEXT NOT NULL, stored_filename TEXT NOT NULL, file_path TEXT NOT NULL, file_size INTEGER, mime_type TEXT, description TEXT, uploaded_by TEXT, upload_date TEXT DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (uploaded_by) REFERENCES users(id) ); -- Create indexes for file attachments CREATE INDEX IF NOT EXISTS idx_file_attachments_entity ON file_attachments(entity_type, entity_id); CREATE INDEX IF NOT EXISTS idx_file_attachments_uploaded_by ON file_attachments(uploaded_by); -- Generic field change history table CREATE TABLE IF NOT EXISTS field_change_history ( id INTEGER PRIMARY KEY AUTOINCREMENT, table_name TEXT NOT NULL, record_id INTEGER NOT NULL, field_name TEXT NOT NULL, old_value TEXT, new_value TEXT, changed_by INTEGER, changed_at TEXT DEFAULT CURRENT_TIMESTAMP, change_reason TEXT, FOREIGN KEY (changed_by) REFERENCES users(id) ); -- Create indexes for field change history CREATE INDEX IF NOT EXISTS idx_field_history_table_record ON field_change_history(table_name, record_id); CREATE INDEX IF NOT EXISTS idx_field_history_field ON field_change_history(table_name, record_id, field_name); CREATE INDEX IF NOT EXISTS idx_field_history_changed_by ON field_change_history(changed_by); `); }