367 lines
10 KiB
JavaScript
367 lines
10 KiB
JavaScript
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')) 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')) 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);
|
|
`);
|
|
}
|