Files
TimeTracker/docker/init-database.py
Dries Peeters 1b3a703c04 feat: comprehensive project cleanup and timezone enhancement
- Remove redundant documentation files (DATABASE_INIT_FIX_*.md, TIMEZONE_FIX_README.md)
- Delete unused Docker files (Dockerfile.test, Dockerfile.combined, docker-compose.yml)
- Remove obsolete deployment scripts (deploy.sh) and unused files (index.html, _config.yml)
- Clean up logs directory (remove 2MB timetracker.log, keep .gitkeep)
- Remove .pytest_cache directory

- Consolidate Docker setup to two main container types:
  * Simple container (recommended for production)
  * Public container (for development/testing)

- Enhance timezone support in admin settings:
  * Add 100+ timezone options organized by region
  * Implement real-time timezone preview with current time display
  * Add timezone offset calculation and display
  * Remove search functionality for cleaner interface
  * Update timezone utility functions for database-driven configuration

- Update documentation:
  * Revise README.md to reflect current project state
  * Add comprehensive timezone features documentation
  * Update Docker deployment instructions
  * Create PROJECT_STRUCTURE.md for project overview
  * Remove references to deleted files

- Improve project structure:
  * Streamlined file organization
  * Better maintainability and focus
  * Preserved all essential functionality
  * Cleaner deployment options
2025-08-28 14:52:09 +02:00

176 lines
6.1 KiB
Python

#!/usr/bin/env python3
"""
Database initialization script for TimeTracker
This script checks if the database is connected and initialized,
and initializes it if needed.
"""
import os
import sys
import time
import traceback
from sqlalchemy import create_engine, text, inspect
from sqlalchemy.exc import OperationalError, ProgrammingError
def wait_for_database(url, max_attempts=30, delay=2):
"""Wait for database to be ready"""
print(f"Waiting for database to be ready...")
for attempt in range(max_attempts):
try:
engine = create_engine(url, pool_pre_ping=True)
with engine.connect() as conn:
conn.execute(text("SELECT 1"))
print("Database connection established successfully")
return engine
except Exception as e:
print(f"Waiting for database... (attempt {attempt+1}/{max_attempts}): {e}")
if attempt < max_attempts - 1:
time.sleep(delay)
else:
print("Database not ready after waiting, exiting...")
sys.exit(1)
return None
def check_database_initialization(engine):
"""Check if database is initialized by looking for required tables"""
print("Checking if database is initialized...")
try:
inspector = inspect(engine)
# Check if our main tables exist
existing_tables = inspector.get_table_names()
required_tables = ['users', 'projects', 'time_entries', 'settings']
missing_tables = [table for table in required_tables if table not in existing_tables]
if missing_tables:
print(f"Database not fully initialized. Missing tables: {missing_tables}")
return False
else:
print("Database is already initialized with all required tables")
return True
except Exception as e:
print(f"Error checking database initialization: {e}")
print(f"Traceback: {traceback.format_exc()}")
return False
def initialize_database(engine):
"""Initialize database using Flask CLI command"""
print("Initializing database...")
try:
# Set environment variables for Flask
os.environ['FLASK_APP'] = 'app'
os.environ['FLASK_ENV'] = 'production'
print("Importing Flask app...")
# Import Flask app and initialize database
from app import create_app, db
from app.models import User, Project, TimeEntry, Settings
print("Creating Flask app...")
app = create_app()
print("Setting up app context...")
with app.app_context():
print("Creating all tables...")
# Create all tables
db.create_all()
print("Verifying tables were created...")
# Verify tables were created
inspector = inspect(engine)
existing_tables = inspector.get_table_names()
print(f"Tables after creation: {existing_tables}")
# Create default admin user if it doesn't exist
admin_username = os.getenv('ADMIN_USERNAMES', 'admin').split(',')[0]
print(f"Checking for admin user: {admin_username}")
if not User.query.filter_by(username=admin_username).first():
print("Creating admin user...")
admin_user = User(
username=admin_username,
role='admin'
)
admin_user.is_active = True
db.session.add(admin_user)
db.session.commit()
print(f"Created default admin user: {admin_username}")
else:
print(f"Admin user {admin_username} already exists")
# Create default settings if they don't exist
print("Checking for default settings...")
if not Settings.query.first():
print("Creating default settings...")
settings = Settings()
db.session.add(settings)
db.session.commit()
print("Created default settings")
else:
print("Default settings already exist")
# Create default project if it doesn't exist
print("Checking for default project...")
if not Project.query.first():
print("Creating default project...")
project = Project(
name='General',
client='Default Client',
description='Default project for general tasks',
billable=True,
status='active'
)
db.session.add(project)
db.session.commit()
print("Created default project")
else:
print("Default project already exists")
print("Database initialized successfully")
return True
except Exception as e:
print(f"Error initializing database: {e}")
print(f"Traceback: {traceback.format_exc()}")
return False
def main():
"""Main function"""
url = os.getenv("DATABASE_URL", "")
if not url.startswith("postgresql"):
print("No PostgreSQL database configured, skipping initialization")
return
print(f"Database URL: {url}")
# Wait for database to be ready
engine = wait_for_database(url)
# Check if database is initialized
if not check_database_initialization(engine):
# Initialize database
if initialize_database(engine):
print("Database initialization completed successfully")
# Verify initialization worked
if check_database_initialization(engine):
print("Database verification successful")
else:
print("Database verification failed - tables still missing")
sys.exit(1)
else:
print("Database initialization failed")
sys.exit(1)
else:
print("Database already initialized, no action needed")
if __name__ == "__main__":
main()