Files
saas_backend/app/__init__.py
T

230 lines
7.9 KiB
Python
Raw Normal View History

2026-02-16 15:32:05 +05:30
from contextlib import asynccontextmanager
2026-01-17 14:18:00 +05:30
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.security import HTTPBearer
import logging
from sqlalchemy import text
from app.config.settings import settings
from app.config.database import engine
import app.models.auth.user_model
import app.models.auth.role_model
import app.models.auth.tenant_model
import app.models.theme.color_palette_model
import app.models.auth.module_model
import app.models.auth.module_environment_model
import app.models.auth.tenant_module_model
import app.models.auth.sso_grant_model
import app.models.auth.access_model
import app.models.system.event_log_model
import asyncio
from app.services.auth.event_service import EventService
from app.config.database import SessionLocal
from app.core.redis import redis_client, sync_redis_client
from fastapi.concurrency import run_in_threadpool
2026-01-17 14:18:00 +05:30
# Configure logging
logging.basicConfig(
level=settings.LOG_LEVEL.upper(),
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
)
logger = logging.getLogger(__name__)
2026-02-16 15:32:05 +05:30
@asynccontextmanager
async def lifespan(app: FastAPI):
logger.info("Testing database connection...")
try:
with engine.connect() as conn:
conn.execute(text("SELECT 1"))
conn.commit()
logger.info("Database connection successful!")
except Exception as e:
logger.error(f"Database connection failed: {e}")
raise
await redis_client.connect()
await run_in_threadpool(sync_redis_client.connect)
logger.info(
f"{settings.PROJECT_NAME} v{settings.VERSION} started ({settings.APP_ENV})"
)
async def redis_event_consumer():
logger.info("Redis Event Consumer STARTED")
while True:
try:
if not redis_client.client:
await asyncio.sleep(5)
continue
result = await redis_client.client.blpop("saas:events:queue", timeout=5)
if result:
_, event_id = result
try:
with SessionLocal() as db:
await run_in_threadpool(EventService.process_queue_item, db, event_id)
except Exception as e:
logger.error(f"Error processing event {event_id}: {e}")
except Exception as e:
await asyncio.sleep(1)
async def fallback_poller():
logger.info("Fallback Event Poller STARTED")
while True:
try:
with SessionLocal() as db:
await run_in_threadpool(EventService.process_outbox, db)
except Exception as e:
logger.error(f"Fallback poller error: {e}")
await asyncio.sleep(60)
background_tasks = [
asyncio.create_task(redis_event_consumer()),
asyncio.create_task(fallback_poller()),
]
yield
logger.info("Shutting down...")
for task in background_tasks:
task.cancel()
await asyncio.gather(*background_tasks, return_exceptions=True)
await redis_client.close()
await run_in_threadpool(sync_redis_client.close)
2026-01-17 14:18:00 +05:30
def create_app() -> FastAPI:
app = FastAPI(
title=settings.PROJECT_NAME,
version=settings.VERSION,
description="SaaS Architecture API",
docs_url="/docs",
redoc_url="/redoc",
openapi_url="/openapi.json",
2026-02-16 15:32:05 +05:30
lifespan=lifespan,
2026-01-17 14:18:00 +05:30
)
# === OpenAPI Security Scheme ===
from fastapi.openapi.utils import get_openapi
def custom_openapi():
if app.openapi_schema:
return app.openapi_schema
openapi_schema = get_openapi(
title=app.title,
version=app.version,
description=app.description,
routes=app.routes,
)
openapi_schema["components"]["securitySchemes"] = {
"BearerAuth": {
"type": "http",
"scheme": "bearer",
"bearerFormat": "JWT",
}
}
app.openapi_schema = openapi_schema
return app.openapi_schema
app.openapi = custom_openapi
# === CORS ===
origins = []
if settings.CORS_ALLOWED_ORIGINS:
origins = [
origin.strip()
for origin in settings.CORS_ALLOWED_ORIGINS.split(",")
if origin.strip()
]
if not origins:
raise RuntimeError(
"CORS_ALLOWED_ORIGINS must be set when allow_credentials=True"
)
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
2026-02-16 15:32:05 +05:30
allow_credentials=True,
2026-01-17 14:18:00 +05:30
allow_methods=["*"],
allow_headers=["*"],
)
# === Include Routers ===
from app.routes.auth.auth import router as auth_router
from app.routes.auth.tenant import router as tenant_router
from app.routes.auth.role import router as role_router
from app.routes.auth.access import router as access_router
from app.routes.auth.user import router as user_router
from app.routes.auth.sso import public_router as sso_public_router, internal_router as sso_internal_router
from app.routes.api.module import router as module_router
2026-01-17 14:18:00 +05:30
app.include_router(auth_router, prefix="/api/auth", tags=["Authentication"])
app.include_router(tenant_router, prefix="/api/tenant", tags=["Tenant Management"])
app.include_router(role_router, prefix="/api/role", tags=["Role Management"])
app.include_router(access_router, prefix="/api/access", tags=["Access Management"])
app.include_router(user_router, prefix="/api/user", tags=["User Management"])
from app.routes.internal.module import router as internal_module_router
2026-02-16 15:32:05 +05:30
app.include_router(sso_public_router, prefix="/api/sso", tags=["SSO"])
app.include_router(sso_internal_router, prefix="/internal/sso", tags=["Internal SSO"])
app.include_router(module_router, prefix="/api/modules", tags=["Modules"])
app.include_router(internal_module_router, prefix="/internal/modules", tags=["Internal Modules"])
2026-01-17 14:18:00 +05:30
2026-01-19 10:39:12 +05:30
from app.routes.theme.color_palette import router as palette_router
app.include_router(palette_router, prefix="/api/theme", tags=["Theme Management"])
# === Admin Routes ===
from app.routes.admin.modules import router as admin_modules_router
from app.routes.admin.module_environments import router as admin_module_env_router
from app.routes.admin.tenant_modules import router as admin_tenant_modules_router
2026-02-16 15:32:05 +05:30
app.include_router(admin_modules_router, prefix="/api/admin/modules", tags=["Admin - Modules"])
app.include_router(admin_module_env_router, prefix="/api/admin/modules", tags=["Admin - Module Environments"])
app.include_router(admin_tenant_modules_router, prefix="/api/admin/tenants", tags=["Admin - Tenant Modules"])
2026-01-17 14:18:00 +05:30
# === Basic Routes ===
@app.get("/", tags=["Root"])
def root():
"""
Root endpoint - API information.
Returns basic information about the API including version and documentation links.
"""
return {
"message": "Welcome to SaaS Architecture Backend API",
"version": settings.VERSION,
"docs": "/docs",
"redoc": "/redoc",
}
@app.get("/health", tags=["Health"])
def health():
"""
Health check endpoint.
Returns the health status of the API and database connection.
Used by monitoring tools and load balancers.
"""
db_status = "connected"
try:
with engine.connect() as conn:
conn.execute(text("SELECT 1"))
db_status = "healthy"
except Exception as e:
2026-02-16 15:32:05 +05:30
logger.error(f"Health check DB error: {e}")
db_status = "unhealthy"
2026-01-17 14:18:00 +05:30
return {
"status": "healthy" if db_status == "healthy" else "degraded",
"environment": settings.APP_ENV,
"database": db_status,
"version": settings.VERSION,
}
2026-02-16 15:32:05 +05:30
return app