182 lines
10 KiB
Python
182 lines
10 KiB
Python
from sqlalchemy import text
|
|
from sqlalchemy.engine import Engine
|
|
|
|
|
|
def _table_columns(engine: Engine, table_name: str) -> set[str]:
|
|
with engine.connect() as conn:
|
|
rows = conn.execute(text(f"PRAGMA table_info({table_name})")).mappings().all()
|
|
return {row["name"] for row in rows}
|
|
|
|
|
|
def run_startup_migrations(engine: Engine) -> None:
|
|
if engine.dialect.name != "sqlite":
|
|
return
|
|
|
|
user_columns = _table_columns(engine, "users")
|
|
|
|
statements: list[str] = []
|
|
if "preferred_home_view" not in user_columns:
|
|
statements.append("ALTER TABLE users ADD COLUMN preferred_home_view VARCHAR(16) NOT NULL DEFAULT 'week'")
|
|
if "preferred_month_view_mode" not in user_columns:
|
|
statements.append("ALTER TABLE users ADD COLUMN preferred_month_view_mode VARCHAR(16) NOT NULL DEFAULT 'flat'")
|
|
if "entry_mode" not in user_columns:
|
|
statements.append("ALTER TABLE users ADD COLUMN entry_mode VARCHAR(16) NOT NULL DEFAULT 'manual'")
|
|
if "working_days_csv" not in user_columns:
|
|
statements.append("ALTER TABLE users ADD COLUMN working_days_csv VARCHAR(32) NOT NULL DEFAULT '0,1,2,3,4'")
|
|
if "count_vacation_as_worktime" not in user_columns:
|
|
statements.append("ALTER TABLE users ADD COLUMN count_vacation_as_worktime BOOLEAN NOT NULL DEFAULT 0")
|
|
if "count_holiday_as_worktime" not in user_columns:
|
|
statements.append("ALTER TABLE users ADD COLUMN count_holiday_as_worktime BOOLEAN NOT NULL DEFAULT 0")
|
|
if "count_sick_as_worktime" not in user_columns:
|
|
statements.append("ALTER TABLE users ADD COLUMN count_sick_as_worktime BOOLEAN NOT NULL DEFAULT 0")
|
|
if "automatic_break_rules_enabled" not in user_columns:
|
|
statements.append("ALTER TABLE users ADD COLUMN automatic_break_rules_enabled BOOLEAN NOT NULL DEFAULT 0")
|
|
if "default_break_minutes" not in user_columns:
|
|
statements.append("ALTER TABLE users ADD COLUMN default_break_minutes INTEGER NOT NULL DEFAULT 0")
|
|
if "overtime_start_date" not in user_columns:
|
|
statements.append("ALTER TABLE users ADD COLUMN overtime_start_date DATE")
|
|
if "overtime_expiry_days" not in user_columns:
|
|
statements.append("ALTER TABLE users ADD COLUMN overtime_expiry_days INTEGER")
|
|
if "expire_negative_overtime" not in user_columns:
|
|
statements.append("ALTER TABLE users ADD COLUMN expire_negative_overtime BOOLEAN NOT NULL DEFAULT 0")
|
|
if "vacation_days_total" not in user_columns:
|
|
statements.append("ALTER TABLE users ADD COLUMN vacation_days_total INTEGER NOT NULL DEFAULT 0")
|
|
if "vacation_show_in_header" not in user_columns:
|
|
statements.append("ALTER TABLE users ADD COLUMN vacation_show_in_header BOOLEAN NOT NULL DEFAULT 1")
|
|
if "workhours_counter_enabled" not in user_columns:
|
|
statements.append("ALTER TABLE users ADD COLUMN workhours_counter_enabled BOOLEAN NOT NULL DEFAULT 0")
|
|
if "workhours_counter_show_in_header" not in user_columns:
|
|
statements.append("ALTER TABLE users ADD COLUMN workhours_counter_show_in_header BOOLEAN NOT NULL DEFAULT 0")
|
|
if "workhours_counter_start_date" not in user_columns:
|
|
statements.append("ALTER TABLE users ADD COLUMN workhours_counter_start_date DATE")
|
|
if "workhours_counter_end_date" not in user_columns:
|
|
statements.append("ALTER TABLE users ADD COLUMN workhours_counter_end_date DATE")
|
|
if "workhours_counter_manual_offset_minutes" not in user_columns:
|
|
statements.append("ALTER TABLE users ADD COLUMN workhours_counter_manual_offset_minutes INTEGER NOT NULL DEFAULT 0")
|
|
if "workhours_counter_target_minutes" not in user_columns:
|
|
statements.append("ALTER TABLE users ADD COLUMN workhours_counter_target_minutes INTEGER")
|
|
if "workhours_counter_target_email_enabled" not in user_columns:
|
|
statements.append("ALTER TABLE users ADD COLUMN workhours_counter_target_email_enabled BOOLEAN NOT NULL DEFAULT 0")
|
|
if "workhours_counter_warning_last_sent_on" not in user_columns:
|
|
statements.append("ALTER TABLE users ADD COLUMN workhours_counter_warning_last_sent_on DATE")
|
|
if "workhours_counter_warning_last_sent_key" not in user_columns:
|
|
statements.append("ALTER TABLE users ADD COLUMN workhours_counter_warning_last_sent_key VARCHAR(120)")
|
|
if "federal_state" not in user_columns:
|
|
statements.append("ALTER TABLE users ADD COLUMN federal_state VARCHAR(8)")
|
|
if "email_verified" not in user_columns:
|
|
statements.append("ALTER TABLE users ADD COLUMN email_verified BOOLEAN NOT NULL DEFAULT 1")
|
|
if "email_verification_token_hash" not in user_columns:
|
|
statements.append("ALTER TABLE users ADD COLUMN email_verification_token_hash VARCHAR(128)")
|
|
if "email_verification_expires_at" not in user_columns:
|
|
statements.append("ALTER TABLE users ADD COLUMN email_verification_expires_at DATETIME")
|
|
if "email_verification_sent_at" not in user_columns:
|
|
statements.append("ALTER TABLE users ADD COLUMN email_verification_sent_at DATETIME")
|
|
if "mfa_method" not in user_columns:
|
|
statements.append("ALTER TABLE users ADD COLUMN mfa_method VARCHAR(16) NOT NULL DEFAULT 'none'")
|
|
if "mfa_totp_secret_encrypted" not in user_columns:
|
|
statements.append("ALTER TABLE users ADD COLUMN mfa_totp_secret_encrypted TEXT")
|
|
if "mfa_email_code_hash" not in user_columns:
|
|
statements.append("ALTER TABLE users ADD COLUMN mfa_email_code_hash VARCHAR(255)")
|
|
if "mfa_email_code_expires_at" not in user_columns:
|
|
statements.append("ALTER TABLE users ADD COLUMN mfa_email_code_expires_at DATETIME")
|
|
if "mfa_email_code_sent_at" not in user_columns:
|
|
statements.append("ALTER TABLE users ADD COLUMN mfa_email_code_sent_at DATETIME")
|
|
|
|
email_config_columns = _table_columns(engine, "email_server_config")
|
|
if "registration_admin_notify_enabled" not in email_config_columns:
|
|
statements.append("ALTER TABLE email_server_config ADD COLUMN registration_admin_notify_enabled BOOLEAN NOT NULL DEFAULT 1")
|
|
if "registration_admin_notify_admin_ids_csv" not in email_config_columns:
|
|
statements.append("ALTER TABLE email_server_config ADD COLUMN registration_admin_notify_admin_ids_csv VARCHAR(1024)")
|
|
|
|
time_entry_columns = _table_columns(engine, "time_entries")
|
|
if "break_rule_mode" not in time_entry_columns:
|
|
statements.append("ALTER TABLE time_entries ADD COLUMN break_rule_mode VARCHAR(16) NOT NULL DEFAULT 'manual'")
|
|
|
|
if not statements:
|
|
return
|
|
|
|
with engine.begin() as conn:
|
|
for statement in statements:
|
|
conn.execute(text(statement))
|
|
conn.execute(text("UPDATE users SET entry_mode = 'auto_until_today' WHERE entry_mode = 'auto'"))
|
|
conn.execute(
|
|
text("CREATE INDEX IF NOT EXISTS ix_users_email_verification_token_hash ON users (email_verification_token_hash)")
|
|
)
|
|
conn.execute(
|
|
text(
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS overtime_adjustments (
|
|
id VARCHAR(36) PRIMARY KEY NOT NULL,
|
|
user_id VARCHAR(36) NOT NULL,
|
|
date DATE NOT NULL,
|
|
minutes INTEGER NOT NULL,
|
|
notes TEXT,
|
|
created_at DATETIME NOT NULL,
|
|
FOREIGN KEY(user_id) REFERENCES users (id) ON DELETE CASCADE,
|
|
CONSTRAINT uq_user_overtime_adjustment_date UNIQUE (user_id, date)
|
|
)
|
|
"""
|
|
)
|
|
)
|
|
conn.execute(text("CREATE INDEX IF NOT EXISTS ix_overtime_adjustments_user_id ON overtime_adjustments (user_id)"))
|
|
conn.execute(text("CREATE INDEX IF NOT EXISTS ix_overtime_adjustments_date ON overtime_adjustments (date)"))
|
|
conn.execute(
|
|
text(
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS auto_entry_skips (
|
|
id VARCHAR(36) PRIMARY KEY NOT NULL,
|
|
user_id VARCHAR(36) NOT NULL,
|
|
date DATE NOT NULL,
|
|
created_at DATETIME NOT NULL,
|
|
FOREIGN KEY(user_id) REFERENCES users (id) ON DELETE CASCADE,
|
|
CONSTRAINT uq_user_auto_entry_skip_date UNIQUE (user_id, date)
|
|
)
|
|
"""
|
|
)
|
|
)
|
|
conn.execute(text("CREATE INDEX IF NOT EXISTS ix_auto_entry_skips_user_id ON auto_entry_skips (user_id)"))
|
|
conn.execute(text("CREATE INDEX IF NOT EXISTS ix_auto_entry_skips_date ON auto_entry_skips (date)"))
|
|
conn.execute(
|
|
text(
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS site_content (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
|
key VARCHAR(64) NOT NULL UNIQUE,
|
|
markdown_text TEXT NOT NULL DEFAULT '',
|
|
updated_by_user_id VARCHAR(36),
|
|
updated_at DATETIME,
|
|
FOREIGN KEY(updated_by_user_id) REFERENCES users (id) ON DELETE SET NULL
|
|
)
|
|
"""
|
|
)
|
|
)
|
|
conn.execute(text("CREATE INDEX IF NOT EXISTS ix_site_content_key ON site_content (key)"))
|
|
conn.execute(
|
|
text(
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS support_tickets (
|
|
id VARCHAR(36) PRIMARY KEY NOT NULL,
|
|
user_id VARCHAR(36),
|
|
category VARCHAR(24) NOT NULL DEFAULT 'problem',
|
|
status VARCHAR(24) NOT NULL DEFAULT 'open',
|
|
name VARCHAR(255) NOT NULL DEFAULT '',
|
|
email VARCHAR(255) NOT NULL,
|
|
subject VARCHAR(255) NOT NULL,
|
|
message TEXT NOT NULL,
|
|
admin_notes TEXT,
|
|
source_ip_hash VARCHAR(128),
|
|
source_user_agent VARCHAR(512),
|
|
created_at DATETIME NOT NULL,
|
|
updated_at DATETIME,
|
|
closed_at DATETIME,
|
|
FOREIGN KEY(user_id) REFERENCES users (id) ON DELETE SET NULL
|
|
)
|
|
"""
|
|
)
|
|
)
|
|
conn.execute(text("CREATE INDEX IF NOT EXISTS ix_support_tickets_user_id ON support_tickets (user_id)"))
|
|
conn.execute(text("CREATE INDEX IF NOT EXISTS ix_support_tickets_email ON support_tickets (email)"))
|
|
conn.execute(text("CREATE INDEX IF NOT EXISTS ix_support_tickets_status ON support_tickets (status)"))
|
|
conn.execute(text("CREATE INDEX IF NOT EXISTS ix_support_tickets_source_ip_hash ON support_tickets (source_ip_hash)"))
|
|
conn.execute(text("CREATE INDEX IF NOT EXISTS ix_support_tickets_created_at ON support_tickets (created_at)"))
|