|
| 1 | +""" |
| 2 | +Membership management. |
| 3 | +""" |
| 4 | + |
| 5 | + |
| 6 | +from fastapi import APIRouter, HTTPException |
| 7 | +from pydantic import BaseModel |
| 8 | + |
| 9 | +from soauth.api.dependencies import DatabaseDependency, LoggerDependency |
| 10 | +from soauth.core.members import InstitutionData, InstitutionalMembershipData |
| 11 | +from soauth.toolkit.fastapi import AuthenticatedUserDependency |
| 12 | + |
| 13 | +from soauth.service import membership as membership_service |
| 14 | +from soauth.service import user as user_service |
| 15 | +from soauth.core.uuid import UUID |
| 16 | + |
| 17 | +membership_app = APIRouter(tags=["Membership Management"]) |
| 18 | + |
| 19 | + |
| 20 | +@membership_app.put( |
| 21 | + "", |
| 22 | + summary="Create a new institution", |
| 23 | + description="Create a new institution, only available to either admins or users " |
| 24 | + "with the membership grant.", |
| 25 | + responses={ |
| 26 | + 200: {"description": "Institution created."}, |
| 27 | + 401: {"description": "Unauthorized."}, |
| 28 | + } |
| 29 | +) |
| 30 | +async def create_institution( |
| 31 | + institution: InstitutionData, |
| 32 | + user: AuthenticatedUserDependency, |
| 33 | + conn: DatabaseDependency, |
| 34 | + log: LoggerDependency, |
| 35 | +) -> InstitutionData: |
| 36 | + """ |
| 37 | + Create a new institution. |
| 38 | + """ |
| 39 | + log = log.bind(user_id=user.user_id) |
| 40 | + |
| 41 | + if "admin" not in user.grants and "membership" not in user.grants: |
| 42 | + await log.awarning("institution.create_unauthorized") |
| 43 | + raise HTTPException(status_code=401, detail="Unauthorized") |
| 44 | + |
| 45 | + created_institution = await membership_service.create_institution( |
| 46 | + institution_name=institution.institution_name, |
| 47 | + unit_name=institution.unit_name, |
| 48 | + publication_text=institution.publication_text, |
| 49 | + role=institution.role, |
| 50 | + conn=conn, |
| 51 | + log=log |
| 52 | + ) |
| 53 | + await log.ainfo("institution.created", institution_id=created_institution.institution_id) |
| 54 | + |
| 55 | + return created_institution.to_core() |
| 56 | + |
| 57 | + |
| 58 | +@membership_app.get( |
| 59 | + "/list", |
| 60 | + summary="List all institutions", |
| 61 | + description="Retrieve a list of all institutions, only available to either admins " |
| 62 | + "or users with the membership grant.", |
| 63 | + responses={ |
| 64 | + 200: {"description": "List of institutions."}, |
| 65 | + 401: {"description": "Unauthorized."}, |
| 66 | + } |
| 67 | +) |
| 68 | +async def list_institutions( |
| 69 | + user: AuthenticatedUserDependency, |
| 70 | + conn: DatabaseDependency, |
| 71 | + log: LoggerDependency, |
| 72 | +) -> list[InstitutionData]: |
| 73 | + """ |
| 74 | + List all institutions. |
| 75 | + """ |
| 76 | + log = log.bind(user_id=user.user_id) |
| 77 | + |
| 78 | + if "admin" not in user.grants and "membership" not in user.grants: |
| 79 | + await log.awarning("institution.list_unauthorized") |
| 80 | + raise HTTPException(status_code=401, detail="Unauthorized") |
| 81 | + |
| 82 | + institutions = await membership_service.get_institution_list(conn=conn, log=log) |
| 83 | + await log.adebug("institution.listed") |
| 84 | + |
| 85 | + return [i.to_core() for i in institutions] |
| 86 | + |
| 87 | + |
| 88 | +@membership_app.get( |
| 89 | + "/{institution_id}", |
| 90 | + summary="Get institution by ID", |
| 91 | + description="Retrieve an institution by its ID, only available to either admins " |
| 92 | + "or users with the membership grant.", |
| 93 | + responses={ |
| 94 | + 200: {"description": "Institution details."}, |
| 95 | + 401: {"description": "Unauthorized."}, |
| 96 | + 404: {"description": "Institution not found."}, |
| 97 | + } |
| 98 | +) |
| 99 | +async def get_institution( |
| 100 | + institution_id: UUID, |
| 101 | + user: AuthenticatedUserDependency, |
| 102 | + conn: DatabaseDependency, |
| 103 | + log: LoggerDependency, |
| 104 | +) -> dict[str, InstitutionData | list[InstitutionalMembershipData]]: |
| 105 | + """ |
| 106 | + Get institution by ID. |
| 107 | + """ |
| 108 | + log = log.bind(user_id=user.user_id) |
| 109 | + |
| 110 | + if "admin" not in user.grants and "membership" not in user.grants: |
| 111 | + await log.awarning("institution.get_unauthorized", institution_id=institution_id) |
| 112 | + raise HTTPException(status_code=401, detail="Unauthorized") |
| 113 | + |
| 114 | + institution = await membership_service.read_by_id(institution_id=institution_id, conn=conn, log=log) |
| 115 | + await log.adebug("institution.retrieved", institution_id=institution_id) |
| 116 | + |
| 117 | + members = await membership_service.get_membership_list_of_institution(institution_id=institution_id, conn=conn, log=log) |
| 118 | + await log.adebug("institution.members_retrieved", institution_id=institution_id, number_of_members=len(members)) |
| 119 | + |
| 120 | + return { |
| 121 | + "institution": institution.to_core(), |
| 122 | + "members": [x.to_core() for x in members] |
| 123 | + } |
| 124 | + |
| 125 | + |
| 126 | +@membership_app.post( |
| 127 | + "/{institution_id}/add_member/{user_id}", |
| 128 | + summary="Add a member to an institution", |
| 129 | + description="Add a user as a member to an institution, only available to either " |
| 130 | + "admins or users with the membership grant.", |
| 131 | + responses={ |
| 132 | + 200: {"description": "User added as member."}, |
| 133 | + 401: {"description": "Unauthorized."}, |
| 134 | + 404: {"description": "Institution or user not found."}, |
| 135 | + 400: {"description": "User is not a member."}, |
| 136 | + }) |
| 137 | +async def add_member_to_institution( |
| 138 | + institution_id: UUID, |
| 139 | + user_id: UUID, |
| 140 | + user: AuthenticatedUserDependency, |
| 141 | + conn: DatabaseDependency, |
| 142 | + log: LoggerDependency, |
| 143 | +) -> None: |
| 144 | + """ |
| 145 | + Add a member to an institution. |
| 146 | + """ |
| 147 | + log = log.bind(user_id=user.user_id, institution_id=institution_id, new_member_id=user_id) |
| 148 | + |
| 149 | + if "admin" not in user.grants and "membership" not in user.grants: |
| 150 | + await log.awarning("institution.add_member_unauthorized", institution_id=institution_id) |
| 151 | + raise HTTPException(status_code=401, detail="Unauthorized") |
| 152 | + |
| 153 | + await membership_service.add_member_to_institution( |
| 154 | + institution_id=institution_id, |
| 155 | + user_id=user_id, |
| 156 | + conn=conn, |
| 157 | + log=log |
| 158 | + ) |
| 159 | + await log.ainfo("institution.member_added") |
| 160 | + |
| 161 | + return None |
| 162 | + |
| 163 | + |
| 164 | +@membership_app.post( |
| 165 | + "/{institution_id}/remove_member/{user_id}", |
| 166 | +) |
| 167 | +async def remove_member_from_institution(): |
| 168 | + raise NotImplementedError() |
| 169 | + |
| 170 | + |
| 171 | +@membership_app.get( |
| 172 | + "/details/{user_id}", |
| 173 | + summary="Get member details", |
| 174 | + description="Retrieve member details by user ID, only available to either admins " |
| 175 | + "or users with the membership grant.", |
| 176 | + responses={ |
| 177 | + 200: {"description": "Member details."}, |
| 178 | + 401: {"description": "Unauthorized."}, |
| 179 | + 404: {"description": "User not found."}, |
| 180 | + } |
| 181 | +) |
| 182 | +async def get_member_details( |
| 183 | + user_id: UUID, |
| 184 | + user: AuthenticatedUserDependency, |
| 185 | + conn: DatabaseDependency, |
| 186 | + log: LoggerDependency, |
| 187 | +) -> dict: |
| 188 | + """ |
| 189 | + Get member details by user ID. |
| 190 | + """ |
| 191 | + log = log.bind(user_id=user.user_id, queried_user_id=user_id) |
| 192 | + |
| 193 | + if "admin" not in user.grants and "membership" not in user.grants: |
| 194 | + await log.awarning("membership.get_details_unauthorized", queried_user_id=user_id) |
| 195 | + raise HTTPException(status_code=401, detail="Unauthorized") |
| 196 | + |
| 197 | + user = await user_service.read_by_id(user_id=user_id, conn=conn) |
| 198 | + |
| 199 | + await log.adebug("membership.details_retrieved", queried_user_id=user_id) |
| 200 | + |
| 201 | + if user.membership is None: |
| 202 | + raise HTTPException(status_code=404, detail="User not found") |
| 203 | + |
| 204 | + return user.membership.to_core() |
| 205 | + |
| 206 | + |
| 207 | +class PromoteToMemberRequest(BaseModel): |
| 208 | + first_name: str |
| 209 | + last_name: str |
| 210 | + email: str |
| 211 | + status: str |
| 212 | + confluence: str | None = None |
| 213 | + website: str | None = None |
| 214 | + orcid: str | None = None |
| 215 | + |
| 216 | +@membership_app.post( |
| 217 | + "/promote/{user_id}", |
| 218 | + summary="Promote a user to member", |
| 219 | + description="Promote a user to member, only available to either admins " |
| 220 | + "or users with the membership grant.", |
| 221 | + responses={ |
| 222 | + 200: {"description": "User promoted to member."}, |
| 223 | + 401: {"description": "Unauthorized."}, |
| 224 | + 404: {"description": "User not found."}, |
| 225 | + } |
| 226 | +) |
| 227 | +async def promote_user_to_member( |
| 228 | + user_id: UUID, |
| 229 | + details: PromoteToMemberRequest, |
| 230 | + user: AuthenticatedUserDependency, |
| 231 | + conn: DatabaseDependency, |
| 232 | + log: LoggerDependency, |
| 233 | +) -> None: |
| 234 | + """ |
| 235 | + Promote a user to member. |
| 236 | + """ |
| 237 | + log = log.bind(user_id=user.user_id, promoted_user_id=user_id) |
| 238 | + |
| 239 | + if "admin" not in user.grants and "membership" not in user.grants: |
| 240 | + await log.awarning("membership.promote_unauthorized", promoted_user_id=user_id) |
| 241 | + raise HTTPException(status_code=401, detail="Unauthorized") |
| 242 | + |
| 243 | + await membership_service.update_user_to_be_member( |
| 244 | + user_id=user_id, |
| 245 | + first_name=details.first_name, |
| 246 | + last_name=details.last_name, |
| 247 | + email=details.email, |
| 248 | + status=details.status, |
| 249 | + confluence=details.confluence, |
| 250 | + website=details.website, |
| 251 | + orcid=details.orcid, |
| 252 | + conn=conn, |
| 253 | + log=log |
| 254 | + ) |
| 255 | + await log.ainfo("membership.user_promoted") |
| 256 | + |
| 257 | + return None |
| 258 | + |
0 commit comments