jira-ai-fixer/app/main.py

99 lines
3.0 KiB
Python

"""JIRA AI Fixer - Enterprise Issue Analysis Platform."""
from contextlib import asynccontextmanager
from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from fastapi.responses import FileResponse, RedirectResponse
from starlette.middleware.base import BaseHTTPMiddleware
import os
from app.core.config import settings
from app.core.database import init_db
from app.api import api_router
class HTTPSRedirectMiddleware(BaseHTTPMiddleware):
"""Force HTTPS in redirects when behind reverse proxy."""
async def dispatch(self, request: Request, call_next):
response = await call_next(request)
# Fix Location header to use HTTPS if behind proxy
if response.status_code in (301, 302, 303, 307, 308):
location = response.headers.get("location", "")
if location.startswith("http://"):
response.headers["location"] = location.replace("http://", "https://", 1)
return response
@asynccontextmanager
async def lifespan(app: FastAPI):
# Startup
await init_db()
yield
# Shutdown
app = FastAPI(
title=settings.APP_NAME,
version=settings.APP_VERSION,
description="Enterprise AI-powered issue analysis and automated fix generation",
docs_url="/api/docs",
redoc_url="/api/redoc",
openapi_url="/api/openapi.json",
lifespan=lifespan
)
# Add HTTPS redirect middleware
app.add_middleware(HTTPSRedirectMiddleware)
# CORS
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# API routes
app.include_router(api_router, prefix="/api")
# Health check
@app.get("/api/health")
async def health():
return {
"status": "healthy",
"service": "jira-ai-fixer",
"version": settings.APP_VERSION
}
# Serve static frontend (will be mounted if exists)
FRONTEND_DIR = "/app/frontend"
ASSETS_DIR = f"{FRONTEND_DIR}/assets"
if os.path.exists(FRONTEND_DIR) and os.path.exists(ASSETS_DIR):
app.mount("/assets", StaticFiles(directory=ASSETS_DIR), name="assets")
@app.get("/")
async def serve_frontend():
return FileResponse(f"{FRONTEND_DIR}/index.html")
# SPA fallback - MUST be last and NOT capture /api/*
@app.get("/{path:path}", include_in_schema=False)
async def serve_spa(path: str):
# Explicitly skip API routes
if path.startswith("api"):
from fastapi import HTTPException
raise HTTPException(status_code=404, detail="Not Found")
file_path = f"{FRONTEND_DIR}/{path}"
if os.path.exists(file_path) and os.path.isfile(file_path):
return FileResponse(file_path)
return FileResponse(f"{FRONTEND_DIR}/index.html")
# Fallback: serve basic info page when no frontend
@app.get("/")
async def root():
return {
"service": settings.APP_NAME,
"version": settings.APP_VERSION,
"docs": "/api/docs",
"health": "/api/health"
}