Independent FastAPI backend for Maskan CRM. Owns contacts, organizations, leads, pipelines, activities, products, quotes, users, permissions, audit records and first-party integration credentials, with Alembic migrations against PostgreSQL. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
108 lines
2.7 KiB
Python
108 lines
2.7 KiB
Python
from uuid import uuid4
|
|
|
|
from fastapi import FastAPI, HTTPException, Request
|
|
from fastapi.exceptions import RequestValidationError
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
from fastapi.responses import JSONResponse
|
|
from sqlalchemy import text
|
|
|
|
from app.api.router import api_router
|
|
from app.core.config import get_settings
|
|
from app.core.database import SessionLocal
|
|
|
|
settings = get_settings()
|
|
app = FastAPI(
|
|
title="Maskan CRM API",
|
|
version="0.1.0",
|
|
docs_url="/api/docs",
|
|
openapi_url="/api/openapi.json",
|
|
)
|
|
|
|
app.add_middleware(
|
|
CORSMiddleware,
|
|
allow_origins=settings.cors_origins,
|
|
allow_credentials=True,
|
|
allow_methods=["*"],
|
|
allow_headers=["*"],
|
|
)
|
|
|
|
|
|
def error_response(
|
|
*,
|
|
request: Request,
|
|
status_code: int,
|
|
code: str,
|
|
message: str,
|
|
details: object | None = None,
|
|
) -> JSONResponse:
|
|
request_id = getattr(request.state, "request_id", str(uuid4()))
|
|
return JSONResponse(
|
|
status_code=status_code,
|
|
content={
|
|
"error": {
|
|
"code": code,
|
|
"message": message,
|
|
"details": details,
|
|
"request_id": request_id,
|
|
},
|
|
},
|
|
headers={"X-Request-ID": request_id},
|
|
)
|
|
|
|
|
|
@app.middleware("http")
|
|
async def request_id_middleware(request: Request, call_next):
|
|
request.state.request_id = request.headers.get("X-Request-ID") or str(uuid4())
|
|
response = await call_next(request)
|
|
response.headers["X-Request-ID"] = request.state.request_id
|
|
return response
|
|
|
|
|
|
@app.exception_handler(HTTPException)
|
|
async def http_exception_handler(request: Request, exc: HTTPException) -> JSONResponse:
|
|
return error_response(
|
|
request=request,
|
|
status_code=exc.status_code,
|
|
code=f"HTTP_{exc.status_code}",
|
|
message=str(exc.detail),
|
|
)
|
|
|
|
|
|
@app.exception_handler(RequestValidationError)
|
|
async def validation_exception_handler(
|
|
request: Request,
|
|
exc: RequestValidationError,
|
|
) -> JSONResponse:
|
|
return error_response(
|
|
request=request,
|
|
status_code=422,
|
|
code="VALIDATION_ERROR",
|
|
message="One or more fields are invalid.",
|
|
details=exc.errors(),
|
|
)
|
|
|
|
|
|
@app.get("/health/live", tags=["Health"])
|
|
def live() -> dict[str, str]:
|
|
return {"status": "ok", "service": "maskan-crm-api"}
|
|
|
|
|
|
@app.get("/health/ready", tags=["Health"])
|
|
def ready() -> dict[str, str]:
|
|
with SessionLocal() as db:
|
|
db.execute(text("SELECT 1"))
|
|
return {"status": "ready", "database": "connected"}
|
|
|
|
|
|
@app.get("/api/v1/status", tags=["Health"])
|
|
def status() -> dict[str, str]:
|
|
return {
|
|
"product": "Maskan CRM",
|
|
"mode": settings.mode,
|
|
"environment": settings.environment,
|
|
}
|
|
|
|
|
|
app.include_router(api_router)
|
|
|