Spaces:
Running
Running
File size: 5,054 Bytes
2d3a0d2 6594ddb 954e0aa b65231d 9ab1911 962a395 8c7a11b 962a395 3b6b3bd 962a395 8c7a11b 6f77435 6ee4b4a 954e0aa b65231d 28227c9 37bcb58 28227c9 2d3a0d2 7b90b65 954e0aa 373441d 6f77435 b65231d 962a395 93ff956 ee6424d 962a395 ee6424d 962a395 463e7aa 1f8bc1c 954e0aa 1f8bc1c 954e0aa 1f8bc1c 463e7aa 1f8bc1c 962a395 463e7aa 962a395 2d3a0d2 962a395 3b6b3bd 962a395 8c7a11b 6f77435 8c7a11b 962a395 6ee4b4a 6594ddb | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 | import os
import sys
current_dir = os.path.dirname(os.path.abspath(__file__))
parent_dir = os.path.dirname(current_dir)
if parent_dir not in sys.path:
sys.path.insert(0, parent_dir)
from src.logger import setup_logging, logger
setup_logging()
from contextlib import asynccontextmanager
from fastapi import FastAPI, Depends
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import RedirectResponse
from src.materials.routes import router as materials_router
from src.summary_generator.routes import router as summary_router
from src.rag.routes import router as tutor_router, ws_router
from src.quiz_generator.routes import router as quiz_router
from src.auth.routes import router as auth_router
from src.asr.routes import router as asr_router
from src.store import get_usage
from src.dependencies import get_current_user_id
from src.config import settings
@asynccontextmanager
async def lifespan(app: FastAPI):
# Run pending Alembic database migrations automatically on server startup
try:
import subprocess
logger.info("Running database migrations via Alembic...")
subprocess.run(["alembic", "upgrade", "head"], check=True)
logger.info("Database migrations completed successfully.")
except Exception as e:
logger.warning(f"Database migration step failed or skipped: {e}")
try:
from src.database import warmup_database
warmup_database()
except Exception as e:
logger.warning(f"Database warmup failed: {e}")
try:
from src.rag.rag import get_embedder
get_embedder()
logger.info("Embedder loaded successfully.")
except Exception as e:
logger.warning(f"Embedder failed to load: {e}")
# Eagerly load ASR models so warmup runs at startup, not on first request
try:
from src.asr.models import get_audio_model_en
get_audio_model_en()
except Exception as e:
logger.warning(f"English ASR model failed to load: {e}")
try:
from src.asr.models import get_audio_model_ar
get_audio_model_ar()
except Exception as e:
logger.warning(f"Arabic ASR model failed to load: {e}")
from src.rag.batch_workers import start_workers
start_workers()
from src.asr.batch_workers import start_asr_workers
start_asr_workers()
yield
from uvicorn.middleware.proxy_headers import ProxyHeadersMiddleware
app = FastAPI(
title="AI Tutor API",
description="Backend API for the AI Tutor for Students application",
version="1.0.0",
lifespan=lifespan,
)
# When allow_credentials=True, browsers REJECT responses with "Access-Control-Allow-Origin: *"
# and refuse to store or send cookies. We must always use explicit origins.
_DEFAULT_ORIGINS = [
"https://www.studybuddyai.dev",
"https://studybuddyai.dev",
"https://hamdy005-study-buddy.hf.space",
"http://localhost:3000",
"http://localhost:3001",
]
_raw_origins = settings.cors_allowed_origins if settings.cors_allowed_origins else _DEFAULT_ORIGINS
# Remove '*' if present to avoid browser credential rejection
_cors_origins = [o.strip() for o in _raw_origins if o.strip() and o.strip() != "*"] or _DEFAULT_ORIGINS
@app.middleware("http")
async def log_request_timing(request, call_next):
import time
start = time.perf_counter()
# Fix double slashes in paths (e.g., //api/usage -> /api/usage)
path = request.scope.get("path")
if path and "//" in path:
request.scope["path"] = path.replace("//", "/")
response = await call_next(request)
duration_sec = time.perf_counter() - start
status = response.status_code
if 200 <= status < 300:
status_str = f"<green>{status}</green>"
elif 300 <= status < 400:
status_str = f"<cyan>{status}</cyan>"
elif 400 <= status < 500:
status_str = f"<red>{status}</red>"
else:
status_str = f"<bold><red>{status}</red></bold>"
logger.opt(colors=True).info(f"{request.method} {request.url.path} - {status_str} ({duration_sec:.2f}s)")
return response
app.add_middleware(ProxyHeadersMiddleware, trusted_hosts="*")
# CORSMiddleware MUST be added LAST so it becomes the outermost layer in Starlette's middleware stack.
app.add_middleware(
CORSMiddleware,
allow_origins=_cors_origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
app.include_router(materials_router)
app.include_router(summary_router)
app.include_router(tutor_router)
app.include_router(ws_router)
app.include_router(quiz_router)
app.include_router(auth_router)
app.include_router(asr_router)
@app.get("/")
async def root():
return RedirectResponse(url="/docs")
@app.get("/api/health")
async def health_check():
return {"status": "ok", "service": "AI Tutor API"}
@app.get("/api/usage")
async def get_user_usage(user_id: str = Depends(get_current_user_id)):
return get_usage(user_id)
if __name__ == "__main__":
import uvicorn
uvicorn.run("src.main:app", host="0.0.0.0", port=8000, reload=True)
|