Files
zeroclock/src-tauri/src/database.rs

221 lines
7.5 KiB
Rust

use rusqlite::Connection;
pub fn init_db(conn: &Connection) -> Result<(), rusqlite::Error> {
conn.execute(
"CREATE TABLE IF NOT EXISTS clients (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT,
address TEXT,
created_at TEXT DEFAULT CURRENT_TIMESTAMP
)",
[],
)?;
// Migrate clients table — add new columns (safe to re-run)
let migration_columns = [
"ALTER TABLE clients ADD COLUMN company TEXT",
"ALTER TABLE clients ADD COLUMN phone TEXT",
"ALTER TABLE clients ADD COLUMN tax_id TEXT",
"ALTER TABLE clients ADD COLUMN payment_terms TEXT",
"ALTER TABLE clients ADD COLUMN notes TEXT",
];
for sql in &migration_columns {
match conn.execute(sql, []) {
Ok(_) => {}
Err(e) => {
let msg = e.to_string();
if !msg.contains("duplicate column") {
return Err(e);
}
}
}
}
conn.execute(
"CREATE TABLE IF NOT EXISTS projects (
id INTEGER PRIMARY KEY AUTOINCREMENT,
client_id INTEGER,
name TEXT NOT NULL,
hourly_rate REAL DEFAULT 0,
color TEXT DEFAULT '#F59E0B',
archived INTEGER DEFAULT 0,
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (client_id) REFERENCES clients(id)
)",
[],
)?;
// Migrate projects table — add budget columns (safe to re-run)
let project_migrations = [
"ALTER TABLE projects ADD COLUMN budget_hours REAL DEFAULT NULL",
"ALTER TABLE projects ADD COLUMN budget_amount REAL DEFAULT NULL",
"ALTER TABLE projects ADD COLUMN rounding_override INTEGER DEFAULT NULL",
];
for sql in &project_migrations {
match conn.execute(sql, []) {
Ok(_) => {}
Err(e) => {
let msg = e.to_string();
if !msg.contains("duplicate column") {
return Err(e);
}
}
}
}
conn.execute(
"CREATE TABLE IF NOT EXISTS tasks (
id INTEGER PRIMARY KEY AUTOINCREMENT,
project_id INTEGER NOT NULL,
name TEXT NOT NULL,
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (project_id) REFERENCES projects(id)
)",
[],
)?;
conn.execute(
"CREATE TABLE IF NOT EXISTS time_entries (
id INTEGER PRIMARY KEY AUTOINCREMENT,
project_id INTEGER NOT NULL,
task_id INTEGER,
description TEXT,
start_time TEXT NOT NULL,
end_time TEXT,
duration INTEGER DEFAULT 0,
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (project_id) REFERENCES projects(id),
FOREIGN KEY (task_id) REFERENCES tasks(id)
)",
[],
)?;
conn.execute(
"CREATE TABLE IF NOT EXISTS invoices (
id INTEGER PRIMARY KEY AUTOINCREMENT,
client_id INTEGER NOT NULL,
invoice_number TEXT NOT NULL,
date TEXT NOT NULL,
due_date TEXT,
subtotal REAL DEFAULT 0,
tax_rate REAL DEFAULT 0,
tax_amount REAL DEFAULT 0,
discount REAL DEFAULT 0,
total REAL DEFAULT 0,
notes TEXT,
status TEXT DEFAULT 'draft',
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (client_id) REFERENCES clients(id)
)",
[],
)?;
conn.execute(
"CREATE TABLE IF NOT EXISTS invoice_items (
id INTEGER PRIMARY KEY AUTOINCREMENT,
invoice_id INTEGER NOT NULL,
description TEXT NOT NULL,
quantity REAL DEFAULT 1,
rate REAL DEFAULT 0,
amount REAL DEFAULT 0,
time_entry_id INTEGER,
FOREIGN KEY (invoice_id) REFERENCES invoices(id),
FOREIGN KEY (time_entry_id) REFERENCES time_entries(id)
)",
[],
)?;
conn.execute(
"CREATE TABLE IF NOT EXISTS tracked_apps (
id INTEGER PRIMARY KEY AUTOINCREMENT,
project_id INTEGER NOT NULL,
exe_name TEXT NOT NULL,
exe_path TEXT,
display_name TEXT,
FOREIGN KEY (project_id) REFERENCES projects(id)
)",
[],
)?;
conn.execute(
"CREATE TABLE IF NOT EXISTS tags (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
color TEXT DEFAULT '#6B7280'
)",
[],
)?;
conn.execute(
"CREATE TABLE IF NOT EXISTS entry_tags (
entry_id INTEGER NOT NULL,
tag_id INTEGER NOT NULL,
PRIMARY KEY (entry_id, tag_id),
FOREIGN KEY (entry_id) REFERENCES time_entries(id) ON DELETE CASCADE,
FOREIGN KEY (tag_id) REFERENCES tags(id) ON DELETE CASCADE
)",
[],
)?;
conn.execute(
"CREATE TABLE IF NOT EXISTS favorites (
id INTEGER PRIMARY KEY AUTOINCREMENT,
project_id INTEGER NOT NULL,
task_id INTEGER,
description TEXT,
sort_order INTEGER DEFAULT 0,
FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE,
FOREIGN KEY (task_id) REFERENCES tasks(id) ON DELETE SET NULL
)",
[],
)?;
conn.execute(
"CREATE TABLE IF NOT EXISTS settings (
key TEXT PRIMARY KEY,
value TEXT
)",
[],
)?;
// Insert default settings
conn.execute(
"INSERT OR IGNORE INTO settings (key, value) VALUES ('hourly_rate', '50')",
[],
)?;
conn.execute(
"INSERT OR IGNORE INTO settings (key, value) VALUES ('idle_detection', 'true')",
[],
)?;
conn.execute(
"INSERT OR IGNORE INTO settings (key, value) VALUES ('idle_timeout', '5')",
[],
)?;
conn.execute(
"INSERT OR IGNORE INTO settings (key, value) VALUES ('reminder_interval', '30')",
[],
)?;
conn.execute(
"INSERT OR IGNORE INTO settings (key, value) VALUES ('app_tracking_mode', 'auto')",
[],
)?;
conn.execute(
"INSERT OR IGNORE INTO settings (key, value) VALUES ('app_check_interval', '5')",
[],
)?;
conn.execute("INSERT OR IGNORE INTO settings (key, value) VALUES ('goals_enabled', 'true')", [])?;
conn.execute("INSERT OR IGNORE INTO settings (key, value) VALUES ('daily_goal_hours', '8')", [])?;
conn.execute("INSERT OR IGNORE INTO settings (key, value) VALUES ('weekly_goal_hours', '40')", [])?;
conn.execute("INSERT OR IGNORE INTO settings (key, value) VALUES ('rounding_enabled', 'false')", [])?;
conn.execute("INSERT OR IGNORE INTO settings (key, value) VALUES ('rounding_increment', '15')", [])?;
conn.execute("INSERT OR IGNORE INTO settings (key, value) VALUES ('rounding_method', 'nearest')", [])?;
conn.execute("INSERT OR IGNORE INTO settings (key, value) VALUES ('theme_mode', 'dark')", [])?;
conn.execute("INSERT OR IGNORE INTO settings (key, value) VALUES ('accent_color', 'amber')", [])?;
conn.execute("INSERT OR IGNORE INTO settings (key, value) VALUES ('shortcut_toggle_timer', 'CmdOrCtrl+Shift+T')", [])?;
conn.execute("INSERT OR IGNORE INTO settings (key, value) VALUES ('shortcut_show_app', 'CmdOrCtrl+Shift+Z')", [])?;
conn.execute("INSERT OR IGNORE INTO settings (key, value) VALUES ('mini_timer_opacity', '90')", [])?;
Ok(())
}