import os import click from flask import Flask, send_from_directory from flask_cors import CORS from flask_migrate import Migrate from flask_socketio import SocketIO from backend.models import db from backend.config import config # Initialize extensions migrate = Migrate() socketio = SocketIO(cors_allowed_origins="*", async_mode='eventlet') def create_app(config_name=None): """Flask application factory""" if config_name is None: config_name = os.environ.get('FLASK_ENV', 'development') app = Flask(__name__, static_folder=os.path.join(os.path.dirname(__file__), 'static'), static_url_path='/static') # Load configuration app.config.from_object(config[config_name]) # Configure session for OAuth state management app.config['SESSION_COOKIE_SAMESITE'] = 'Lax' # Allow cross-site on redirects app.config['SESSION_COOKIE_HTTPONLY'] = True # Ensure required directories exist os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True) os.makedirs(app.config['AUDIO_FOLDER'], exist_ok=True) # Ensure database instance directory exists db_path = app.config['SQLALCHEMY_DATABASE_URI'].replace('sqlite:///', '') if db_path and db_path != ':memory:': os.makedirs(os.path.dirname(db_path), exist_ok=True) # Initialize extensions db.init_app(app) migrate.init_app(app, db) socketio.init_app(app) CORS(app, resources={r"/api/*": {"origins": app.config['CORS_ORIGINS']}}, supports_credentials=True) # Initialize OAuth/OIDC from backend.auth import init_oauth init_oauth(app) # Register blueprints from backend.routes import questions, games, teams, admin, categories, download_jobs, auth, export_import app.register_blueprint(auth.bp) app.register_blueprint(questions.bp) app.register_blueprint(games.bp) app.register_blueprint(teams.bp) app.register_blueprint(admin.bp) app.register_blueprint(categories.bp) app.register_blueprint(download_jobs.bp) app.register_blueprint(export_import.bp) # Register socket events from backend.sockets import events # Serve React frontend in production @app.route('/', defaults={'path': ''}) @app.route('/') def serve_frontend(path): """Serve React frontend""" if path and os.path.exists(os.path.join(app.static_folder, path)): return send_from_directory(app.static_folder, path) elif path.startswith('api/'): # API routes should 404 if not found return {'error': 'Not found'}, 404 else: # Serve index.html for all other routes (React Router) index_path = os.path.join(app.static_folder, 'index.html') if os.path.exists(index_path): return send_from_directory(app.static_folder, 'index.html') else: return {'message': 'Trivia Game API', 'status': 'running'}, 200 # Health check endpoint @app.route('/api/health') def health_check(): """Health check endpoint""" return {'status': 'ok', 'message': 'Trivia Game API is running'}, 200 @app.cli.command('transfer-questions') @click.argument('user_id', type=int) def transfer_questions(user_id): """Transfer ownership of ALL questions to the specified user. USER_ID is the database ID of the target user. """ from backend.models import User, Question user = User.query.get(user_id) if not user: click.echo(f"Error: No user found with id {user_id}") click.echo("Available users:") for u in User.query.all(): click.echo(f" - id={u.id}, name={u.name or u.preferred_username}") raise SystemExit(1) display_name = user.name or user.preferred_username count = Question.query.count() if count == 0: click.echo("No questions found in the database.") return click.echo(f"This will transfer ownership of {count} question(s) to '{display_name}' (id={user.id}).") if not click.confirm("Proceed?"): click.echo("Aborted.") return Question.query.update({Question.created_by: user.id}) db.session.commit() click.echo(f"Successfully transferred {count} question(s) to '{display_name}'.") return app