
- Set up project structure with FastAPI and SQLite - Implement user authentication with JWT - Create models for learning content (subjects, lessons, quizzes) - Add progress tracking and gamification features - Implement comprehensive API documentation - Add error handling and validation - Set up proper logging and health check endpoint
188 lines
5.1 KiB
Python
188 lines
5.1 KiB
Python
#!/usr/bin/env python3
|
|
from __future__ import annotations
|
|
|
|
from datetime import datetime, timezone
|
|
from typing import Any, Dict
|
|
import os
|
|
import traceback
|
|
|
|
import uvicorn
|
|
from fastapi import FastAPI, Request
|
|
from fastapi.exceptions import RequestValidationError
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
from fastapi.responses import JSONResponse
|
|
from starlette.exceptions import HTTPException as StarletteHTTPException
|
|
|
|
from app.api.v1.api import api_router
|
|
from app.core.config import settings
|
|
from app.core.exceptions import BaseAPIException
|
|
|
|
app = FastAPI(
|
|
title=settings.PROJECT_NAME,
|
|
description=settings.PROJECT_DESCRIPTION,
|
|
version=settings.VERSION,
|
|
openapi_url="/openapi.json",
|
|
docs_url="/docs",
|
|
redoc_url="/redoc",
|
|
)
|
|
|
|
# Add CORS middleware
|
|
app.add_middleware(
|
|
CORSMiddleware,
|
|
allow_origins=["*"],
|
|
allow_credentials=True,
|
|
allow_methods=["*"],
|
|
allow_headers=["*"],
|
|
)
|
|
|
|
# Add request logging middleware if not in production
|
|
if os.getenv("ENVIRONMENT", "development") != "production":
|
|
from app.core.middleware import RequestLoggingMiddleware
|
|
|
|
app.add_middleware(RequestLoggingMiddleware)
|
|
|
|
|
|
# Exception handlers
|
|
@app.exception_handler(BaseAPIException)
|
|
async def base_api_exception_handler(request: Request, exc: BaseAPIException) -> JSONResponse:
|
|
"""
|
|
Handle custom API exceptions.
|
|
"""
|
|
return JSONResponse(
|
|
status_code=exc.status_code,
|
|
content={
|
|
"error": {
|
|
"code": exc.status_code,
|
|
"message": exc.detail,
|
|
"type": exc.__class__.__name__,
|
|
}
|
|
},
|
|
headers=exc.headers,
|
|
)
|
|
|
|
|
|
@app.exception_handler(RequestValidationError)
|
|
async def validation_exception_handler(request: Request, exc: RequestValidationError) -> JSONResponse:
|
|
"""
|
|
Handle request validation errors.
|
|
"""
|
|
errors = exc.errors()
|
|
error_details = []
|
|
|
|
for error in errors:
|
|
loc = ".".join(str(x) for x in error["loc"])
|
|
error_details.append(
|
|
{
|
|
"field": loc,
|
|
"message": error["msg"],
|
|
"type": error["type"],
|
|
}
|
|
)
|
|
|
|
return JSONResponse(
|
|
status_code=400,
|
|
content={
|
|
"error": {
|
|
"code": 400,
|
|
"message": "Validation Error",
|
|
"type": "ValidationError",
|
|
"details": error_details,
|
|
}
|
|
},
|
|
)
|
|
|
|
|
|
@app.exception_handler(StarletteHTTPException)
|
|
async def http_exception_handler(request: Request, exc: StarletteHTTPException) -> JSONResponse:
|
|
"""
|
|
Handle standard HTTP exceptions.
|
|
"""
|
|
return JSONResponse(
|
|
status_code=exc.status_code,
|
|
content={
|
|
"error": {
|
|
"code": exc.status_code,
|
|
"message": exc.detail,
|
|
"type": "HTTPException",
|
|
}
|
|
},
|
|
headers=exc.headers,
|
|
)
|
|
|
|
|
|
@app.exception_handler(Exception)
|
|
async def general_exception_handler(request: Request, exc: Exception) -> JSONResponse:
|
|
"""
|
|
Handle all other exceptions.
|
|
"""
|
|
# In production, you might want to log the error instead of returning the traceback
|
|
error_detail = str(exc)
|
|
if os.getenv("ENVIRONMENT", "development") == "development":
|
|
error_detail = "".join(traceback.format_exception(type(exc), exc, exc.__traceback__))
|
|
|
|
return JSONResponse(
|
|
status_code=500,
|
|
content={
|
|
"error": {
|
|
"code": 500,
|
|
"message": "Internal Server Error",
|
|
"type": exc.__class__.__name__,
|
|
"detail": error_detail,
|
|
}
|
|
},
|
|
)
|
|
|
|
|
|
# Include API router
|
|
app.include_router(api_router, prefix=settings.API_V1_STR)
|
|
|
|
|
|
@app.get("/")
|
|
async def root() -> Dict[str, Any]:
|
|
"""
|
|
Root endpoint that returns basic service information.
|
|
"""
|
|
return {
|
|
"title": settings.PROJECT_NAME,
|
|
"description": settings.PROJECT_DESCRIPTION,
|
|
"version": settings.VERSION,
|
|
"docs_url": "/docs",
|
|
"redoc_url": "/redoc",
|
|
"openapi_url": "/openapi.json",
|
|
"health_check": "/health",
|
|
"api_base_url": settings.API_V1_STR,
|
|
}
|
|
|
|
|
|
@app.get("/health", tags=["health"])
|
|
async def health_check() -> Dict[str, Any]:
|
|
"""
|
|
Health check endpoint to verify the service is running correctly.
|
|
"""
|
|
try:
|
|
# Check database connection
|
|
from app.db.session import SessionLocal
|
|
|
|
db = SessionLocal()
|
|
db.execute("SELECT 1")
|
|
db.close()
|
|
db_status = "connected"
|
|
except Exception as e:
|
|
# Using a general exception handler with specific error handling
|
|
db_status = f"error: {e!s}"
|
|
|
|
return {
|
|
"status": "healthy",
|
|
"timestamp": datetime.now(tz=timezone.utc).isoformat(),
|
|
"version": settings.VERSION,
|
|
"database": db_status,
|
|
"environment": os.getenv("ENVIRONMENT", "development"),
|
|
}
|
|
|
|
|
|
if __name__ == "__main__":
|
|
# Using localhost for development, configurable for production
|
|
host = os.getenv("HOST", "127.0.0.1")
|
|
port = int(os.getenv("PORT", "8000"))
|
|
uvicorn.run("main:app", host=host, port=port, reload=True)
|