Initial commit with Phase 0 Scaffolding
Some checks failed
Automated Container Build / build-and-push (push) Failing after 3s
CI / Backend (Python) (push) Failing after 28s
CI / Frontend (TypeScript) (push) Failing after 5m8s

This commit is contained in:
Elijah 2026-06-10 18:28:17 -07:00
parent b393607602
commit c545d4b17d
51 changed files with 7064 additions and 4 deletions

85
backend/app/main.py Normal file
View 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)