Files
Akiba So 198da00ecd
All checks were successful
CI / Windows build (push) Successful in 7m47s
feat: vault-aligned server v1 + UX polish
Redesign the optional FastAPI companion around vault files (manifest /
PUT/GET/DELETE + OCR jobs) instead of legacy strokes_json notes. Wire a
client Server settings panel for health/login. Polish shell UX: l10n for
settings/home/board, sticky-board empty state, and a narrow-screen
diagnostics FAB.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-05 19:04:48 +08:00

92 lines
3.4 KiB
Python

"""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"],
}