From cba04e424971f5b8f3bf4b67e581ca8a20a248bd Mon Sep 17 00:00:00 2001 From: luxferre Date: Thu, 28 May 2026 16:11:54 +0100 Subject: [PATCH 1/3] docs: user router summaries Issue: #13 --- src/user/router.py | 48 ++++++++++++++++++++++++++++++---------------- 1 file changed, 32 insertions(+), 16 deletions(-) diff --git a/src/user/router.py b/src/user/router.py index 90c133f..0966850 100644 --- a/src/user/router.py +++ b/src/user/router.py @@ -23,9 +23,13 @@ router = APIRouter( ) -@router.get("/self/claims", response_model=OIDCClaims, status_code=status.HTTP_200_OK, responses={ - status.HTTP_200_OK: {"description": "Successful retrieval from database"}, -}) +@router.get("/self/claims", + summary="Get current user OIDC claims.", + response_model=OIDCClaims, + status_code=status.HTTP_200_OK, + responses={ + status.HTTP_200_OK: {"description": "Successful retrieval from database"}, + }) async def current_user_claims(user: claims_dependency): """ Returns the full OIDC claims associated with the currently logged-in user. @@ -34,10 +38,14 @@ async def current_user_claims(user: claims_dependency): return user -@router.get("/self/db", response_model=UserResponse, status_code=status.HTTP_200_OK, responses={ - status.HTTP_404_NOT_FOUND: {"description": "User not found"}, - status.HTTP_200_OK: {"description": "Successful retrieval from database"}, -}) +@router.get("/self/db", + summary="Get current user hub details.", + response_model=UserResponse, + status_code=status.HTTP_200_OK, + responses={ + status.HTTP_404_NOT_FOUND: {"description": "User not found"}, + status.HTTP_200_OK: {"description": "Successful retrieval from database"}, + }) async def current_user(user_model: user_model_claims_dependency): """ Returns the database details associated with the currently logged-in user. @@ -45,10 +53,14 @@ async def current_user(user_model: user_model_claims_dependency): return user_model -@router.get("/", response_model=UserResponse, status_code=status.HTTP_200_OK, responses={ - status.HTTP_404_NOT_FOUND: {"description": "User not found"}, - status.HTTP_200_OK: {"description": "Successful retrieval from database"}, -}) +@router.get("/", + summary="Get user hub details by ID.", + response_model=UserResponse, + status_code=status.HTTP_200_OK, + responses={ + status.HTTP_404_NOT_FOUND: {"description": "User not found"}, + status.HTTP_200_OK: {"description": "Successful retrieval from database"}, + }) async def get_user_by_id(user_model: user_model_query_dependency, su: super_admin_dependency): """ Returns the database details associated with the provided user ID. @@ -56,11 +68,15 @@ async def get_user_by_id(user_model: user_model_query_dependency, su: super_admi return user_model -@router.delete("/", status_code=status.HTTP_204_NO_CONTENT, responses={ - status.HTTP_204_NO_CONTENT: {"description": "User deleted"}, - status.HTTP_404_NOT_FOUND: {"description": "User not found"}, - }) -async def delete_user_by_id(db: db_dependency, user_model: user_model_body_dependency, su: super_admin_dependency, request_model: UserDeleteUserRequest): +@router.delete("/", + summary="Delete user from hub by ID.", + status_code=status.HTTP_204_NO_CONTENT, + responses={ + status.HTTP_204_NO_CONTENT: {"description": "User deleted"}, + status.HTTP_404_NOT_FOUND: {"description": "User not found"}, + }) +async def delete_user_by_id(db: db_dependency, user_model: user_model_body_dependency, su: super_admin_dependency, + request_model: UserDeleteUserRequest): """ Deletes the user with the provided ID from the database. This will not remove them from OIDC, and they will be automatically readded on next login. """ From d3bdfe84699e51698bbf2c4758419212dfeee64f Mon Sep 17 00:00:00 2001 From: luxferre Date: Thu, 28 May 2026 16:43:39 +0100 Subject: [PATCH 2/3] docs: org router decorators Issue: #13 --- src/organisation/router.py | 122 +++++++++++++++++++++++++++++++++---- 1 file changed, 110 insertions(+), 12 deletions(-) diff --git a/src/organisation/router.py b/src/organisation/router.py index 2e71fba..8e5433c 100644 --- a/src/organisation/router.py +++ b/src/organisation/router.py @@ -47,7 +47,16 @@ router = APIRouter( ) -@router.get("/id", response_model=OrgGetOrgResponse) +@router.get("/id", + summary="Get org details by ID.", + response_model=OrgGetOrgResponse, + status_code=status.HTTP_200_OK, + responses={ + status.HTTP_200_OK: {"description": "Successful retrieval from database"}, + status.HTTP_404_NOT_FOUND: {"description": "Organisation not found"}, + status.HTTP_422_UNPROCESSABLE_CONTENT: {"description": "Missing or invalid org_id query parameter"}, + status.HTTP_401_UNAUTHORIZED: {"description": "Not authorised. Must be org root user."}, + }) async def get_org_by_id(org_model: org_model_root_claim_query_dependency): response = { "name": org_model.name, @@ -61,8 +70,17 @@ async def get_org_by_id(org_model: org_model_root_claim_query_dependency): return response -@router.post("/") +@router.post("/", + summary="Create new organisation.", + status_code=status.HTTP_201_CREATED, + responses={ + status.HTTP_201_CREATED: {"description": "Successfully created organisation."}, + status.HTTP_422_UNPROCESSABLE_CONTENT: {"description": "Invalid data in request."}, + status.HTTP_401_UNAUTHORIZED: {"description": "User must be logged in with OIDC to create organisation."}, + status.HTTP_409_CONFLICT: {"description": "Organisation with this name already exists."}, + }) async def create_org(db: db_dependency, user_model: user_model_claims_dependency, request_model: OrgPostOrgRequest): + # TODO: Response model if request_model.intake_questionnaire: intake_questionnaire = request_model.intake_questionnaire.model_dump() else: @@ -88,13 +106,21 @@ async def create_org(db: db_dependency, user_model: user_model_claims_dependency db.commit() -@router.patch("/questionnaire") +@router.patch("/questionnaire", + summary="Update questionnaire.", + status_code=status.HTTP_200_OK, + responses={ + status.HTTP_200_OK: {"description": "Successfully updated questionnaire."}, + status.HTTP_422_UNPROCESSABLE_CONTENT: {"description": "Invalid data in request."}, + status.HTTP_401_UNAUTHORIZED: {"description": "Not authorised. Must be org root user."}, + }) async def update_questionnaire(db: db_dependency, org_model: org_model_root_claim_query_dependency, request_model: OrgPatchQuestionnaireRequest): """ Route for updating questionnaire. The partial bool allows for submission of partially completed questionnaire and/or final "are you sure" check before setting the org to be in "submitted" status, awaiting admin approval. """ + # TODO: Response model org_model.intake_questionnaire = request_model.intake_questionnaire.model_dump() # Allows for partially completed questionnaires to be saved without being submitted for review @@ -104,45 +130,101 @@ async def update_questionnaire(db: db_dependency, org_model: org_model_root_clai db.commit() -@router.patch("/status") +@router.patch("/status", + summary="Update status of organisation.", + status_code=status.HTTP_200_OK, + responses={ + status.HTTP_200_OK: {"description": "Successfully updated organisation status."}, + status.HTTP_422_UNPROCESSABLE_CONTENT: {"description": "Invalid data in request."}, + status.HTTP_401_UNAUTHORIZED: {"description": "Not authorised. Must be super admin."}, + }) async def update_status(db: db_dependency, org_model: org_model_body_dependency, su: super_admin_dependency, request_model: OrgPatchStatusRequest): + # TODO: Response model org_model.status = request_model.status db.commit() -@router.get("/users", response_model=OrgGetUserResponse) +@router.get("/users", + summary="Get email addresses of users of the organisation.", + status_code=status.HTTP_200_OK, + response_model=OrgGetUserResponse, + responses={ + status.HTTP_200_OK: {"description": "Successful retrieval of users."}, + status.HTTP_422_UNPROCESSABLE_CONTENT: {"description": "Org ID missing or invalid."}, + status.HTTP_401_UNAUTHORIZED: {"description": "Not authorised. Must be org root user."}, + }) async def get_users(org_model: org_model_root_claim_query_dependency): return {"users": [user.email for user in org_model.user_rel]} -@router.post("/users") +@router.post("/users", + summary="All user to the organisation.", + status_code=status.HTTP_200_OK, + responses={ + status.HTTP_200_OK: {"description": "Successfully added user to the organisation."}, + status.HTTP_401_UNAUTHORIZED: {"description": "Not authorised. Must be org root user."}, + status.HTTP_422_UNPROCESSABLE_CONTENT: {"description": "Invalid data in request."}, + status.HTTP_409_CONFLICT: {"description": "User is already a member of the organisation."}, + }) async def add_user_to_org(db: db_dependency, org_model: org_model_root_claim_body_dependency, user_model: user_model_body_dependency, request_model: OrgPostUserRequest): + # TODO: response model if user_model in org_model.user_rel: raise Conflict(message="User already a part of this organisation") org_model.user_rel.append(user_model) db.commit() -@router.delete("/", status_code=status.HTTP_204_NO_CONTENT) +@router.delete("/", + summary="Delete organisation from the hub.", + status_code=status.HTTP_204_NO_CONTENT, + responses={ + status.HTTP_204_NO_CONTENT: {"description": "Successfully deleted organisation."}, + status.HTTP_401_UNAUTHORIZED: {"description": "Not authorised. Must be super admin."}, + status.HTTP_422_UNPROCESSABLE_CONTENT: {"description": "Org ID missing or invalid."}, + }) async def delete_organisation_by_id(db: db_dependency, org_model: org_model_body_dependency, su: super_admin_dependency, request_model: OrgDeleteOrgRequest): db.delete(org_model) db.commit() -@router.patch("/root_user", status_code=status.HTTP_204_NO_CONTENT) +@router.patch("/root_user", + summary="Update the root user of the organisation.", + status_code=status.HTTP_200_OK, + responses={ + status.HTTP_200_OK: {"description": "Successfully updated root user."}, + status.HTTP_422_UNPROCESSABLE_CONTENT: {"description": "Invalid data in request."}, + status.HTTP_401_UNAUTHORIZED: {"description": "Not authorised. Must be super admin."}, + }) async def update_root_user(db: db_dependency, org_model: org_model_body_dependency, user_model: user_model_body_dependency, su: super_admin_dependency, request_model: OrgPatchRootRequest): + # TODO: response model org_model.root_user_rel = user_model db.commit() -@router.get("/groups", response_model=OrgGetGroupResponse) +@router.get("/groups", + summary="Get all organisation IAM groups.", + status_code=status.HTTP_200_OK, + response_model=OrgGetGroupResponse, + responses={ + status.HTTP_200_OK: {"description": "Successful retrieval of IAM groups."}, + status.HTTP_422_UNPROCESSABLE_CONTENT: {"description": "Org ID missing or invalid."}, + status.HTTP_401_UNAUTHORIZED: {"description": "Not authorised. Must be org root user."}, + }) async def get_org_groups(org_model: org_model_root_claim_query_dependency): return {"groups": [group.name for group in org_model.group_rel]} -@router.delete("/user", status_code=status.HTTP_204_NO_CONTENT) +@router.delete("/user", + summary="Remove user from organisation.", + status_code=status.HTTP_204_NO_CONTENT, + responses={ + status.HTTP_204_NO_CONTENT: {"description": "Successfully removed user."}, + status.HTTP_401_UNAUTHORIZED: {"description": "Not authorised. Must be org root user."}, + status.HTTP_422_UNPROCESSABLE_CONTENT: {"description": "Invalid data in request."}, + }) async def remove_user_from_org(db: db_dependency, org_model: org_model_root_claim_body_dependency, user_model: user_model_body_dependency, request_model: OrgDeleteUserRequest): + # TODO: response model if user_model not in org_model.user_rel: return @@ -150,7 +232,15 @@ async def remove_user_from_org(db: db_dependency, org_model: org_model_root_clai db.commit() -@router.get("/contact", response_model=OrgGetContactResponse) +@router.get("/contact", + summary="Get contact for organisation.", + status_code=status.HTTP_200_OK, + response_model=OrgGetContactResponse, + responses={ + status.HTTP_200_OK: {"description": "Successful retrieval of contact."}, + status.HTTP_422_UNPROCESSABLE_CONTENT: {"description": "Invalid data in request."}, + status.HTTP_401_UNAUTHORIZED: {"description": "Not authorised. Must be org root user."}, + }) async def get_contact(org_model: org_model_root_claim_query_dependency, contact_type: Annotated[ContactType, Query()]): match contact_type: case "billing": @@ -171,7 +261,15 @@ async def get_contact(org_model: org_model_root_claim_query_dependency, contact_ return {"contact": contact_response} -@router.patch("/contact", response_model=OrgGetContactResponse) +@router.patch("/contact", + summary="Update contact for organisation.", + status_code=status.HTTP_200_OK, + response_model=OrgGetContactResponse, + responses={ + status.HTTP_200_OK: {"description": "Successfully updated contact."}, + status.HTTP_422_UNPROCESSABLE_CONTENT: {"description": "Invalid data in request."}, + status.HTTP_401_UNAUTHORIZED: {"description": "Not authorised. Must be org root user."}, + }) async def update_contact(db: db_dependency, org_model: org_model_root_claim_body_dependency, request_model: OrgPatchContactRequest): match request_model.contact_type: case "billing": From 6a90e03d4056c1b1b2a1531e91b9209b178f5625 Mon Sep 17 00:00:00 2001 From: luxferre Date: Thu, 28 May 2026 16:46:44 +0100 Subject: [PATCH 3/3] docs: org router tag metadata Issue: #13 --- src/main.py | 4 ++++ src/organisation/router.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main.py b/src/main.py index 0092af0..a5ed717 100644 --- a/src/main.py +++ b/src/main.py @@ -36,6 +36,10 @@ tags_metadata = [ "name": "Service", "description": "Services related operations, includes registering services and reissuing API keys", }, + { + "name": "Organisation", + "description": "Organisation related operations, includes getting lists of users etc associated with orgs", + }, ] diff --git a/src/organisation/router.py b/src/organisation/router.py index 8e5433c..97f3e85 100644 --- a/src/organisation/router.py +++ b/src/organisation/router.py @@ -43,7 +43,7 @@ from src.organisation.schemas import OrgPostOrgRequest, OrgPatchQuestionnaireReq router = APIRouter( prefix="/org", - tags=["org"], + tags=["Organisation"], )