mirror of
https://github.com/DRYTRIX/TimeTracker.git
synced 2026-01-03 02:00:55 -06:00
- Fix AUTH_METHOD=none: Read from Flask app config instead of Config class - Add comprehensive schema verification: Verify all SQLAlchemy models against database and auto-fix missing columns - Improve startup logging: Unified format with timestamps and log levels - Enhanced migration flow: Automatic schema verification after migrations Fixes authentication issue where password field showed even with AUTH_METHOD=none. Ensures all database columns from models exist, preventing missing column errors. Improves startup logging for better debugging and monitoring.
261 lines
7.7 KiB
Python
261 lines
7.7 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Script to manually add missing columns to the users table.
|
|
This is a workaround for cases where migrations show as applied but columns are missing.
|
|
|
|
Usage:
|
|
python scripts/fix_missing_columns.py
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
from sqlalchemy import create_engine, inspect, text
|
|
from sqlalchemy.exc import OperationalError
|
|
|
|
# Add parent directory to path
|
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
|
|
# Get database URL from environment
|
|
DATABASE_URL = os.getenv("DATABASE_URL", "postgresql+psycopg2://timetracker:timetracker@localhost:5432/timetracker")
|
|
|
|
|
|
def has_column(engine, table_name, column_name):
|
|
"""Check if a column exists in a table"""
|
|
inspector = inspect(engine)
|
|
try:
|
|
columns = [col['name'] for col in inspector.get_columns(table_name)]
|
|
return column_name in columns
|
|
except Exception:
|
|
return False
|
|
|
|
|
|
def add_column_if_missing(engine, table_name, column_name, column_type, nullable=True, default=None):
|
|
"""Add a column to a table if it doesn't exist"""
|
|
if has_column(engine, table_name, column_name):
|
|
print(f"✓ Column '{column_name}' already exists in '{table_name}'")
|
|
return False
|
|
|
|
try:
|
|
with engine.connect() as conn:
|
|
# Build ALTER TABLE statement
|
|
alter_sql = f"ALTER TABLE {table_name} ADD COLUMN {column_name} {column_type}"
|
|
if not nullable:
|
|
alter_sql += " NOT NULL"
|
|
if default is not None:
|
|
alter_sql += f" DEFAULT {default}"
|
|
|
|
conn.execute(text(alter_sql))
|
|
conn.commit()
|
|
print(f"✓ Added column '{column_name}' to '{table_name}'")
|
|
return True
|
|
except Exception as e:
|
|
print(f"✗ Failed to add column '{column_name}' to '{table_name}': {e}")
|
|
return False
|
|
|
|
|
|
def main():
|
|
"""Main function to add missing columns"""
|
|
print("=" * 60)
|
|
print("TimeTracker - Fix Missing Database Columns")
|
|
print("=" * 60)
|
|
print()
|
|
|
|
try:
|
|
engine = create_engine(DATABASE_URL)
|
|
|
|
# Test connection
|
|
with engine.connect() as conn:
|
|
conn.execute(text("SELECT 1"))
|
|
print("✓ Database connection successful")
|
|
print()
|
|
|
|
# Check if users table exists
|
|
inspector = inspect(engine)
|
|
if 'users' not in inspector.get_table_names():
|
|
print("✗ 'users' table does not exist. Please run migrations first.")
|
|
return 1
|
|
|
|
print("Checking and adding missing columns to 'users' table...")
|
|
print()
|
|
|
|
# List of columns that should exist based on the User model
|
|
# These are the columns that are commonly missing after migration issues
|
|
columns_to_add = [
|
|
{
|
|
'name': 'password_hash',
|
|
'type': 'VARCHAR(255)',
|
|
'nullable': True,
|
|
'default': None
|
|
},
|
|
{
|
|
'name': 'password_change_required',
|
|
'type': 'BOOLEAN',
|
|
'nullable': False,
|
|
'default': 'false'
|
|
},
|
|
{
|
|
'name': 'email',
|
|
'type': 'VARCHAR(200)',
|
|
'nullable': True,
|
|
'default': None
|
|
},
|
|
{
|
|
'name': 'full_name',
|
|
'type': 'VARCHAR(200)',
|
|
'nullable': True,
|
|
'default': None
|
|
},
|
|
{
|
|
'name': 'theme_preference',
|
|
'type': 'VARCHAR(10)',
|
|
'nullable': True,
|
|
'default': None
|
|
},
|
|
{
|
|
'name': 'preferred_language',
|
|
'type': 'VARCHAR(8)',
|
|
'nullable': True,
|
|
'default': None
|
|
},
|
|
{
|
|
'name': 'oidc_sub',
|
|
'type': 'VARCHAR(255)',
|
|
'nullable': True,
|
|
'default': None
|
|
},
|
|
{
|
|
'name': 'oidc_issuer',
|
|
'type': 'VARCHAR(255)',
|
|
'nullable': True,
|
|
'default': None
|
|
},
|
|
{
|
|
'name': 'avatar_filename',
|
|
'type': 'VARCHAR(255)',
|
|
'nullable': True,
|
|
'default': None
|
|
},
|
|
{
|
|
'name': 'email_notifications',
|
|
'type': 'BOOLEAN',
|
|
'nullable': False,
|
|
'default': 'true'
|
|
},
|
|
{
|
|
'name': 'notification_overdue_invoices',
|
|
'type': 'BOOLEAN',
|
|
'nullable': False,
|
|
'default': 'true'
|
|
},
|
|
{
|
|
'name': 'notification_task_assigned',
|
|
'type': 'BOOLEAN',
|
|
'nullable': False,
|
|
'default': 'true'
|
|
},
|
|
{
|
|
'name': 'notification_task_comments',
|
|
'type': 'BOOLEAN',
|
|
'nullable': False,
|
|
'default': 'true'
|
|
},
|
|
{
|
|
'name': 'notification_weekly_summary',
|
|
'type': 'BOOLEAN',
|
|
'nullable': False,
|
|
'default': 'false'
|
|
},
|
|
{
|
|
'name': 'timezone',
|
|
'type': 'VARCHAR(50)',
|
|
'nullable': True,
|
|
'default': None
|
|
},
|
|
{
|
|
'name': 'date_format',
|
|
'type': 'VARCHAR(20)',
|
|
'nullable': False,
|
|
'default': "'YYYY-MM-DD'"
|
|
},
|
|
{
|
|
'name': 'time_format',
|
|
'type': 'VARCHAR(10)',
|
|
'nullable': False,
|
|
'default': "'24h'"
|
|
},
|
|
{
|
|
'name': 'week_start_day',
|
|
'type': 'INTEGER',
|
|
'nullable': False,
|
|
'default': '1'
|
|
},
|
|
{
|
|
'name': 'time_rounding_enabled',
|
|
'type': 'BOOLEAN',
|
|
'nullable': False,
|
|
'default': 'true'
|
|
},
|
|
{
|
|
'name': 'time_rounding_minutes',
|
|
'type': 'INTEGER',
|
|
'nullable': False,
|
|
'default': '1'
|
|
},
|
|
{
|
|
'name': 'time_rounding_method',
|
|
'type': 'VARCHAR(10)',
|
|
'nullable': False,
|
|
'default': "'nearest'"
|
|
},
|
|
{
|
|
'name': 'standard_hours_per_day',
|
|
'type': 'FLOAT',
|
|
'nullable': False,
|
|
'default': '8.0'
|
|
},
|
|
{
|
|
'name': 'client_portal_enabled',
|
|
'type': 'BOOLEAN',
|
|
'nullable': False,
|
|
'default': 'false'
|
|
},
|
|
{
|
|
'name': 'client_id',
|
|
'type': 'INTEGER',
|
|
'nullable': True,
|
|
'default': None
|
|
},
|
|
]
|
|
|
|
added_count = 0
|
|
for col in columns_to_add:
|
|
if add_column_if_missing(
|
|
engine,
|
|
'users',
|
|
col['name'],
|
|
col['type'],
|
|
col['nullable'],
|
|
col['default']
|
|
):
|
|
added_count += 1
|
|
|
|
print()
|
|
print("=" * 60)
|
|
if added_count > 0:
|
|
print(f"✓ Successfully added {added_count} missing column(s)")
|
|
else:
|
|
print("✓ All columns already exist")
|
|
print("=" * 60)
|
|
|
|
return 0
|
|
|
|
except Exception as e:
|
|
print(f"✗ Error: {e}")
|
|
import traceback
|
|
traceback.print_exc()
|
|
return 1
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|