"""FastAPI web upload endpoint for mobile file ingestion. Auth: Token-based via URL query param (?token=secret) or header (X-Upload-Token). Processing: File is saved immediately, enrichment runs in background. """ import logging import secrets import threading from pathlib import Path from fastapi import FastAPI, File, HTTPException, Query, Request, UploadFile from fastapi.responses import HTMLResponse from ingestion.config import IngestionConfig from ingestion.web.browse import router as browse_router logger = logging.getLogger(__name__) app = FastAPI(title="NoteGraph", version="0.1.0") app.include_router(browse_router) @app.get("/") async def root(request: Request): """Redirect root to browse page.""" from fastapi.responses import RedirectResponse token = request.query_params.get("token", "") return RedirectResponse(url=f"/browse?token={token}" if token else "/browse") def _get_config() -> IngestionConfig: return IngestionConfig() def _verify_token(request: Request, token: str = Query(default=None)): """Verify upload token from query param or X-Upload-Token header.""" config = _get_config() # Token is the password part of SB_USER (after the colon) sb_user = config.sb_user.strip() parts = sb_user.split(":", 1) expected_token = parts[1].strip() if len(parts) == 2 else sb_user # Check query param if token and secrets.compare_digest(token.encode(), expected_token.encode()): return True # Check header header_token = request.headers.get("X-Upload-Token", "") if header_token and secrets.compare_digest(header_token.encode(), expected_token.encode()): return True raise HTTPException(status_code=401, detail="Invalid or missing token. Use ?token=YOUR_PASSWORD") def _process_in_background(file_path: Path, config: IngestionConfig): """Run the enrichment pipeline in a background thread.""" try: from ingestion.pipeline import process_file result = process_file(file_path=file_path, config=config) if result.success: logger.info("Background processing complete: %s → %s", file_path.name, result.output_file) else: logger.warning("Background processing failed for %s: %s", file_path.name, result.error) except Exception as e: logger.error("Background processing error for %s: %s", file_path.name, e) UPLOAD_HTML = """