import os import logging from logging.handlers import RotatingFileHandler from flask import Flask from werkzeug.middleware.proxy_fix import ProxyFix from flask_sqlalchemy import SQLAlchemy from flask_login import LoginManager from flask_mail import Mail from flask_migrate import Migrate from flask_socketio import SocketIO from config.config import config db = SQLAlchemy() login_manager= LoginManager() mail = Mail() migrate = Migrate() socketio = SocketIO() def create_app(config_name=None): if config_name is None: config_name = os.environ.get('FLASK_ENV', 'production') app = Flask(__name__, template_folder='templates', static_folder='static') app.config.from_object(config.get(config_name, config['default'])) # ── ProxyFix: trust one nginx proxy hop for real client IPs ─────────────── app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1) # ── Extensions ──────────────────────────────────────────────────────────── db.init_app(app) login_manager.init_app(app) mail.init_app(app) migrate.init_app(app, db) socketio.init_app(app, async_mode='eventlet', cors_allowed_origins='*') login_manager.login_view = 'auth.login' login_manager.login_message = 'Please log in to access this page.' login_manager.login_message_category = 'info' # ── Upload directory ────────────────────────────────────────────────────── upload_dir = app.config['UPLOAD_FOLDER'] # always absolute — set in config.py os.makedirs(upload_dir, exist_ok=True) # ── Logging ─────────────────────────────────────────────────────────────── if not app.debug: os.makedirs('logs', exist_ok=True) file_handler = RotatingFileHandler( 'logs/it_tickets.log', maxBytes=10_485_760, backupCount=10 ) file_handler.setFormatter(logging.Formatter( '%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]' )) file_handler.setLevel(logging.INFO) app.logger.addHandler(file_handler) app.logger.setLevel(logging.INFO) app.logger.info('IT Ticket System startup') # ── Blueprints ──────────────────────────────────────────────────────────── from app.routes.auth import auth_bp from app.routes.tickets import tickets_bp from app.routes.admin import admin_bp from app.routes.api import api_bp from app.routes.chatbot import chatbot_bp app.register_blueprint(auth_bp) app.register_blueprint(tickets_bp) app.register_blueprint(admin_bp) app.register_blueprint(api_bp) app.register_blueprint(chatbot_bp) # ── User loader ─────────────────────────────────────────────────────────── from app.models import User @login_manager.user_loader def load_user(user_id): return User.query.get(int(user_id)) # ── Context processors ──────────────────────────────────────────────────── @app.context_processor def inject_globals(): from flask_login import current_user unread = 0 if current_user.is_authenticated: from app.models import Notification unread = Notification.query.filter_by( user_id=current_user.id, is_read=False ).count() return dict(unread_notifications=unread) # ── DB initialisation (first run) ───────────────────────────────────────── with app.app_context(): db.create_all() _seed_admin(app) return app def _seed_admin(app): """Create the default admin account if none exists.""" from app.models import User, UserRole if User.query.filter_by(role=UserRole.ADMIN).first(): return admin = User( email = app.config['ADMIN_EMAIL'], username = 'admin', full_name = 'System Administrator', role = UserRole.ADMIN, department= 'IT', is_active = True, ) admin.set_password(app.config['ADMIN_PASSWORD']) db.session.add(admin) db.session.commit() app.logger.info(f'[SEED] Default admin account created: {admin.email}')