| import sentry_sdk |
| from contextlib import asynccontextmanager |
| from fastapi import FastAPI, Request, Response |
| from fastapi.middleware.cors import CORSMiddleware |
| from slowapi import _rate_limit_exceeded_handler |
| from slowapi.errors import RateLimitExceeded |
| from app.config import settings |
| from app.limiter import limiter, get_real_client_ip |
| from app.routers import auth, audit, design, github, billing, settings as settings_router, legal, projects |
| from loguru import logger |
|
|
| @asynccontextmanager |
| async def lifespan(app: FastAPI): |
| logger.info(f"{settings.APP_NAME} API startup complete. Environment: {'DEBUG' if settings.DEBUG else 'PRODUCTION'}") |
| yield |
| logger.info(f"{settings.APP_NAME} shutting down.") |
|
|
| app = FastAPI( |
| title=settings.APP_NAME, |
| description="Archvise SaaS Backend Engine", |
| version=settings.API_VERSION, |
| debug=settings.DEBUG, |
| lifespan=lifespan |
| ) |
|
|
| |
| app.state.limiter = limiter |
| app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler) |
|
|
| |
| |
| allowed_origins = [ |
| "http://localhost:3000", |
| "https://archvise.vercel.app", |
| ] |
| if isinstance(settings.CORS_ORIGINS, list): |
| allowed_origins.extend(settings.CORS_ORIGINS) |
| elif isinstance(settings.CORS_ORIGINS, str) and settings.CORS_ORIGINS != "*": |
| allowed_origins.append(settings.CORS_ORIGINS) |
|
|
| app.add_middleware( |
| CORSMiddleware, |
| allow_origins=list(set(allowed_origins)), |
| allow_origin_regex=r"https://archvise-.*\.vercel\.app", |
| allow_credentials=True, |
| allow_methods=["*"], |
| allow_headers=["*"], |
| ) |
|
|
| |
| app.include_router(auth.router, prefix="/api") |
| app.include_router(audit.router, prefix="/api") |
| app.include_router(design.router, prefix="/api") |
| app.include_router(github.router, prefix="/api") |
| app.include_router(billing.router, prefix="/api") |
| app.include_router(settings_router.router, prefix="/api") |
| app.include_router(legal.router, prefix="/api") |
| app.include_router(projects.router, prefix="/api") |
|
|
|
|
| @app.get("/") |
| def read_root(): |
| return {"status": "online", "app": settings.APP_NAME, "version": settings.API_VERSION} |
|
|
| @app.get("/health") |
| async def health_check(): |
| """Liveness + readiness probe — checks DB and Redis connectivity.""" |
| import redis as redis_lib |
| from app.database import engine |
| from sqlalchemy import text |
| checks = {"database": "ok", "redis": "ok"} |
| status_code = 200 |
|
|
| |
| try: |
| async with engine.connect() as conn: |
| await conn.execute(text("SELECT 1")) |
| except Exception as e: |
| checks["database"] = f"error: {str(e)[:80]}" |
| status_code = 503 |
|
|
| |
| try: |
| r = redis_lib.Redis.from_url(settings.REDIS_URL, socket_connect_timeout=2) |
| r.ping() |
| r.close() |
| except Exception as e: |
| checks["redis"] = f"error: {str(e)[:80]}" |
| status_code = 503 |
|
|
| from fastapi.responses import JSONResponse |
| return JSONResponse(content={"status": "healthy" if status_code == 200 else "degraded", **checks}, status_code=status_code) |
|
|