""" Router endpoints for organisation module Endpoints: - [get]/id/{org_id} - Retrieves an organisation by its ID - [post]/ - Creates a new organisation - [patch]/{org_id}/questionnaire - Updates the questionnaire data for an organisation (can be partial or final submission) - [patch]/{org_id}/status - Updates the status of an organisation - [patch]/{org_id}/contact - Assigns a contact to an organisation (as billing, security, or owner) - [get]/{org_id}/users - Retrieves all users associated with an organisation - [post]/{org_id}/users - Adds a new user to an organisation - [delete]/{org_id} - Deletes an organisation by ID - [get]/{org_id}/contact/{contact_type} - Retrieves the contact of a specific type (owner, billing, security) for an organisation """ from typing import Annotated from fastapi import APIRouter, HTTPException, status from fastapi.params import Path, Query from sqlalchemy.sql import exists from src.database import db_dependency from src.contact.models import Contact from src.iam.models import Group from src.auth.service import root_user_dependency from src.organisation.dependencies import org_model_dependency from src.organisation.constants import ContactType from src.organisation.models import Organisation as Org, OrgUsers from src.organisation.schemas import OrgOrgPostRequest, OrgQuestionnairePatchRequest, OrgStatusPatchRequest, \ OrgContactPatchRequest, \ OrgUserPostRequest, OrgUserGetResponse, OrgContactGetResponse, OrgOrgGetResponse router = APIRouter( prefix="/org", tags=["org"], ) @router.get("/id/{org_id}", response_model=OrgOrgGetResponse) async def get_org_by_id(db: db_dependency, org_id: Annotated[int, Path(gt=0)]): org_model = (db.query(Org).filter(Org.id == org_id).first()) if org_model is None: raise HTTPException(status_code=404, detail="Organisation not found") response = { "name": org_model.name, "status": org_model.status, "owner_contact": (db.query(Contact).filter(Contact.id == org_model.owner_contact_id).first()), "billing_contact": (db.query(Contact).filter(Contact.id == org_model.billing_contact_id).first()), "security_contact": (db.query(Contact).filter(Contact.id == org_model.security_contact_id).first()), } return response @router.post("/") async def create_org(db: db_dependency, org_request: OrgOrgPostRequest): # TODO: Root user from current user org_model = Org(**org_request.model_dump()) org_model.status = "partial" # Status is always set to partial at first, see update_questionnaire() doc db.add(org_model) db.commit() @router.patch("/{org_id}/questionnaire") async def update_questionnaire(db: db_dependency, q_request: OrgQuestionnairePatchRequest, org_id: Annotated[int, Path(gt=0)]): """ 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. """ org_model = db.query(Org).filter(Org.id == org_id).first() if org_model is None: raise HTTPException(status_code=404, detail="Organisation not found") org_model.intake_questionnaire = q_request.intake_questionnaire.model_dump() # Allows for partially completed questionnaires to be saved without being submitted for review if not q_request.partial: org_model.status = "submitted" db.add(org_model) db.commit() @router.patch("/{org_id}/status") async def update_status(db: db_dependency, status_request: OrgStatusPatchRequest, org_id: Annotated[int, Path(gt=0)]): org_model = db.query(Org).filter(Org.id == org_id).first() if org_model is None: raise HTTPException(status_code=404, detail="Organisation not found") org_model.status = status_request.status db.add(org_model) db.commit() @router.patch("/{org_id}/contact") async def update_contact(db: db_dependency, contact_request: OrgContactPatchRequest, org_id: Annotated[int, Path(gt=0)]): org_model = db.query(Org).filter(Org.id == org_id).first() if org_model is None: raise HTTPException(status_code=404, detail="Organisation not found") match contact_request.contact_type: case "billing": org_model.billing_contact_id = contact_request.contact_id case "security": org_model.security_contact_id = contact_request.contact_id case "owner": org_model.owner_contact_id = contact_request.contact_id case _: raise HTTPException(status_code=422, detail="Invalid contact type") db.add(org_model) db.commit() @router.get("/{org_id}/users", response_model=list[OrgUserGetResponse]) async def get_users(db: db_dependency, org_id: Annotated[int, Path(gt=0)]): org_exists = db.query(exists().where(Org.id == org_id)).scalar() if not org_exists: raise HTTPException(status_code=404, detail="Organisation not found") org_user_models = db.query(OrgUsers).filter(OrgUsers.org_id == org_id).all() return org_user_models @router.post("/{org_id}/users") async def add_user_to_org(db: db_dependency, user_request: OrgUserPostRequest, org_id: Annotated[int, Path(gt=0)]): org_model = (db.query(Org).filter(Org.id == org_id).first()) if org_model is None: raise HTTPException(status_code=404, detail="Organisation not found") org_user_model = OrgUsers(**user_request.model_dump(), org_id=org_id) db.add(org_user_model) db.commit() @router.delete("/{org_id}") async def delete_organisation_by_id(db: db_dependency, org_id: Annotated[int, Path(gt=0)]): org_model = (db.query(Org).filter(Org.id == org_id).first()) if org_model is None: raise HTTPException(status_code=404, detail="Organisation not found") db.delete(org_model) db.commit() @router.get("/{org_id}/contact/{contact_type}", response_model=OrgContactGetResponse) async def get_contact(db: db_dependency, contact_type: ContactType, org_id: Annotated[int, Path(gt=0)]): org_model = db.query(Org).filter(Org.id == org_id).first() if org_model is None: raise HTTPException(status_code=404, detail="Organisation not found") match contact_type: case "billing": contact_id = org_model.billing_contact_id case "security": contact_id = org_model.security_contact_id case "owner": contact_id = org_model.owner_contact_id case _: raise HTTPException(status_code=422, detail="Invalid contact type") contact_model = (db.query(Contact).filter(Contact.id == contact_id).first()) if contact_model is None: raise HTTPException(status_code=404, detail="Contact not found") return contact_model @router.get("/{org_id}/root_user") async def get_org_root_user(db: db_dependency, org_model: org_model_dependency, org_id: Annotated[int, Path(gt=0)]): root_user = org_model.root_user_id return {"root_user": root_user} @router.patch("/{org_id}/root_user") async def update_root_user(db: db_dependency, org_model: org_model_dependency, org_id: Annotated[int, Path(gt=0)], root_user: Annotated[int, Query(gt=0)]): # TODO: Request model, ditch query # TODO: Verify root_user exists, possibly with a user_model_dependency org_model.root_user_id = root_user db.add(org_model) db.commit() # TODO: Response model @router.get("/{org_id}/groups") async def get_org_groups(db: db_dependency, org_id: Annotated[int, Path(gt=0)]): org_group_models = db.query(Group).filter(Group.org_id == org_id).all() # TODO: Response model return org_group_models @router.delete("/{org_id}/user") async def remove_user_from_org(db: db_dependency, org_model: org_model_dependency, org_id: Annotated[int, Path(gt=0)], user_id: Annotated[int, Query(gt=0)]): orguser_model = db.query(OrgUsers).filter(OrgUsers.org_id == org_id, OrgUsers.user_id == user_id).first() if orguser_model is None: raise HTTPException(status_code=status.HTTP_204_NO_CONTENT) db.delete(orguser_model) db.commit() pass