1ST
This commit is contained in:
160
backend/app.py
Normal file
160
backend/app.py
Normal file
@@ -0,0 +1,160 @@
|
||||
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=5000, debug=True)
|
Reference in New Issue
Block a user