Files
2025-09-03 17:08:22 +08:00

160 lines
5.5 KiB
Python

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)