- Convert invcount to self-contained module - Add Module Manager for install/uninstall - Create module_registry database table - Support hot-reloading of modules - Move data imports into invcount module - Update all templates and routes to new structure Version bumped to 0.16.0
200 lines
6.0 KiB
Python
200 lines
6.0 KiB
Python
"""
|
|
ScanLook Core Database Migration System
|
|
|
|
IMPORTANT: This file only contains CORE system migrations.
|
|
Module-specific migrations are in each module's migrations.py file.
|
|
"""
|
|
|
|
import sqlite3
|
|
import os
|
|
|
|
DB_PATH = os.path.join(os.path.dirname(__file__), 'database', 'scanlook.db')
|
|
|
|
|
|
def get_db():
|
|
"""Get database connection"""
|
|
conn = sqlite3.connect(DB_PATH)
|
|
conn.row_factory = sqlite3.Row
|
|
return conn
|
|
|
|
|
|
def init_migrations_table():
|
|
"""Create the migrations tracking table if it doesn't exist"""
|
|
conn = get_db()
|
|
conn.execute('''
|
|
CREATE TABLE IF NOT EXISTS schema_migrations (
|
|
version INTEGER PRIMARY KEY,
|
|
name TEXT NOT NULL,
|
|
applied_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
)
|
|
''')
|
|
conn.commit()
|
|
conn.close()
|
|
|
|
|
|
def get_applied_migrations():
|
|
"""Get list of already-applied migration versions"""
|
|
conn = get_db()
|
|
try:
|
|
rows = conn.execute('SELECT version FROM schema_migrations ORDER BY version').fetchall()
|
|
return [row['version'] for row in rows]
|
|
except:
|
|
return []
|
|
finally:
|
|
conn.close()
|
|
|
|
|
|
def record_migration(version, name):
|
|
"""Record that a migration was applied"""
|
|
conn = get_db()
|
|
conn.execute('INSERT INTO schema_migrations (version, name) VALUES (?, ?)', [version, name])
|
|
conn.commit()
|
|
conn.close()
|
|
|
|
|
|
def column_exists(table, column):
|
|
"""Check if a column exists in a table"""
|
|
conn = get_db()
|
|
cursor = conn.execute(f'PRAGMA table_info({table})')
|
|
columns = [row[1] for row in cursor.fetchall()]
|
|
conn.close()
|
|
return column in columns
|
|
|
|
|
|
def table_exists(table):
|
|
"""Check if a table exists"""
|
|
conn = get_db()
|
|
cursor = conn.execute("SELECT name FROM sqlite_master WHERE type='table' AND name=?", [table])
|
|
exists = cursor.fetchone() is not None
|
|
conn.close()
|
|
return exists
|
|
|
|
|
|
# ============================================
|
|
# CORE SYSTEM MIGRATIONS ONLY
|
|
# ============================================
|
|
# Module-specific migrations are handled by each module's migrations.py
|
|
# ============================================
|
|
|
|
def migration_001_add_modules_tables():
|
|
"""Add Modules and UserModules tables (if not created by init_db)"""
|
|
conn = get_db()
|
|
|
|
if not table_exists('Modules'):
|
|
conn.execute('''
|
|
CREATE TABLE Modules (
|
|
module_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
module_name TEXT NOT NULL,
|
|
module_key TEXT UNIQUE NOT NULL,
|
|
description TEXT,
|
|
icon TEXT,
|
|
is_active INTEGER DEFAULT 1,
|
|
display_order INTEGER DEFAULT 0
|
|
)
|
|
''')
|
|
print(" Created Modules table")
|
|
|
|
if not table_exists('UserModules'):
|
|
conn.execute('''
|
|
CREATE TABLE UserModules (
|
|
user_module_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
user_id INTEGER NOT NULL,
|
|
module_id INTEGER NOT NULL,
|
|
granted_by INTEGER,
|
|
granted_timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
FOREIGN KEY (user_id) REFERENCES Users(user_id),
|
|
FOREIGN KEY (module_id) REFERENCES Modules(module_id),
|
|
FOREIGN KEY (granted_by) REFERENCES Users(user_id),
|
|
UNIQUE(user_id, module_id)
|
|
)
|
|
''')
|
|
print(" Created UserModules table")
|
|
|
|
conn.commit()
|
|
conn.close()
|
|
|
|
|
|
def migration_002_add_usermodules_granted_columns():
|
|
"""Add granted_by and granted_timestamp to UserModules if missing"""
|
|
conn = get_db()
|
|
|
|
if table_exists('UserModules'):
|
|
if not column_exists('UserModules', 'granted_by'):
|
|
conn.execute('ALTER TABLE UserModules ADD COLUMN granted_by INTEGER')
|
|
print(" Added granted_by column to UserModules")
|
|
|
|
if not column_exists('UserModules', 'granted_timestamp'):
|
|
conn.execute('ALTER TABLE UserModules ADD COLUMN granted_timestamp DATETIME')
|
|
print(" Added granted_timestamp column to UserModules")
|
|
|
|
conn.commit()
|
|
conn.close()
|
|
|
|
|
|
def migration_003_add_module_registry():
|
|
"""Add module_registry table for new module manager system"""
|
|
conn = get_db()
|
|
|
|
if not table_exists('module_registry'):
|
|
conn.execute('''
|
|
CREATE TABLE module_registry (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
module_key TEXT UNIQUE NOT NULL,
|
|
name TEXT NOT NULL,
|
|
version TEXT NOT NULL,
|
|
author TEXT,
|
|
description TEXT,
|
|
is_installed INTEGER DEFAULT 0,
|
|
is_active INTEGER DEFAULT 0,
|
|
installed_at TEXT,
|
|
config_json TEXT
|
|
)
|
|
''')
|
|
print(" Created module_registry table")
|
|
|
|
conn.commit()
|
|
conn.close()
|
|
|
|
|
|
# List of CORE migrations only
|
|
MIGRATIONS = [
|
|
(1, 'add_modules_tables', migration_001_add_modules_tables),
|
|
(2, 'add_usermodules_granted_columns', migration_002_add_usermodules_granted_columns),
|
|
(3, 'add_module_registry', migration_003_add_module_registry),
|
|
]
|
|
|
|
|
|
def run_migrations():
|
|
"""Run all pending core migrations"""
|
|
print("🔄 Checking core database migrations...")
|
|
|
|
# Make sure migrations table exists
|
|
init_migrations_table()
|
|
|
|
# Get already-applied migrations
|
|
applied = get_applied_migrations()
|
|
|
|
# Run pending migrations
|
|
pending = [(v, n, f) for v, n, f in MIGRATIONS if v not in applied]
|
|
|
|
if not pending:
|
|
print("✅ Core database is up to date")
|
|
return
|
|
|
|
print(f"📦 Running {len(pending)} core migration(s)...")
|
|
|
|
for version, name, func in pending:
|
|
print(f"\n Migration {version}: {name}")
|
|
try:
|
|
func()
|
|
record_migration(version, name)
|
|
print(f" ✅ Migration {version} complete")
|
|
except Exception as e:
|
|
print(f" ❌ Migration {version} failed: {e}")
|
|
raise
|
|
|
|
print("\n✅ All core migrations complete")
|
|
|
|
|
|
if __name__ == '__main__':
|
|
run_migrations() |