Files
TimeTracker/app/__init__.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

212 lines
7.1 KiB
Python

import os
import logging
from datetime import timedelta
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
from flask_login import LoginManager
from flask_socketio import SocketIO
from dotenv import load_dotenv
import re
from jinja2 import ChoiceLoader, FileSystemLoader
# Load environment variables
load_dotenv()
# Initialize extensions
db = SQLAlchemy()
migrate = Migrate()
login_manager = LoginManager()
socketio = SocketIO()
def create_app(config=None):
"""Application factory pattern"""
app = Flask(__name__)
# Configuration
app.config.from_object('app.config.Config')
if config:
app.config.update(config)
# Add top-level templates directory in addition to app/templates
extra_templates_path = os.path.abspath(
os.path.join(app.root_path, '..', 'templates')
)
app.jinja_loader = ChoiceLoader([
app.jinja_loader,
FileSystemLoader(extra_templates_path)
])
# Prefer Postgres if POSTGRES_* envs are present but URL points to SQLite
current_url = app.config.get('SQLALCHEMY_DATABASE_URI', '')
if (
not app.config.get('TESTING')
and isinstance(current_url, str)
and current_url.startswith('sqlite')
and (
os.getenv('POSTGRES_DB')
or os.getenv('POSTGRES_USER')
or os.getenv('POSTGRES_PASSWORD')
)
):
pg_user = os.getenv('POSTGRES_USER', 'timetracker')
pg_pass = os.getenv('POSTGRES_PASSWORD', 'timetracker')
pg_db = os.getenv('POSTGRES_DB', 'timetracker')
pg_host = os.getenv('POSTGRES_HOST', 'db')
app.config['SQLALCHEMY_DATABASE_URI'] = (
f'postgresql+psycopg2://{pg_user}:{pg_pass}@{pg_host}:5432/{pg_db}'
)
# Initialize extensions
db.init_app(app)
migrate.init_app(app, db)
login_manager.init_app(app)
socketio.init_app(app, cors_allowed_origins="*")
# Log effective database URL (mask password)
db_url = app.config.get('SQLALCHEMY_DATABASE_URI', '')
try:
masked_db_url = re.sub(r"//([^:]+):[^@]+@", r"//\\1:***@", db_url)
except Exception:
masked_db_url = db_url
app.logger.info(f"Using database URL: {masked_db_url}")
# Configure login manager
login_manager.login_view = 'auth.login'
login_manager.login_message = 'Please log in to access this page.'
login_manager.login_message_category = 'info'
# Configure session
app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(
seconds=int(os.getenv('PERMANENT_SESSION_LIFETIME', 86400))
)
# Setup logging
setup_logging(app)
# Register blueprints
from app.routes.auth import auth_bp
from app.routes.main import main_bp
from app.routes.projects import projects_bp
from app.routes.timer import timer_bp
from app.routes.reports import reports_bp
from app.routes.admin import admin_bp
from app.routes.api import api_bp
app.register_blueprint(auth_bp)
app.register_blueprint(main_bp)
app.register_blueprint(projects_bp)
app.register_blueprint(timer_bp)
app.register_blueprint(reports_bp)
app.register_blueprint(admin_bp)
app.register_blueprint(api_bp)
# Register error handlers
from app.utils.error_handlers import register_error_handlers
register_error_handlers(app)
# Register context processors
from app.utils.context_processors import register_context_processors
register_context_processors(app)
# Register template filters
from app.utils.template_filters import register_template_filters
register_template_filters(app)
# Register CLI commands
from app.utils.cli import register_cli_commands
register_cli_commands(app)
# Initialize database on first request
def initialize_database():
try:
# Import models to ensure they are registered
from app.models import User, Project, TimeEntry, Settings
# Create database tables
db.create_all()
# Create default admin user if it doesn't exist
admin_username = app.config.get('ADMIN_USERNAMES', ['admin'])[0]
if not User.query.filter_by(username=admin_username).first():
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}")
print("Database initialized successfully")
except Exception as e:
print(f"Error initializing database: {e}")
# Don't raise the exception, just log it
# Store the initialization function for later use
app.initialize_database = initialize_database
return app
def setup_logging(app):
"""Setup application logging"""
log_level = os.getenv('LOG_LEVEL', 'INFO')
log_file = os.getenv('LOG_FILE')
# Prepare handlers
handlers = [logging.StreamHandler()]
# Add file handler if log_file is specified
if log_file:
try:
# Ensure log directory exists
log_dir = os.path.dirname(log_file)
if log_dir and not os.path.exists(log_dir):
os.makedirs(log_dir, exist_ok=True)
# Test if we can write to the log file
file_handler = logging.FileHandler(log_file)
handlers.append(file_handler)
except (PermissionError, OSError) as e:
print(f"Warning: Could not create log file '{log_file}': {e}")
print("Logging to console only")
# Don't add file handler, just use console logging
# Configure logging
logging.basicConfig(
level=getattr(logging, log_level.upper()),
format='%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]',
handlers=handlers
)
# Suppress Werkzeug logs in production
if not app.debug:
logging.getLogger('werkzeug').setLevel(logging.ERROR)
def init_database(app):
"""Initialize database tables and create default admin user"""
with app.app_context():
try:
# Import models to ensure they are registered
from app.models import User, Project, TimeEntry, Settings
# Create database tables
db.create_all()
# Create default admin user if it doesn't exist
admin_username = app.config.get('ADMIN_USERNAMES', ['admin'])[0]
if not User.query.filter_by(username=admin_username).first():
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}")
print("Database initialized successfully")
except Exception as e:
print(f"Error initializing database: {e}")
raise