- Copy Flask v3.1 HTML templates (index.html, login.html) to backend/templates/ - Add Jinja2 support to FastAPI main.py - Update all JavaScript fetch() calls to use /api/* endpoints - Add JWT authentication to all requests (Authorization: Bearer token) - Implement automatic token refresh on 401 errors - Store JWT tokens in localStorage Benefits: - 100% identical UI to Flask v3.1 (proven, working design) - Gold gradient, Montserrat font, all animations preserved - FastAPI backend (Redis sessions, JWT auth, async) - No React rewrite needed - All features work immediately UI now matches Flask exactly while using modern FastAPI backend. Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>
143 lines
4 KiB
Python
143 lines
4 KiB
Python
"""
|
|
Oliver Metadata Tool - FastAPI Backend
|
|
Main application entry point with CORS, middleware, and routers.
|
|
"""
|
|
|
|
from fastapi import FastAPI, Request
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
from fastapi.responses import JSONResponse, FileResponse
|
|
from fastapi.staticfiles import StaticFiles
|
|
from fastapi.templating import Jinja2Templates
|
|
from contextlib import asynccontextmanager
|
|
import os
|
|
from pathlib import Path
|
|
|
|
from app.api import auth, files, metadata, templates
|
|
from app.api import import_api
|
|
from app.core.redis_client import RedisSessionStore
|
|
from app.core.database import init_db
|
|
|
|
# Jinja2 Templates for Flask UI compatibility
|
|
TEMPLATE_DIR = Path(__file__).parent.parent / "templates"
|
|
jinja_templates = Jinja2Templates(directory=str(TEMPLATE_DIR))
|
|
|
|
|
|
# Lifespan context manager for startup/shutdown events
|
|
@asynccontextmanager
|
|
async def lifespan(app: FastAPI):
|
|
"""Application lifespan: startup and shutdown logic"""
|
|
# Startup
|
|
print("🚀 Starting Oliver Metadata Tool API...")
|
|
|
|
# Initialize database
|
|
await init_db()
|
|
print("✅ Database initialized")
|
|
|
|
# Initialize Redis
|
|
redis_url = os.getenv("REDIS_URL", "redis://localhost:6379/0")
|
|
app.state.redis = RedisSessionStore(redis_url)
|
|
print(f"✅ Redis connected: {redis_url}")
|
|
|
|
yield
|
|
|
|
# Shutdown
|
|
print("👋 Shutting down Oliver Metadata Tool API...")
|
|
await app.state.redis.close()
|
|
|
|
|
|
# Create FastAPI app
|
|
app = FastAPI(
|
|
title="Oliver Metadata Tool API",
|
|
description="Universal metadata creation and management API for files",
|
|
version="4.0.0",
|
|
lifespan=lifespan
|
|
)
|
|
|
|
|
|
# CORS Configuration
|
|
# Allow React frontend to make requests from different origin
|
|
origins = [
|
|
"http://localhost:3000", # React dev server
|
|
"http://localhost:5173", # Vite dev server
|
|
"http://localhost:80", # Production frontend
|
|
os.getenv("FRONTEND_URL", ""), # Custom frontend URL from env
|
|
]
|
|
|
|
app.add_middleware(
|
|
CORSMiddleware,
|
|
allow_origins=origins,
|
|
allow_credentials=True,
|
|
allow_methods=["*"],
|
|
allow_headers=["*"],
|
|
)
|
|
|
|
|
|
# Include routers with /api prefix
|
|
app.include_router(auth.router, prefix="/api/auth", tags=["auth"])
|
|
app.include_router(files.router, prefix="/api/files", tags=["files"])
|
|
app.include_router(metadata.router, prefix="/api/metadata", tags=["metadata"])
|
|
app.include_router(templates.router, prefix="/api/templates", tags=["templates"])
|
|
app.include_router(import_api.router, prefix="/api/import", tags=["import"])
|
|
|
|
|
|
# Serve Flask HTML templates (hybrid mode)
|
|
@app.get("/")
|
|
async def root(request: Request):
|
|
"""Serve Flask index.html template"""
|
|
# Check if user is authenticated (simplified for now)
|
|
return jinja_templates.TemplateResponse(
|
|
"index.html",
|
|
{
|
|
"request": request,
|
|
"username": None, # Will be set by JavaScript from JWT
|
|
"docker_mode": os.getenv("DOCKER_MODE", "false") == "true"
|
|
}
|
|
)
|
|
|
|
@app.get("/login")
|
|
async def login_page(request: Request):
|
|
"""Serve Flask login.html template"""
|
|
return jinja_templates.TemplateResponse(
|
|
"login.html",
|
|
{
|
|
"request": request,
|
|
"sso_enabled": bool(os.getenv("AZURE_CLIENT_ID"))
|
|
}
|
|
)
|
|
|
|
|
|
# Health check endpoint
|
|
@app.get("/health")
|
|
async def health_check():
|
|
"""Health check endpoint for Docker/K8s"""
|
|
return {
|
|
"status": "healthy",
|
|
"database": "connected", # Will check actual DB later
|
|
"redis": "connected" # Will check actual Redis later
|
|
}
|
|
|
|
|
|
# Global exception handler
|
|
@app.exception_handler(Exception)
|
|
async def global_exception_handler(request, exc):
|
|
"""Handle all uncaught exceptions"""
|
|
return JSONResponse(
|
|
status_code=500,
|
|
content={
|
|
"error": "Internal server error",
|
|
"detail": str(exc) if os.getenv("DEBUG") == "true" else "An error occurred"
|
|
}
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
import uvicorn
|
|
|
|
# Run with: python -m app.main
|
|
uvicorn.run(
|
|
"app.main:app",
|
|
host="0.0.0.0",
|
|
port=8000,
|
|
reload=True, # Auto-reload on code changes
|
|
log_level="info"
|
|
)
|