Initial commit with Phase 0 Scaffolding
This commit is contained in:
parent
b393607602
commit
c545d4b17d
51 changed files with 7064 additions and 4 deletions
85
backend/app/main.py
Normal file
85
backend/app/main.py
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
"""
|
||||
PaperJet backend — FastAPI application factory.
|
||||
|
||||
This is the main entry point. The lifespan handler initializes the database
|
||||
and ensures required directories exist on startup.
|
||||
"""
|
||||
|
||||
from contextlib import asynccontextmanager
|
||||
from collections.abc import AsyncGenerator
|
||||
from typing import Any
|
||||
|
||||
from fastapi import FastAPI, Request
|
||||
from fastapi.responses import JSONResponse
|
||||
|
||||
from app.api.v1 import router as v1_router
|
||||
from app.config import settings
|
||||
from app.db import Base, engine
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
|
||||
"""Application lifespan: startup and shutdown tasks."""
|
||||
# Create all tables (Alembic handles migrations in production,
|
||||
# but this ensures tables exist for development/first run)
|
||||
Base.metadata.create_all(bind=engine)
|
||||
|
||||
# Ensure storage directories exist
|
||||
settings.PDF_STORAGE_PATH.mkdir(parents=True, exist_ok=True)
|
||||
settings.THUMBNAILS_PATH.mkdir(parents=True, exist_ok=True)
|
||||
settings.DATABASE_PATH.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
yield
|
||||
|
||||
# Shutdown: dispose of the engine
|
||||
engine.dispose()
|
||||
|
||||
|
||||
app = FastAPI(
|
||||
title="PaperJet",
|
||||
description="Self-hosted PDF editor API",
|
||||
version=settings.APP_VERSION,
|
||||
docs_url="/api/v1/docs" if settings.DEBUG else None,
|
||||
redoc_url="/api/v1/redoc" if settings.DEBUG else None,
|
||||
openapi_url="/api/v1/openapi.json",
|
||||
lifespan=lifespan,
|
||||
)
|
||||
|
||||
|
||||
# --- Error handlers ---
|
||||
|
||||
@app.exception_handler(404)
|
||||
async def not_found_handler(request: Request, exc: Any) -> JSONResponse:
|
||||
"""Consistent 404 error envelope."""
|
||||
return JSONResponse(
|
||||
status_code=404,
|
||||
content={"error": {"code": "not_found", "message": "Resource not found"}},
|
||||
)
|
||||
|
||||
|
||||
@app.exception_handler(422)
|
||||
async def validation_error_handler(request: Request, exc: Any) -> JSONResponse:
|
||||
"""Consistent validation error envelope."""
|
||||
return JSONResponse(
|
||||
status_code=422,
|
||||
content={
|
||||
"error": {
|
||||
"code": "validation_error",
|
||||
"message": "Request validation failed",
|
||||
"details": exc.errors() if hasattr(exc, "errors") else str(exc),
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@app.exception_handler(500)
|
||||
async def internal_error_handler(request: Request, exc: Any) -> JSONResponse:
|
||||
"""Consistent 500 error envelope."""
|
||||
return JSONResponse(
|
||||
status_code=500,
|
||||
content={"error": {"code": "internal_error", "message": "Internal server error"}},
|
||||
)
|
||||
|
||||
|
||||
# --- Mount routers ---
|
||||
app.include_router(v1_router)
|
||||
Loading…
Add table
Add a link
Reference in a new issue