132 lines
6.8 KiB
Python
132 lines
6.8 KiB
Python
from contextlib import asynccontextmanager
|
|
from urllib.parse import parse_qs, urlparse
|
|
|
|
from fastapi import FastAPI, HTTPException, Request
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
from fastapi.responses import JSONResponse
|
|
from fastapi.exception_handlers import http_exception_handler
|
|
|
|
from app.core.install_secrets import ensure_install_secrets
|
|
from app.core.config import settings
|
|
from app.core.jwt_denylist import ensure_jwt_revocation_backend
|
|
from app.core.app_settings import bootstrap_settings
|
|
from app.core.storage import ensure_bucket
|
|
from app.core import database as db_module
|
|
from app.db.token_cleanup import cleanup_expired_tokens
|
|
from app.db.base import Base
|
|
from app.db import models as _models # noqa: F401
|
|
from app.db.seed import run_seed
|
|
from app.modules.auth.router import router as auth_router
|
|
from app.modules.users.router import router as users_router
|
|
from app.modules.content.router import router as content_router
|
|
from app.modules.admin.router import router as admin_router
|
|
from app.modules.media.router import router as media_router
|
|
from app.modules.test.router import router as test_router
|
|
from app.modules.sync.router import router as sync_router, enterprise_router as sync_enterprise_router
|
|
from app.modules.zootech.router import router as zootech_router
|
|
from app.modules.zootech.wesp_compat import router as wesp_compat_router
|
|
from app.modules.zootech.wesp_compat_auth import router as wesp_compat_auth_router
|
|
from app.modules.zootech.wesp_compat_sync import router as wesp_compat_sync_router
|
|
from app.modules.zootech.wesp_compat_admin import router as wesp_compat_admin_router
|
|
from app.modules.zootech.wesp_compat_misc import router as wesp_compat_misc_router
|
|
from app.modules.zootech.wesp_compat_reports import router as wesp_compat_reports_router
|
|
from app.modules.zootech.wesp_compat_analytics import router as wesp_compat_analytics_router
|
|
from app.modules.zootech.wesp_compat_sklad import router as wesp_compat_sklad_router
|
|
from app.modules.zootech.wesp_compat_feed_accounting import router as wesp_compat_feed_accounting_router
|
|
from app.modules.zootech.wesp_compat_lab import router as wesp_compat_lab_router
|
|
from app.modules.zootech.wesp_compat_daily_plan import router as wesp_compat_daily_plan_router
|
|
from app.modules.enterprise.router import router as enterprise_router
|
|
|
|
|
|
def _assert_production_guards() -> None:
|
|
if settings.app_env.lower() != "production":
|
|
return
|
|
if settings.enable_test_routes:
|
|
raise RuntimeError("ENABLE_TEST_ROUTES must be false in production")
|
|
if settings.enable_docs:
|
|
raise RuntimeError("ENABLE_DOCS must be false in production")
|
|
if not settings.enable_rate_limit:
|
|
raise RuntimeError("ENABLE_RATE_LIMIT must be true in production")
|
|
if not settings.cookie_secure:
|
|
raise RuntimeError("COOKIE_SECURE must be true in production")
|
|
if settings.jwt_access_secret.startswith("change-me-"):
|
|
raise RuntimeError("JWT_ACCESS_SECRET placeholder is not allowed in production")
|
|
if settings.jwt_refresh_pepper.startswith("change-me-"):
|
|
raise RuntimeError("JWT_REFRESH_PEPPER placeholder is not allowed in production")
|
|
parsed = urlparse(settings.database_url)
|
|
if parsed.username == "user" and parsed.password == "pass":
|
|
raise RuntimeError("Default database credentials are not allowed in production")
|
|
if parsed.scheme.startswith("postgresql"):
|
|
sslmode = parse_qs(parsed.query).get("sslmode", [""])[0]
|
|
if sslmode != "require":
|
|
raise RuntimeError("DATABASE_URL must contain sslmode=require in production")
|
|
|
|
|
|
def create_app() -> FastAPI:
|
|
@asynccontextmanager
|
|
async def lifespan(_: FastAPI):
|
|
ensure_install_secrets()
|
|
bootstrap_settings()
|
|
ensure_jwt_revocation_backend()
|
|
_assert_production_guards()
|
|
if settings.enable_test_routes:
|
|
Base.metadata.create_all(db_module.engine)
|
|
run_seed()
|
|
cleanup_expired_tokens()
|
|
ensure_bucket()
|
|
yield
|
|
|
|
app = FastAPI(
|
|
title="Compton API",
|
|
version="1.0.0",
|
|
docs_url="/api/v1/docs" if settings.enable_docs else None,
|
|
openapi_url="/api/v1/openapi.json" if settings.enable_docs else None,
|
|
lifespan=lifespan,
|
|
)
|
|
app.add_middleware(
|
|
CORSMiddleware,
|
|
allow_origins=settings.cors_origins,
|
|
allow_credentials=True,
|
|
allow_methods=["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
|
|
allow_headers=["Authorization", "Content-Type", "X-Request-ID", "X-Enterprise-Id"],
|
|
)
|
|
|
|
@app.exception_handler(HTTPException)
|
|
async def wesp_compat_http_exception_handler(request: Request, exc: HTTPException):
|
|
if isinstance(exc.detail, dict) and (
|
|
exc.detail.get("status") == "error" or exc.detail.get("error") is True
|
|
):
|
|
return JSONResponse(status_code=exc.status_code, content=exc.detail)
|
|
return await http_exception_handler(request, exc)
|
|
|
|
@app.get("/api/v1/health")
|
|
async def health() -> dict[str, str]:
|
|
return {"status": "ok"}
|
|
|
|
app.include_router(auth_router, prefix="/api/v1/auth", tags=["auth"])
|
|
app.include_router(users_router, prefix="/api/v1/users", tags=["users"])
|
|
app.include_router(content_router, prefix="/api/v1/content", tags=["content"])
|
|
app.include_router(admin_router, prefix="/api/v1/admin", tags=["admin"])
|
|
app.include_router(media_router, prefix="/api/v1/media", tags=["media"])
|
|
app.include_router(sync_router, prefix="/api/v1/sync", tags=["sync"])
|
|
app.include_router(sync_enterprise_router, prefix="/api/v1/enterprise", tags=["enterprise"])
|
|
app.include_router(enterprise_router, prefix="/api/v1/enterprise", tags=["enterprise"])
|
|
app.include_router(zootech_router, prefix="/api/v1/zootech", tags=["zootech"])
|
|
app.include_router(wesp_compat_router, prefix="/api", tags=["zootech-wesp-compat"])
|
|
app.include_router(wesp_compat_auth_router, prefix="/api", tags=["zootech-wesp-compat"])
|
|
app.include_router(wesp_compat_sync_router, prefix="/api", tags=["zootech-wesp-compat"])
|
|
app.include_router(wesp_compat_admin_router, prefix="/api", tags=["zootech-wesp-compat"])
|
|
app.include_router(wesp_compat_misc_router, prefix="/api", tags=["zootech-wesp-compat"])
|
|
app.include_router(wesp_compat_reports_router, prefix="/api", tags=["zootech-wesp-compat"])
|
|
app.include_router(wesp_compat_analytics_router, prefix="/api", tags=["zootech-wesp-compat"])
|
|
app.include_router(wesp_compat_sklad_router, prefix="/api", tags=["zootech-wesp-compat"])
|
|
app.include_router(wesp_compat_feed_accounting_router, prefix="/api", tags=["zootech-wesp-compat"])
|
|
app.include_router(wesp_compat_lab_router, prefix="/api", tags=["zootech-wesp-compat"])
|
|
app.include_router(wesp_compat_daily_plan_router, prefix="/api", tags=["zootech-wesp-compat"])
|
|
if settings.enable_test_routes:
|
|
app.include_router(test_router, prefix="/api/v1/test", tags=["test"])
|
|
return app
|
|
|
|
|
|
app = create_app()
|