import os import logging from datetime import datetime from flask import Flask, jsonify from flask_cors import CORS from flask_jwt_extended import JWTManager from jwt.exceptions import InvalidTokenError from flask_migrate import Migrate from flask_mail import Mail from config import config from models import db from utils.logger import setup_logger # Import blueprints from routes.auth import auth_bp from routes.todos import todos_bp from routes.users import users_bp from routes.admin import admin_bp from routes.health import health_bp from routes.reports import reports_bp from routes.excel import excel_bp from routes.notifications import notifications_bp from routes.scheduler import scheduler_bp migrate = Migrate() mail = Mail() jwt = JWTManager() def setup_jwt_error_handlers(jwt): @jwt.expired_token_loader def expired_token_callback(jwt_header, jwt_payload): return jsonify({'msg': 'Token has expired'}), 401 @jwt.invalid_token_loader def invalid_token_callback(error): return jsonify({'msg': 'Invalid token'}), 401 @jwt.unauthorized_loader def missing_token_callback(error): return jsonify({'msg': 'Missing Authorization Header'}), 401 def create_app(config_name=None): if config_name is None: config_name = os.environ.get('FLASK_ENV', 'development') app = Flask(__name__) app.config.from_object(config[config_name]) # Initialize extensions db.init_app(app) migrate.init_app(app, db) mail.init_app(app) jwt.init_app(app) # Setup CORS CORS(app, origins=app.config['CORS_ORIGINS'], methods=['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'], allow_headers=['Content-Type', 'Authorization'], supports_credentials=True, expose_headers=['Content-Type', 'Authorization']) # Setup logging setup_logger(app) # Setup JWT error handlers setup_jwt_error_handlers(jwt) # Register blueprints app.register_blueprint(auth_bp, url_prefix='/api/auth') app.register_blueprint(todos_bp, url_prefix='/api/todos') app.register_blueprint(users_bp, url_prefix='/api/users') app.register_blueprint(admin_bp, url_prefix='/api/admin') app.register_blueprint(health_bp, url_prefix='/api/health') app.register_blueprint(reports_bp, url_prefix='/api/reports') app.register_blueprint(excel_bp, url_prefix='/api/excel') app.register_blueprint(notifications_bp, url_prefix='/api/notifications') app.register_blueprint(scheduler_bp, url_prefix='/api/scheduler') # Register error handlers register_error_handlers(app) # Create tables with app.app_context(): db.create_all() return app def register_error_handlers(app): @app.errorhandler(400) def bad_request(error): return jsonify({'error': 'Bad Request', 'message': str(error)}), 400 @app.errorhandler(401) def unauthorized(error): return jsonify({'error': 'Unauthorized', 'message': 'Authentication required'}), 401 @app.errorhandler(403) def forbidden(error): return jsonify({'error': 'Forbidden', 'message': 'Access denied'}), 403 @app.errorhandler(404) def not_found(error): return jsonify({'error': 'Not Found', 'message': 'Resource not found'}), 404 @app.errorhandler(500) def internal_error(error): db.session.rollback() app.logger.error(f"Internal error: {error}") return jsonify({'error': 'Internal Server Error', 'message': 'An error occurred'}), 500 # Database connection error handlers from sqlalchemy.exc import OperationalError, DisconnectionError, TimeoutError from pymysql.err import OperationalError as PyMySQLOperationalError, Error as PyMySQLError @app.errorhandler(OperationalError) def handle_db_operational_error(error): db.session.rollback() app.logger.error(f"Database operational error: {error}") # Check if it's a connection timeout or server unavailable error error_str = str(error) if 'timed out' in error_str or 'Lost connection' in error_str or "Can't connect" in error_str: return jsonify({ 'error': 'Database Connection Error', 'message': '資料庫連線暫時不穩定,請稍後再試' }), 503 return jsonify({ 'error': 'Database Error', 'message': '資料庫操作失敗,請稍後再試' }), 500 @app.errorhandler(DisconnectionError) def handle_db_disconnection_error(error): db.session.rollback() app.logger.error(f"Database disconnection error: {error}") return jsonify({ 'error': 'Database Connection Lost', 'message': '資料庫連線中斷,正在重新連線,請稍後再試' }), 503 @app.errorhandler(TimeoutError) def handle_db_timeout_error(error): db.session.rollback() app.logger.error(f"Database timeout error: {error}") return jsonify({ 'error': 'Database Timeout', 'message': '資料庫操作超時,請稍後再試' }), 504 @app.errorhandler(Exception) def handle_exception(error): db.session.rollback() app.logger.error(f"Unhandled exception: {error}", exc_info=True) return jsonify({'error': 'Server Error', 'message': 'An unexpected error occurred'}), 500 if __name__ == '__main__': app = create_app() app.run(host='0.0.0.0', port=12011, debug=True)