solventum-image-metadata/backend/app/main.py
SamoilenkoVadym 20cbd32e62 feat(ui): use Flask HTML templates with FastAPI backend (hybrid mode)
- 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>
2026-02-09 19:13:02 +00:00

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"
)