Files
orion/main.py

167 lines
5.4 KiB
Python

# main.py
import logging
from datetime import datetime, timezone
from pathlib import Path
from fastapi import Depends, FastAPI, HTTPException, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import HTMLResponse, RedirectResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
from sqlalchemy import text
from sqlalchemy.orm import Session
from app.api.main import api_router
# Import page routers
from app.api.v1.admin import pages as admin_pages
from app.api.v1.vendor import pages as vendor_pages
from app.api.v1.public.vendors import pages as shop_pages
from app.core.config import settings
from app.core.database import get_db
from app.core.lifespan import lifespan
from app.exceptions.handler import setup_exception_handlers
from app.exceptions import ServiceUnavailableException
from middleware.theme_context import theme_context_middleware
from middleware.vendor_context import vendor_context_middleware
logger = logging.getLogger(__name__)
# Get the project root directory (where main.py is located)
BASE_DIR = Path(__file__).resolve().parent
STATIC_DIR = BASE_DIR / "static"
TEMPLATES_DIR = BASE_DIR / "app" / "templates"
# FastAPI app with lifespan
app = FastAPI(
title=settings.project_name,
description=settings.description,
version=settings.version,
lifespan=lifespan,
)
# Configure Jinja2 Templates
templates = Jinja2Templates(directory=str(TEMPLATES_DIR))
# Setup custom exception handlers (unified approach)
setup_exception_handlers(app)
# Add CORS middleware
app.add_middleware(
CORSMiddleware,
allow_origins=settings.allowed_hosts,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Add vendor context middleware (must be after CORS)
app.middleware("http")(vendor_context_middleware)
# Add theme context middleware (must be after vendor context)
app.middleware("http")(theme_context_middleware)
# ========================================
# MOUNT STATIC FILES - Use absolute path
# ========================================
if STATIC_DIR.exists():
app.mount("/static", StaticFiles(directory=str(STATIC_DIR)), name="static")
else:
logger.warning(f"Static directory not found at {STATIC_DIR}")
# ========================================
# Include API router (JSON endpoints at /api/*)
app.include_router(api_router, prefix="/api")
# ============================================================================
# OLD: Keep frontend router for now (we'll phase it out)
# app.include_router(frontend_router)
# ============================================================================
# HTML PAGE ROUTES (Jinja2 Templates)
# ============================================================================
# Admin pages
app.include_router(
admin_pages.router,
prefix="/admin",
tags=["admin-pages"],
include_in_schema=False # Don't show HTML pages in API docs
)
# Vendor pages
app.include_router(
vendor_pages.router,
tags=["vendor-pages"],
include_in_schema=False # Don't show HTML pages in API docs
)
# Shop pages
app.include_router(
shop_pages.router,
tags=["shop-pages"],
include_in_schema=False # Don't show HTML pages in API docs
)
# ============================================================================
# API ROUTES (JSON Responses)
# ============================================================================
# Public Routes (no authentication required)
@app.get("/", include_in_schema=False)
async def root():
"""Redirect root to documentation"""
return RedirectResponse(url="/documentation")
@app.get("/health")
def health_check(db: Session = Depends(get_db)):
"""Health check endpoint"""
try:
# Test database connection
db.execute(text("SELECT 1"))
return {
"status": "healthy",
"timestamp": datetime.now(timezone.utc),
"message": f"{settings.project_name} v{settings.version}",
"docs": {
"swagger": "/docs",
"redoc": "/redoc",
"openapi": "/openapi.json",
"complete": "/documentation",
},
"features": [
"Multi-tenant architecture with vendor isolation",
"JWT Authentication with role-based access control",
"Marketplace product import and curation",
"Vendor catalog management",
"Product-based inventory tracking",
"Stripe Connect payment processing",
],
"supported_marketplaces": [
"Letzshop",
],
"deployment_modes": [
"Subdomain-based (production): vendor.platform.com",
"Path-based (development): /vendor/vendorname/",
],
"auth_required": "Most endpoints require Bearer token authentication",
}
except Exception as e:
logger.error(f"Health check failed: {e}")
raise ServiceUnavailableException("Service unhealthy")
@app.get("/documentation", response_class=HTMLResponse, include_in_schema=False)
async def documentation():
"""Redirect to documentation"""
if settings.debug:
return RedirectResponse(url="http://localhost:8001")
return RedirectResponse(url=settings.documentation_url)
if __name__ == "__main__":
import uvicorn
uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True)