"""BadNote FastAPI server — main application. Architecture (v1): - Vault files are the source of truth (same layout as the Flutter vault). - ``/api/v1/vault/*`` assists multi-device sync (manifest + PUT/GET/DELETE). - ``/api/v1/ocr/*`` accepts ink rasters for deferred server OCR. - Legacy ``/api/notes`` etc. remain mounted under ``/api/legacy/*`` for compatibility with old experiments; new clients must not use them. """ import os from contextlib import asynccontextmanager from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from .config import settings from .database import close_db, init_db from .routers.auth_router import router as auth_router from .routers.notes_router import router as notes_router from .routers.documents_router import router as documents_router from .routers.ocr_router import router as ocr_router from .routers.sync_router import router as sync_router from .routers.v1_vault_router import router as v1_vault_router from .routers.v1_ocr_router import router as v1_ocr_router @asynccontextmanager async def lifespan(app: FastAPI): """Startup: create directories and init DB. Shutdown: close DB.""" os.makedirs(settings.storage_path, exist_ok=True) os.makedirs(settings.vault_path, exist_ok=True) for subdir in ("pending", "processing", "done", "failed"): os.makedirs(os.path.join(settings.queue_path, subdir), exist_ok=True) await init_db() yield await close_db() app = FastAPI( title="BadNote Server", version="2.0.0", description=( "Self-hosted companion for BadNote. " "Primary API is /api/v1 (vault + OCR). " "Legacy notes CRUD lives under /api/legacy." ), lifespan=lifespan, ) _cors_origins = settings.cors_origins or ["*"] app.add_middleware( CORSMiddleware, allow_origins=_cors_origins, allow_credentials=False, allow_methods=["*"], allow_headers=["*"], ) # --- Auth (shared by v1 + legacy) --- app.include_router(auth_router, prefix="/api/auth", tags=["auth"]) app.include_router(auth_router, prefix="/api/v1/auth", tags=["auth-v1"]) # --- Primary v1 API --- app.include_router(v1_vault_router, prefix="/api/v1/vault", tags=["vault-v1"]) app.include_router(v1_ocr_router, prefix="/api/v1/ocr", tags=["ocr-v1"]) # --- Legacy (deprecated) --- app.include_router(notes_router, prefix="/api/legacy/notes", tags=["legacy-notes"]) app.include_router(documents_router, prefix="/api/legacy/documents", tags=["legacy-documents"]) app.include_router(ocr_router, prefix="/api/legacy/ocr", tags=["legacy-ocr"]) app.include_router(sync_router, prefix="/api/legacy/sync", tags=["legacy-sync"]) # Keep old paths temporarily so existing bookmarks to /docs experiments still work, # but they are the same legacy routers. app.include_router(notes_router, prefix="/api/notes", tags=["legacy-notes"], include_in_schema=False) app.include_router(documents_router, prefix="/api/documents", tags=["legacy-documents"], include_in_schema=False) app.include_router(ocr_router, prefix="/api/ocr", tags=["legacy-ocr"], include_in_schema=False) app.include_router(sync_router, prefix="/api/sync", tags=["legacy-sync"], include_in_schema=False) @app.get("/api/ping") @app.get("/api/v1/health") async def health() -> dict: """Liveness probe for clients and reverse proxies.""" return { "status": "ok", "version": "2.0.0", "api": "v1", "features": ["vault", "ocr", "auth"], }