TorbatYar/backend/core-service/app/main.py
Mortezakoohjani 0d424c500a feat(platform): complete commercial runtime consolidation
Unify commercial runtime ownership across backend and frontend so platform, experience, and hospitality modules use the shared commercial source of truth.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-29 11:11:53 +03:30

97 lines
3.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""نقطه ورود برنامه FastAPI برای Core Platform Service."""
from __future__ import annotations
from contextlib import asynccontextmanager
from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
from app import __version__
from app.api.v1 import api_router, health
from app.core.cache import cache
from app.core.config import settings
from app.core.logging import configure_logging, get_logger
from app.middlewares.tenant import TenantResolutionMiddleware
from shared.exceptions import AppError
from shared.responses import ErrorDetail, ErrorResponse
configure_logging()
logger = get_logger(__name__)
@asynccontextmanager
async def lifespan(app: FastAPI):
logger.info("service_starting", extra={"service": settings.service_name})
# Best-effort: ensure commercial meta-registry kinds exist (idempotent).
# Skip in unit tests — discovery_catalog / seed handle kinds there.
if settings.environment != "test":
try:
from app.commercial.service import CommercialRegistryService
from app.core.database import AsyncSessionLocal
async with AsyncSessionLocal() as session:
await CommercialRegistryService(session).ensure_builtin_kinds()
logger.info("commercial_registry_kinds_ready")
except Exception as exc: # noqa: BLE001
logger.warning("commercial_kinds_bootstrap_skipped", extra={"error": str(exc)})
yield
await cache.close()
logger.info("service_stopped")
def create_app() -> FastAPI:
app = FastAPI(
title=f"{settings.platform_name} - Core Platform Service",
version=__version__,
description="سرویس هسته پلتفرم SaaS چندمستأجری (فاز ۱).",
lifespan=lifespan,
)
# CORS - در production باید origins محدود شود.
# allow_origin_regex برای ساب‌دامین tenantها (<slug>.torbatyar.ir)
app.add_middleware(
CORSMiddleware,
allow_origins=settings.cors_origin_list,
allow_origin_regex=r"https?://([a-z0-9-]+\.)*torbatyar\.ir",
allow_credentials=False,
allow_methods=["*"],
allow_headers=["*"],
)
# Middleware تشخیص tenant.
app.add_middleware(
TenantResolutionMiddleware, base_domain=settings.platform_base_domain or None
)
# ثبت exception handlerها برای تبدیل خطاهای دامنه به پاسخ استاندارد.
_register_exception_handlers(app)
# routerها
app.include_router(health.router)
app.include_router(api_router, prefix=settings.api_v1_prefix)
return app
def _register_exception_handlers(app: FastAPI) -> None:
@app.exception_handler(AppError)
async def app_error_handler(request: Request, exc: AppError) -> JSONResponse:
response = ErrorResponse(
error=ErrorDetail(
code=exc.error_code, message=exc.message, details=exc.details
)
)
return JSONResponse(status_code=exc.status_code, content=response.model_dump())
@app.exception_handler(Exception)
async def unhandled_handler(request: Request, exc: Exception) -> JSONResponse:
logger.error("unhandled_exception", extra={"error": str(exc)}, exc_info=exc)
response = ErrorResponse(
error=ErrorDetail(code="internal_error", message="خطای داخلی سرور")
)
return JSONResponse(status_code=500, content=response.model_dump())
app = create_app()