Files
gh-christianlouis-leagueledger/app/main.py
T

139 lines
4.9 KiB
Python

#!/usr/bin/env python3
from fastapi import FastAPI, Request, Depends, Form
from fastapi.staticfiles import StaticFiles
from fastapi.responses import HTMLResponse, RedirectResponse
from fastapi.templating import Jinja2Templates
from fastapi.middleware.cors import CORSMiddleware
from pathlib import Path
import os
from starlette.middleware.sessions import SessionMiddleware
from starlette.middleware.authentication import AuthenticationMiddleware
from dotenv import load_dotenv
import logging
import contextlib
from .db import engine, get_db
from . import models
from .templates_config import templates
from .views import qr, redeem, teams, admin, leaderboard, dashboard, static, pages, auth, convenience, setup
from .db_init import init_db
from .auth.middleware import SessionAuthBackend, on_auth_error
# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# Load environment variables
load_dotenv()
# Create the FastAPI application
app = FastAPI(title="LeagueLedger")
# CORS middleware
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # In production, replace with specific origins
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Get secret key for the session
SECRET_KEY = os.getenv("SECRET_KEY", "a-very-secure-secret-key-for-development")
# Important: Order of middleware matters!
# First add AuthenticationMiddleware
app.add_middleware(
AuthenticationMiddleware,
backend=SessionAuthBackend(),
on_error=on_auth_error
)
# Then add SessionMiddleware (last added = first executed)
app.add_middleware(SessionMiddleware, secret_key=SECRET_KEY)
# Initialize database on startup
@app.on_event("startup")
async def startup_db_client():
logger.info("Starting database initialization")
try:
# Import models to ensure they're registered with Base before initialization
from . import models
# Initialize database (applies migrations and seeds data)
init_db()
logger.info("Database initialized and migrated successfully")
except Exception as e:
logger.error(f"Database initialization error: {str(e)}")
# We continue even if there was an error, as the application might still work with partial functionality
# Mount static files
app.mount("/static", StaticFiles(directory="app/static"), name="static")
# Configure static files
static.configure_static_files(app)
# Setup Jinja2 templates
templates = Jinja2Templates(directory="app/templates")
# User context middleware
@app.middleware("http")
async def add_template_globals(request: Request, call_next):
"""Add global variables to all templates."""
try:
# Set user in request state for templates
if hasattr(request, "session"):
user_id = request.session.get("user_id")
if user_id:
# Get a database session
from sqlalchemy.orm import Session
from .db import SessionLocal
from .models import User
db = SessionLocal()
try:
# Fetch actual user from database
user = db.query(User).filter(User.id == user_id).first()
if user:
request.state.user = user
else:
request.state.user = None
finally:
db.close()
else:
request.state.user = None
else:
request.state.user = None
except Exception as e:
# Log error but continue processing
logger.error(f"Error setting template globals: {str(e)}")
request.state.user = None
# Continue with request
response = await call_next(request)
return response
# Handle exceptions
@app.exception_handler(404)
async def not_found_exception_handler(request: Request, exc):
"""Handle 404 errors with a custom template."""
return templates.TemplateResponse(
"error.html",
{"request": request, "error": "Page not found"},
status_code=404
)
# Routers
app.include_router(setup.router, tags=["Setup"]) # Setup router for initial admin setup
app.include_router(pages.router, tags=["Pages"]) # Pages router for index and static pages
app.include_router(auth.router, prefix="/auth", tags=["auth"]) # Include the auth router
app.include_router(qr.router, prefix="/qr", tags=["QR"])
app.include_router(redeem.router, prefix="/redeem", tags=["Redeem"])
app.include_router(teams.router, prefix="/teams", tags=["teams"])
app.include_router(admin.router, prefix="/admin", tags=["Admin"])
app.include_router(leaderboard.router, prefix="/leaderboard", tags=["leaderboard"])
app.include_router(dashboard.router, prefix="/dashboard", tags=["Dashboard"])
app.include_router(static.router, tags=["Static"]) # Include the static router
app.include_router(convenience.router, tags=["Convenience"]) # Include convenience routes