From ed5679948b4ff53a7a2586589160c783c26439bb Mon Sep 17 00:00:00 2001 From: Chris Date: Tue, 31 Mar 2026 22:20:24 +0800 Subject: [PATCH] docs(api): add internal API contract and expose response schemas in swagger --- app/api/internal.py | 5 ++- app/api/internal_catalog.py | 27 +++++++----- app/schemas/internal.py | 85 +++++++++++++++++++++++++++++++++++++ 3 files changed, 105 insertions(+), 12 deletions(-) create mode 100644 app/schemas/internal.py diff --git a/app/api/internal.py b/app/api/internal.py index e5e4f8b..f580d55 100644 --- a/app/api/internal.py +++ b/app/api/internal.py @@ -4,6 +4,7 @@ from sqlalchemy.orm import Session from app.core.config import get_settings from app.db.session import get_db from app.repositories.permissions_repo import PermissionsRepository +from app.schemas.internal import InternalUpsertUserBySubResponse from app.repositories.users_repo import UsersRepository from app.schemas.authentik_admin import AuthentikEnsureUserRequest, AuthentikEnsureUserResponse from app.schemas.permissions import PermissionSnapshotResponse @@ -15,11 +16,11 @@ from app.services.permission_service import PermissionService router = APIRouter(prefix="/internal", tags=["internal"], dependencies=[Depends(require_api_client)]) -@router.post("/users/upsert-by-sub") +@router.post("/users/upsert-by-sub", response_model=InternalUpsertUserBySubResponse) def upsert_user_by_sub( payload: UserUpsertBySubRequest, db: Session = Depends(get_db), -) -> dict[str, str | bool | None]: +) -> InternalUpsertUserBySubResponse: repo = UsersRepository(db) user = repo.upsert_by_sub( authentik_sub=payload.sub, diff --git a/app/api/internal_catalog.py b/app/api/internal_catalog.py index 33d2148..5d43536 100644 --- a/app/api/internal_catalog.py +++ b/app/api/internal_catalog.py @@ -7,28 +7,35 @@ from app.repositories.modules_repo import ModulesRepository from app.repositories.sites_repo import SitesRepository from app.repositories.systems_repo import SystemsRepository from app.repositories.users_repo import UsersRepository +from app.schemas.internal import ( + InternalCompanyListResponse, + InternalMemberListResponse, + InternalModuleListResponse, + InternalSiteListResponse, + InternalSystemListResponse, +) from app.security.api_client_auth import require_api_client router = APIRouter(prefix="/internal", tags=["internal"], dependencies=[Depends(require_api_client)]) -@router.get("/systems") +@router.get("/systems", response_model=InternalSystemListResponse) def internal_list_systems( db: Session = Depends(get_db), limit: int = Query(default=200, ge=1, le=1000), offset: int = Query(default=0, ge=0), -) -> dict: +) -> InternalSystemListResponse: repo = SystemsRepository(db) items, total = repo.list(limit=limit, offset=offset) return {"items": [{"id": i.id, "system_key": i.system_key, "name": i.name, "status": i.status} for i in items], "total": total, "limit": limit, "offset": offset} -@router.get("/modules") +@router.get("/modules", response_model=InternalModuleListResponse) def internal_list_modules( db: Session = Depends(get_db), limit: int = Query(default=500, ge=1, le=2000), offset: int = Query(default=0, ge=0), -) -> dict: +) -> InternalModuleListResponse: modules_repo = ModulesRepository(db) items, total = modules_repo.list(limit=limit, offset=offset) return { @@ -48,25 +55,25 @@ def internal_list_modules( } -@router.get("/companies") +@router.get("/companies", response_model=InternalCompanyListResponse) def internal_list_companies( db: Session = Depends(get_db), keyword: str | None = Query(default=None), limit: int = Query(default=500, ge=1, le=2000), offset: int = Query(default=0, ge=0), -) -> dict: +) -> InternalCompanyListResponse: repo = CompaniesRepository(db) items, total = repo.list(keyword=keyword, limit=limit, offset=offset) return {"items": [{"id": i.id, "company_key": i.company_key, "name": i.name, "status": i.status} for i in items], "total": total, "limit": limit, "offset": offset} -@router.get("/sites") +@router.get("/sites", response_model=InternalSiteListResponse) def internal_list_sites( db: Session = Depends(get_db), company_key: str | None = Query(default=None), limit: int = Query(default=500, ge=1, le=2000), offset: int = Query(default=0, ge=0), -) -> dict: +) -> InternalSiteListResponse: companies_repo = CompaniesRepository(db) sites_repo = SitesRepository(db) company_id = None @@ -80,13 +87,13 @@ def internal_list_sites( return {"items": [{"id": i.id, "site_key": i.site_key, "company_key": mapping.get(i.company_id), "name": i.name, "status": i.status} for i in items], "total": total, "limit": limit, "offset": offset} -@router.get("/members") +@router.get("/members", response_model=InternalMemberListResponse) def internal_list_members( db: Session = Depends(get_db), keyword: str | None = Query(default=None), limit: int = Query(default=500, ge=1, le=2000), offset: int = Query(default=0, ge=0), -) -> dict: +) -> InternalMemberListResponse: repo = UsersRepository(db) items, total = repo.list(keyword=keyword, limit=limit, offset=offset) return { diff --git a/app/schemas/internal.py b/app/schemas/internal.py new file mode 100644 index 0000000..81bfb1b --- /dev/null +++ b/app/schemas/internal.py @@ -0,0 +1,85 @@ +from pydantic import BaseModel + + +class InternalSystemItem(BaseModel): + id: str + system_key: str + name: str + status: str + + +class InternalSystemListResponse(BaseModel): + items: list[InternalSystemItem] + total: int + limit: int + offset: int + + +class InternalModuleItem(BaseModel): + id: str + module_key: str + system_key: str + name: str + status: str + + +class InternalModuleListResponse(BaseModel): + items: list[InternalModuleItem] + total: int + limit: int + offset: int + + +class InternalCompanyItem(BaseModel): + id: str + company_key: str + name: str + status: str + + +class InternalCompanyListResponse(BaseModel): + items: list[InternalCompanyItem] + total: int + limit: int + offset: int + + +class InternalSiteItem(BaseModel): + id: str + site_key: str + company_key: str | None = None + name: str + status: str + + +class InternalSiteListResponse(BaseModel): + items: list[InternalSiteItem] + total: int + limit: int + offset: int + + +class InternalMemberItem(BaseModel): + id: str + authentik_sub: str + username: str | None = None + email: str | None = None + display_name: str | None = None + is_active: bool + + +class InternalMemberListResponse(BaseModel): + items: list[InternalMemberItem] + total: int + limit: int + offset: int + + +class InternalUpsertUserBySubResponse(BaseModel): + id: str + sub: str + authentik_user_id: int | None = None + username: str | None = None + email: str | None = None + display_name: str | None = None + is_active: bool