Coverage for src/lumen/api/admin/packages.py: 88%
32 statements
« prev ^ index » next coverage.py v7.16.0, created at 2026-08-31 18:46 +0000
« prev ^ index » next coverage.py v7.16.0, created at 2026-08-31 18:46 +0000
1"""
2This router implements endpoints for administrative package management.
3It allows uploading and deleting FHIR packages and triggers DB package loader routines.
4"""
6from fastapi import APIRouter, HTTPException, UploadFile, status
7from fastapi.responses import JSONResponse
9from lumen.admin.loader import delete_package, get_installed_packages, load_package_tgz
10from lumen.api.deps import TenantDep
11from lumen.db.pool import get_pool
13router = APIRouter()
16@router.post("/packages", status_code=status.HTTP_201_CREATED)
17async def upload_package(
18 tenant_id: TenantDep,
19 file: UploadFile,
20) -> JSONResponse:
21 """Upload and install a FHIR NPM package (.tgz)."""
22 if not file.filename or not file.filename.endswith(".tgz"):
23 raise HTTPException(
24 status_code=status.HTTP_400_BAD_REQUEST,
25 detail="Only .tgz package files are supported.",
26 )
28 file_bytes = await file.read()
30 pool = get_pool()
31 async with pool.acquire() as conn:
32 try:
33 # We wrap the entire package import in a single transaction
34 async with conn.transaction():
35 results = await load_package_tgz(conn, tenant_id, file_bytes)
36 except Exception as e:
37 raise HTTPException(
38 status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
39 detail=f"Failed to process package: {e!s}",
40 ) from e
42 return JSONResponse(content=results, status_code=status.HTTP_201_CREATED)
45@router.get("/packages")
46async def list_packages(tenant_id: TenantDep) -> JSONResponse:
47 """List all installed FHIR packages for the current tenant."""
48 pool = get_pool()
49 async with pool.acquire() as conn:
50 packages = await get_installed_packages(conn, tenant_id)
52 return JSONResponse(
53 content={"installed_packages": packages},
54 status_code=status.HTTP_200_OK,
55 )
58@router.delete("/packages/{package_id:path}", status_code=status.HTTP_204_NO_CONTENT)
59async def remove_package(
60 tenant_id: TenantDep,
61 package_id: str,
62) -> None:
63 """Uninstall a FHIR package and delete all its conformance resources."""
64 pool = get_pool()
65 async with pool.acquire() as conn, conn.transaction():
66 deleted_count = await delete_package(conn, tenant_id, package_id)
68 if deleted_count == 0:
69 raise HTTPException(
70 status_code=status.HTTP_404_NOT_FOUND,
71 detail=f"Package '{package_id}' not found or no resources were deleted.",
72 )