""" Employee Workstation Activity Tracking - Flask Application Factory This module provides the application factory function 'create_app' that initializes the Flask application with its configuration, database connection, and registered blueprints. """ import logging import os from logging.handlers import RotatingFileHandler from dotenv import load_dotenv from flask import Flask from flask_sqlalchemy import SQLAlchemy from sqlalchemy import event # Initialize SQLAlchemy globally to avoid circular imports db = SQLAlchemy() def create_app(test_config=None): """Create and configure the Flask application using the factory pattern.""" # Load environment variables load_dotenv() # Try .env first config_env_path = os.path.join( os.path.dirname(os.path.dirname(__file__)), "config.env" ) # Defer logging for config.env until logger is configured config_env_loaded_message = None config_env_warning_message = None if os.path.exists(config_env_path): load_dotenv(config_env_path) config_env_loaded_message = ( f"Loaded environment variables from {config_env_path}" ) else: config_env_warning_message = ( f"Warning: config.env file not found at {config_env_path}" ) # Get the project root directory (parent of app directory) project_root = os.path.dirname(os.path.dirname(__file__)) # Create and configure the app with template folder in project root app = Flask( __name__, instance_relative_config=True, template_folder=os.path.join(project_root, "templates"), static_folder=os.path.join(project_root, "static"), ) # Default configuration app.config.from_mapping( SECRET_KEY=os.environ.get("SECRET_KEY", "dev"), SQLALCHEMY_DATABASE_URI=os.environ.get( "DATABASE_URI", os.environ.get("DATABASE_URL") ), SQLALCHEMY_TRACK_MODIFICATIONS=False, ) # Override configuration with test config if provided if test_config is not None: app.config.update(test_config) # Ensure DATABASE_URI is set if not app.config["SQLALCHEMY_DATABASE_URI"]: raise ValueError( "DATABASE_URI or DATABASE_URL environment variable must be set" ) # Ensure the instance folder exists try: os.makedirs(app.instance_path) except OSError: pass # Configure logging if not app.debug: # Avoid duplicate logs in debug mode log_formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s") log_handler = RotatingFileHandler( os.path.join(app.instance_path, "server.log"), maxBytes=1024 * 1024 * 5, # 5 MB backupCount=5, ) log_handler.setFormatter(log_formatter) log_handler.setLevel(logging.INFO) app.logger.addHandler(log_handler) app.logger.setLevel(logging.INFO) # Log config.env messages now that logger is available if config_env_loaded_message: app.logger.info(config_env_loaded_message) if config_env_warning_message: app.logger.warning(config_env_warning_message) app.logger.info("Flask application starting up...") # Initialize the database with the app db.init_app(app) # Set up event listener for PostgreSQL timezone with app.app_context(): if "postgresql" in app.config["SQLALCHEMY_DATABASE_URI"]: @event.listens_for(db.engine, "connect") def set_timezone(dbapi_connection, connection_record): cursor = dbapi_connection.cursor() cursor.execute("SET timezone TO 'Asia/Dubai';") cursor.close() app.logger.info("Set PostgreSQL session timezone to 'Asia/Dubai' (UTC+4)") app.logger.info("Note: Application uses UTC internally, database is set to Asia/Dubai time.") # Import and register blueprints from app.api import events_bp, reports_bp app.register_blueprint(events_bp) app.register_blueprint(reports_bp) from app.views.dashboard import views_bp app.register_blueprint(views_bp) # Register error handlers from app.errors import register_error_handlers register_error_handlers(app) # Initialize database tables when in development @app.cli.command("init-db") def init_db_command(): """Clear existing data and create new tables.""" with app.app_context(): db.create_all() app.logger.info("Database tables created") @app.route("/healthcheck") def healthcheck(): return {"status": "ok"}, 200 # Initialize and start the scheduler from app.scheduler import init_scheduler init_scheduler(app) # Register custom CLI commands from app.cli import register_cli_commands register_cli_commands(app) return app def init_db(app_instance): """Initialize the database outside of the CLI context using a provided app instance.""" # app = create_app() # No longer creating app here, using provided instance with app_instance.app_context(): db.create_all() app_instance.logger.info("Database initialized via init_db function")