Compare commits

..

No commits in common. "71f26a4c9b0e89562ea7124dead2b2fa54cffd1b" and "88a64d204766e9afe0406aaa3bf71fb4a2570d9d" have entirely different histories.

48 changed files with 284 additions and 168 deletions

View file

@ -1,10 +0,0 @@
SECRET_KEY=""
OIDC_CONFIG="https://sso.sr2.uk/realms/sr2/.well-known/openid-configuration"
OIDC_ISSUER="https://sso.sr2.uk/realms/sr2"
OIDC_AUDIENCE="account"
CLIENT_ID=""
DATABASE_NAME="cloud-api"
DATABASE_PORT="5432"
DATABASE_HOSTNAME="localhost"
DATABASE_CREDENTIALS="user:password"

View file

@ -1,3 +1,7 @@
""" """
Configurations for the admin module Configurations for the admin module
Configurations:
- List: Description
- Configs: Description
""" """

View file

@ -1,3 +1,7 @@
""" """
Constants for the admin module Constants and error codes for the admin module
Constants:
- List: Description
- Consts: Description
""" """

View file

@ -1,3 +1,11 @@
""" """
Dependencies for the admin module Router dependencies for the admin module
Classes:
- List: Description
- Classes: Description
Functions:
- List: Description
- Functions: Description
""" """

View file

@ -1,3 +1,7 @@
""" """
Custom exceptions for the admin module Module specific exceptions for the admin module
Exceptions:
- List: Description
- Exceptions: Description
""" """

View file

@ -1,3 +1,7 @@
""" """
Database models for the admin module Database models for the admin module
Models:
- List: Description
- Models: Description
""" """

View file

@ -1,8 +1,9 @@
""" """
Router endpoints for the admin module Router endpoints for the admin module
Exports: Endpoints:
- router: fastapi.APIRouter - List: Description
- Endpoints: Description
""" """
from fastapi import APIRouter from fastapi import APIRouter

View file

@ -1,3 +1,7 @@
""" """
Pydantic models for the admin module Pydantic models for the admin module
Models:
- List: Description
- Models: Description
""" """

View file

@ -1,3 +1,11 @@
""" """
Module specific business logic for the admin module Module specific business logic for the admin module
Classes:
- List: Description
- Classes: Description
Functions:
- List: Description
- Functions: Description
""" """

View file

@ -1,3 +1,11 @@
""" """
Non-business logic reusable functions and classes for the admin module Non-business logic reusable functions and classes for the admin module
Classes:
- List: Description
- Classes: Description
Functions:
- List: Description
- Functions: Description
""" """

View file

@ -1,8 +1,9 @@
""" """
Configurations for the auth module Configurations for auth module, import auth_settings
Exports: Configurations:
- auth_settings: Contains OIDC information - List: Description
- Configs: Description
""" """
from src.config import CustomBaseSettings from src.config import CustomBaseSettings

View file

@ -1,3 +1,7 @@
""" """
Constants for the auth module Constants and error codes for auth module
Constants:
- List: Description
- Consts: Description
""" """

View file

@ -1,17 +1,18 @@
""" """
Auth dependencies Router dependencies for auth module
Exports: Classes:
- org_query_user_claims_dependency: bool: Verifies user belongs to org - List: Description
- org_model_root_claim_query_dependency: org_model: verifies org exists and user is either root or su, gets org from query - Classes: Description
- org_model_root_claim_body_dependency: org_model: verifies org exists and user is either root or su, gets org from body
- super_admin_dependency: user_model: verifies the user is a super admin Functions:
- List: Description
- Functions: Description
""" """
from typing import Annotated from typing import Annotated
from fastapi import Depends from fastapi import Depends
from src.user.dependencies import user_model_claims_dependency from src.user.dependencies import user_model_claims_dependency
from src.user.models import User
from src.organisation.dependencies import org_model_query_dependency, org_model_body_dependency from src.organisation.dependencies import org_model_query_dependency, org_model_body_dependency
from src.organisation.models import Organisation as Org from src.organisation.models import Organisation as Org
@ -68,4 +69,4 @@ async def user_model_super_admin(user_model: user_model_claims_dependency):
raise UnauthorizedException() raise UnauthorizedException()
super_admin_dependency = Annotated[type[User], Depends(user_model_super_admin)] super_admin_dependency = Annotated[bool, Depends(user_model_super_admin)]

View file

@ -1,8 +1,9 @@
""" """
Module specific exceptions for the auth module Module specific exceptions for auth module
Exceptions: Exceptions:
- UnauthorizedException: Takes an optional message string - List: Description
- Exceptions: Description
""" """
from typing import Optional from typing import Optional

View file

@ -1,3 +1,7 @@
""" """
Database models for the auth module Database models for auth module
Models:
- List: Description
- Models: Description
""" """

View file

@ -1,8 +1,8 @@
""" """
Router endpoints for the auth module Router endpoints for auth module
Contains oauth registration
Exports: Endpoints:
- router: fastapi.APIRouter
""" """
from fastapi import APIRouter from fastapi import APIRouter

View file

@ -1,3 +1,7 @@
""" """
Pydantic models for the auth module Pydantic models for auth module
Models:
- List: Description
- Models: Description
""" """

View file

@ -1,8 +1,8 @@
""" """
Module specific business logic for the auth module Module specific business logic for auth module
Exports: Exports:
- claims_dependency: Dict[str, Any] containing OIDC claims and database ID - claims_dependency
""" """
import json import json
import requests import requests

View file

@ -1,3 +1,11 @@
""" """
Non-business logic reusable functions and classes for the auth module Non-business logic reusable functions and classes for auth module
Classes:
- List: Description
- Classes: Description
Functions:
- List: Description
- Functions: Description
""" """

View file

@ -1,3 +1,7 @@
""" """
Configurations for the contact module Configurations for contact module
Configurations:
- List: Description
- Configs: Description
""" """

View file

@ -1,3 +1,7 @@
""" """
Constants for the contact module Constants and error codes for contact module
Constants:
- List: Description
- Consts: Description
""" """

View file

@ -1,3 +1,11 @@
""" """
Dependencies for the contact module Router dependencies for contact module
Classes:
- List: Description
- Classes: Description
Functions:
- List: Description
- Functions: Description
""" """

View file

@ -1,8 +1,9 @@
""" """
Exceptions related to the contact module Module specific exceptions for contact module
Exports: Exceptions:
- ContactNotFoundException: Takes an optional contact ID int - List: Description
- Exceptions: Description
""" """
from typing import Optional from typing import Optional

View file

@ -1,9 +1,9 @@
""" """
Database models for the contact module Database models for contact module
Models: Models:
- Contact: id[pk], email, first_name, last_name, phonenumber, vat_number - Contact: id[pk], email, first_name, last_name, phonenumber, vat_number
street_address, street_address_line_2, post_office_box_number, address_locality, country_code, address_region, postal_code street_address, post_office_box_number, address_locality, country_code, address_region, postal_code
""" """
from sqlalchemy import Column, Integer, String, ForeignKey from sqlalchemy import Column, Integer, String, ForeignKey

View file

@ -1,5 +1,13 @@
""" """
Router endpoints for the contact module Router endpoints for contact module
Endpoints:
- [get]/{contact_id} - Returns non-address type details for contact
- [get]/{contact_id}/address - Returns address details for contact
- [get]/{contact_id}/orgs - Returns a list of orgs which the contact is assigned to, and what they are assigned as
- [post]/ - Creates a new contact
- [patch]/{contact_id} - Updates the details of an existing contact
- [delete]/{contact_id} - Deletes a contact by ID
""" """
from fastapi import APIRouter from fastapi import APIRouter

View file

@ -1,9 +1,9 @@
""" """
Pydantic models for the contact module Pydantic models for contact module
Models: Models:
- ContactAddress - List: Description
- ContactModel: Contains ContactAddress as a property - Models: Description
""" """
from typing import Optional from typing import Optional

View file

@ -1,3 +1,11 @@
""" """
Module specific business logic for the contact module Module specific business logic for contact module
Classes:
- List: Description
- Classes: Description
Functions:
- List: Description
- Functions: Description
""" """

View file

@ -1,3 +1,11 @@
""" """
Non-business logic reusable functions and classes for the contact module Non-business logic reusable functions and classes for contact module
Classes:
- List: Description
- Classes: Description
Functions:
- List: Description
- Functions: Description
""" """

View file

@ -1,3 +1,7 @@
""" """
Configurations for the IAM module Configurations for <this module>
Configurations:
- List: Description
- Configs: Description
""" """

View file

@ -1,3 +1,7 @@
""" """
Constants for the IAM module Constants and error codes for <this module>
Constants:
- List: Description
- Consts: Description
""" """

View file

@ -1,10 +1,13 @@
""" """
Dependencies for the IAM module Router dependencies for the IAM module
Exports: Classes:
- group_model_query_dependency: group_model: Gets group model from db, if it exists. Uses group_id from query param. - List: Description
- group_model_body_dependency: group_model: Gets group model from db, if it exists. Uses group_id from request body. - Classes: Description
- perm_model_body_dependency: perm_model: Gets perm model from db, if it exists. Uses perm_id from request body.
Functions:
- List: Description
- Functions: Description
""" """
from typing import Annotated, Optional from typing import Annotated, Optional
@ -12,7 +15,7 @@ from fastapi import Depends, Query
from src.database import db_dependency from src.database import db_dependency
from src.iam.models import Group, Permission from src.iam.models import Group
from src.iam.exceptions import GroupNotFoundException, PermNotFoundException from src.iam.exceptions import GroupNotFoundException, PermNotFoundException
from src.iam.schemas import GroupIDMixin, PermIDMixin from src.iam.schemas import GroupIDMixin, PermIDMixin
@ -40,14 +43,14 @@ def get_group_model_body(db: db_dependency, request_model: Optional[GroupIDMixin
group_model_body_dependency = Annotated[type[Group], Depends(get_group_model_body)] group_model_body_dependency = Annotated[type[Group], Depends(get_group_model_body)]
def get_perm_model_body(db: db_dependency, request_model: Optional[PermIDMixin] = None) -> type[Permission]: def get_perm_model_body(db: db_dependency, request_model: Optional[PermIDMixin] = None) -> type[Group]:
perm_id = getattr(request_model, "permission_id", None) perm_id = getattr(request_model, "permission_id", None)
if perm_id is None: if perm_id is None:
raise PermNotFoundException raise PermNotFoundException
perm_model = db.get(Permission, perm_id) group_model = db.get(Group, perm_id)
if perm_model is None: if group_model is None:
raise PermNotFoundException(perm_id) raise PermNotFoundException(perm_id)
return perm_model return group_model
perm_model_body_dependency = Annotated[type[Permission], Depends(get_perm_model_body)] perm_model_body_dependency = Annotated[type[Group], Depends(get_perm_model_body)]

View file

@ -1,9 +1,9 @@
""" """
Exceptions related to the IAM module Module specific exceptions for the IAM module
Exceptions: Exceptions:
- GroupNotFoundException: Takes an optional group_id int - List: Description
- PermNotFoundException: Takes an optional perm_id int - Exceptions: Description
""" """
from typing import Optional from typing import Optional

View file

@ -2,11 +2,8 @@
Database models for the IAM module Database models for the IAM module
Models: Models:
- Permission: - List: Description
- id[PK], resource[U1], action[U1], service_id[FK][U1] - Models: Description
- service_rel: ORM relationship over service_id FK
- group_rel: ORM relationship backpops to Group.permission_rel
- service_name: Calc property service_rel.name
""" """
from sqlalchemy import Column, Integer, String, ForeignKey, UniqueConstraint from sqlalchemy import Column, Integer, String, ForeignKey, UniqueConstraint
from sqlalchemy.orm import relationship from sqlalchemy.orm import relationship

View file

@ -1,19 +1,9 @@
""" """
Router endpoints for IAM Router endpoints for <this module>
Endpoints: Endpoints:
- [POST](/iam/can_act_on_resource): [API key & user claim]: Service access point to verify user permissions - List: Description
- [GET](/iam/group/permissions): [root user]: Gets list of perms(service, resource, action) the given group(id) has - Endpoints: Description
- [DELETE](/iam/group/permissions): [root user]: Removes a given perm(id) from the given group(id)
- [GET](/iam/group/users): [root user]: Gets a list of users(id, name, email) that are assigned to the given group(id)
- [POST](/iam/group): [root user]: Creates a new group for the given org(id)
- [PUT](/iam/group/permission): [root user]: Assigns a perm(id) to the given group(id)
- [PUT](/iam/group/user): [root user]: Assigns a user(id) to a group(id)
- [DELETE](/iam/group/user): [root user]: Removes a user(id) from the given group(id)
- [GET](/iam/permissions): [root user]: Gets a list of all permissions
- [POST](/iam/permission): [super admin]: Creates a new permission
- [DELETE](/iam/permission): [super admin]: Removes a permission
- [GET](/iam/permissions/search): [root user]: Returns a list of permissions matching a filter(service|resource|action)
""" """
from fastapi import APIRouter, status from fastapi import APIRouter, status
from sqlalchemy.exc import IntegrityError from sqlalchemy.exc import IntegrityError
@ -35,10 +25,10 @@ from src.iam.service import service_key_dependency
from src.iam.models import Permission as Perm, GroupPermissions as GPerms, Group, UserGroups from src.iam.models import Permission as Perm, GroupPermissions as GPerms, Group, UserGroups
from src.iam.dependencies import group_model_query_dependency, group_model_body_dependency, perm_model_body_dependency from src.iam.dependencies import group_model_query_dependency, group_model_body_dependency, perm_model_body_dependency
from src.iam.schemas import IAMGetGroupPermissionsResponse, IAMGetGroupUsersResponse, IAMPostGroupRequest, \ from src.iam.schemas import IAMGetGroupPermissionsResponse, IAMGetGroupUsersResponse, IAMPostGroupRequest, \
GroupSchema, IAMPostGroupResponse, IAMPutGroupPermissionRequest, IAMPutGroupPermissionResponse, \ GroupResponse, IAMPostGroupResponse, IAMPutGroupPermissionRequest, IAMPutGroupPermissionResponse, \
IAMPutGroupUserRequest, IAMPutGroupUserResponse, IAMDeleteGroupPermissionRequest, IAMDeleteGroupPermissionResponse, \ IAMPutGroupUserRequest, IAMPutGroupUserResponse, IAMDeleteGroupPermissionRequest, IAMDeleteGroupPermissionResponse, \
IAMDeleteGroupUserRequest, IAMDeleteGroupUserResponse, IAMGetPermissionsResponse, IAMPostPermissionRequest, \ IAMDeleteGroupUserRequest, IAMDeleteGroupUserResponse, IAMGetPermissionsResponse, IAMPostPermissionRequest, \
IAMPostPermissionResponse, PermissionSchema, IAMDeletePermissionRequest, IAMGetPermissionsSearchRequest, IAMGetPermissionsSearchResponse IAMPostPermissionResponse, PermissionResponse, IAMDeletePermissionRequest, IAMGetPermissionsSearchRequest, IAMGetPermissionsSearchResponse
router = APIRouter( router = APIRouter(
tags=["IAM"], tags=["IAM"],
@ -101,7 +91,7 @@ async def create_group(db: db_dependency, org_model: org_model_root_claim_body_d
except IntegrityError as e: except IntegrityError as e:
if isinstance(e.orig, errors.UniqueViolation): if isinstance(e.orig, errors.UniqueViolation):
raise Conflict("Group with this name already exists") raise Conflict("Group with this name already exists")
response = GroupSchema(**group_model.__dict__) response = GroupResponse(**group_model.__dict__)
db.commit() db.commit()
return {"group": response} return {"group": response}
@ -117,7 +107,7 @@ async def add_group_permission(db: db_dependency, group_model: group_model_body_
group_model.permission_rel.append(perm_model) group_model.permission_rel.append(perm_model)
db.flush() db.flush()
response = IAMPutGroupPermissionResponse(group=GroupSchema(**group_model.__dict__), permissions=group_model.permission_rel) response = IAMPutGroupPermissionResponse(group=GroupResponse(**group_model.__dict__), permissions=group_model.permission_rel)
db.commit() db.commit()
return response return response
@ -132,7 +122,7 @@ async def add_group_user(db: db_dependency, group_model: group_model_body_depend
group_model.user_rel.append(user_model) group_model.user_rel.append(user_model)
db.flush() db.flush()
response = IAMPutGroupUserResponse(group=GroupSchema(**group_model.__dict__), users=group_model.user_rel) response = IAMPutGroupUserResponse(group=GroupResponse(**group_model.__dict__), users=group_model.user_rel)
db.commit() db.commit()
return response return response
@ -144,8 +134,8 @@ async def remove_group_permissions(db: db_dependency, group_model: group_model_b
group_model.permission_rel.remove(perm_model) group_model.permission_rel.remove(perm_model)
db.flush() db.flush()
response = IAMDeleteGroupPermissionResponse(group=GroupSchema(**group_model.__dict__), response = IAMDeleteGroupPermissionResponse(group=GroupResponse(**group_model.__dict__),
permissions=group_model.permission_rel) permissions=group_model.permission_rel)
db.commit() db.commit()
return response return response
@ -157,7 +147,7 @@ async def remove_group_user(db: db_dependency, group_model: group_model_body_dep
user_model.group_rel.remove(group_model) user_model.group_rel.remove(group_model)
db.flush() db.flush()
response = IAMDeleteGroupUserResponse(group=GroupSchema(**group_model.__dict__), users=group_model.user_rel) response = IAMDeleteGroupUserResponse(group=GroupResponse(**group_model.__dict__), users=group_model.user_rel)
db.commit() db.commit()
return response return response
@ -179,7 +169,7 @@ async def create_new_permission(db: db_dependency, su: super_admin_dependency, r
if isinstance(e.orig, errors.UniqueViolation): if isinstance(e.orig, errors.UniqueViolation):
raise Conflict(message="Permission already exists") raise Conflict(message="Permission already exists")
db.flush() db.flush()
response = IAMPostPermissionResponse(permission=PermissionSchema(**perm_model.__dict__)) response = IAMPostPermissionResponse(permission=PermissionResponse(**perm_model.__dict__))
db.commit() db.commit()
return response return response

View file

@ -1,10 +1,9 @@
""" """
Pydantic models for the IAM module Pydantic models for the IAM module
Models follow the nomenclature of: Models:
- Sub-models: "<Resource><Opt:>Schema" - List: Description
- Mixins: "<Attribute>Mixin" - Models: Description
- Models: "<Module><Method><Resource><Opt:Resource><Direction>" ie "IAMGetGroupPermissionsResponse"
""" """
from typing import Optional from typing import Optional
@ -15,20 +14,20 @@ from src.schemas import CustomBaseModel
from user.schemas import UserIDMixin from user.schemas import UserIDMixin
class UserSchema(CustomBaseModel): class UserResponse(CustomBaseModel):
id: int id: int
first_name: str first_name: str
last_name: str last_name: str
email: EmailStr email: EmailStr
class PermissionSchema(CustomBaseModel): class PermissionResponse(CustomBaseModel):
model_config = ConfigDict(from_attributes=True, extra="ignore") model_config = ConfigDict(from_attributes=True, extra="ignore")
service_name: str service_name: str
resource: str resource: str
action: str action: str
class GroupSchema(CustomBaseModel): class GroupResponse(CustomBaseModel):
id: int id: int
name: str name: str
@ -39,47 +38,47 @@ class PermIDMixin(CustomBaseModel):
permission_id: int permission_id: int
class IAMGetGroupPermissionsResponse(CustomBaseModel): class IAMGetGroupPermissionsResponse(CustomBaseModel):
permissions: list[PermissionSchema] permissions: list[PermissionResponse]
class IAMGetGroupUsersResponse(CustomBaseModel): class IAMGetGroupUsersResponse(CustomBaseModel):
users : list[UserSchema] users : list[UserResponse]
class IAMPostGroupRequest(OrgIDMixin): class IAMPostGroupRequest(OrgIDMixin):
name: str name: str
class IAMPostGroupResponse(CustomBaseModel): class IAMPostGroupResponse(CustomBaseModel):
group: GroupSchema group: GroupResponse
class IAMPutGroupPermissionRequest(GroupIDMixin, PermIDMixin): class IAMPutGroupPermissionRequest(GroupIDMixin, PermIDMixin):
pass pass
class IAMPutGroupPermissionResponse(CustomBaseModel): class IAMPutGroupPermissionResponse(CustomBaseModel):
group: GroupSchema group: GroupResponse
permissions: list[PermissionSchema] permissions: list[PermissionResponse]
class IAMPutGroupUserRequest(GroupIDMixin, UserIDMixin): class IAMPutGroupUserRequest(GroupIDMixin, UserIDMixin):
pass pass
class IAMPutGroupUserResponse(CustomBaseModel): class IAMPutGroupUserResponse(CustomBaseModel):
group: GroupSchema group: GroupResponse
users: list[UserSchema] users: list[UserResponse]
class IAMDeleteGroupPermissionRequest(GroupIDMixin, PermIDMixin): class IAMDeleteGroupPermissionRequest(GroupIDMixin, PermIDMixin):
pass pass
class IAMDeleteGroupPermissionResponse(CustomBaseModel): class IAMDeleteGroupPermissionResponse(CustomBaseModel):
group: GroupSchema group: GroupResponse
permissions: list[PermissionSchema] permissions: list[PermissionResponse]
class IAMDeleteGroupUserRequest(GroupIDMixin, UserIDMixin): class IAMDeleteGroupUserRequest(GroupIDMixin, UserIDMixin):
pass pass
class IAMDeleteGroupUserResponse(CustomBaseModel): class IAMDeleteGroupUserResponse(CustomBaseModel):
group: GroupSchema group: GroupResponse
users: list[UserSchema] users: list[UserResponse]
class IAMGetPermissionsResponse(CustomBaseModel): class IAMGetPermissionsResponse(CustomBaseModel):
permissions: list[PermissionSchema] permissions: list[PermissionResponse]
class IAMPostPermissionRequest(CustomBaseModel): class IAMPostPermissionRequest(CustomBaseModel):
service_id: int service_id: int
@ -87,7 +86,7 @@ class IAMPostPermissionRequest(CustomBaseModel):
action: str action: str
class IAMPostPermissionResponse(CustomBaseModel): class IAMPostPermissionResponse(CustomBaseModel):
permission: PermissionSchema permission: PermissionResponse
class IAMDeletePermissionRequest(PermIDMixin): class IAMDeletePermissionRequest(PermIDMixin):
pass pass
@ -98,4 +97,4 @@ class IAMGetPermissionsSearchRequest(CustomBaseModel):
action: Optional[str] = None action: Optional[str] = None
class IAMGetPermissionsSearchResponse(CustomBaseModel): class IAMGetPermissionsSearchResponse(CustomBaseModel):
permissions: list[PermissionSchema] permissions: list[PermissionResponse]

View file

@ -1,8 +1,7 @@
""" """
Business logic reusable functions related to IAM Module specific business logic for <this module>
Exports: Exports service_key_dependency
- service_key_dependency: bool: verifies request headers contain the correct api key for the service
""" """
from typing import Annotated from typing import Annotated

View file

@ -1,3 +1,11 @@
""" """
Non-business logic reusable functions and classes for the IAM module Non-business logic reusable functions and classes for <this module>
Classes:
- List: Description
- Classes: Description
Functions:
- List: Description
- Functions: Description
""" """

View file

@ -1,3 +1,7 @@
""" """
Configurations for the organisation module Configurations for organisation module
Configurations:
- List: Description
- Configs: Description
""" """

View file

@ -1,5 +1,5 @@
""" """
Constants for the organisation module Constants and error codes for organisation module
Classes: Classes:
- Status(StrEnum): PARTIAL, SUBMITTED, REMEDIATION, APPROVED, REJECTED, REMOVED - Status(StrEnum): PARTIAL, SUBMITTED, REMEDIATION, APPROVED, REJECTED, REMOVED

View file

@ -1,9 +1,13 @@
""" """
Dependencies related to the organisation module Router dependencies for organisation module
Exports: Classes:
- org_model_query_dependency: org_model: Gets org model from db, if it exists. Uses org_id from query param. Also verifies if the org has been approved. - List: Description
- org_model_body_dependency: org_model: Gets org model from db, if it exists. Uses org_id from request body. Also verifies if the org has been approved. - Classes: Description
Functions:
- List: Description
- Functions: Description
""" """
from typing import Annotated, Optional from typing import Annotated, Optional
@ -22,7 +26,7 @@ def get_org_model(db, request: Request, org_id: int):
if org_model is None: if org_model is None:
raise OrgNotFoundException(org_id) raise OrgNotFoundException(org_id)
pre_approval_endpoints = ["PATCH/org/status", "PATCH/org/questionnaire", "GET/org/id", "GET/org/contact", "PATCH/org/contact"] pre_approval_endpoints = ["PATCH/org/status", "PATCH/org/questionnaire", "GET/org/id"]
current_request = f"{request.method}{request.url.path}" current_request = f"{request.method}{request.url.path}"
if current_request not in pre_approval_endpoints and org_model.status != OrgStatus.APPROVED: if current_request not in pre_approval_endpoints and org_model.status != OrgStatus.APPROVED:
raise AwaitingApprovalException(org_id) raise AwaitingApprovalException(org_id)

View file

@ -1,9 +1,9 @@
""" """
Exceptions related to the organisation module Module specific exceptions for organisation module
Exceptions: Exceptions:
- OrgNotFoundException: Takes an optional org_id int - List: Description
- AwaitingApprovalException: Takes an optional org_id int - Exceptions: Description
""" """
from typing import Optional from typing import Optional

View file

@ -2,16 +2,9 @@
Database models for organisation module Database models for organisation module
Models: Models:
- Organisation: - Organisation: id[pk], name, status, intake_questionnaire,
- id[PK], name, status, intake_questionnaire, root_user_id[FK], billing_contact_id[FK], security_contact_id[FK], owner_contact_id[FK] billing_contact_id[fk], security_contact_id[fk], owner_contact_id[fk]
- user_rel: ORM relationship to User via OrgUsers relationship table - OrgUsers: org_id[fk][cpk], user_id[fk][cpk], is_admin
- group_rel: ORM relationship to Group, backprops Group.org_rel
- root_user_rel: ORM relationship to User with root_user_id FK
- root_user_email: Calc property root_user_rel.email
- billing_contact_rel: ORM relationship to Contact with billing_contact FK
- security_contact_rel: ORM relationship to Contact with security_contact FK
- owner_contact_rel: ORM relationship to Contact with owner_contact FK
- OrgUsers: org_id[FK][PK], user_id[FK][PK]
""" """
from sqlalchemy import Column, Integer, String, ForeignKey, JSON from sqlalchemy import Column, Integer, String, ForeignKey, JSON
from sqlalchemy.orm import relationship from sqlalchemy.orm import relationship

View file

@ -2,18 +2,15 @@
Router endpoints for organisation module Router endpoints for organisation module
Endpoints: Endpoints:
- [GET](/org/id): [root user]: Get details about an organisation(id) - [get]/id/{org_id} - Retrieves an organisation by its ID
- [POST](/org/): [oidc claim]: Creates an organisation, adds the current user as a user and sets them to be the root user - [post]/ - Creates a new organisation
- [PATCH](/org/questionnaire): [root user]: Updates the org's intake questionnaire and optionally be submitted for review - [patch]/{org_id}/questionnaire - Updates the questionnaire data for an organisation (can be partial or final submission)
- [PATCH](/org/status): [super admin]: Allows a super admin to update an org(id) status(Status enum) - [patch]/{org_id}/status - Updates the status of an organisation
- [GET](/org/users): [root user]: Gets a list of the org(id) users(email) - [patch]/{org_id}/contact - Assigns a contact to an organisation (as billing, security, or owner)
- [POST](/org/users): [root user]: Adds a new user(id) to the org(id) - [get]/{org_id}/users - Retrieves all users associated with an organisation
- [DELETE](/org/): [super admin]: Deletes an organisation(id) - [post]/{org_id}/users - Adds a new user to an organisation
- [PATCH](/org/root_user): [super admin]: Updates an org(id) root user(id) - [delete]/{org_id} - Deletes an organisation by ID
- [GET](/org/groups): [root user]: Gets a list of the org(id) groups(name) - [get]/{org_id}/contact/{contact_type} - Retrieves the contact of a specific type (owner, billing, security) for an organisation
- [DELETE](/org/user): [root user]: Removes a user(id) from an org(id)
- [GET](/org/contact): [root user]: Gets the (contact_type) contact for an org(id)
- [PATCH](/org/contact): [root user]: Updates the (contact_type) contact for an org(id). Any number of details can be changed.
""" """
from typing import Annotated, Optional from typing import Annotated, Optional

View file

@ -1,10 +1,9 @@
""" """
Pydantic models for organisation module Pydantic models for organisation module
Models follow the nomenclature of: Models:
- Sub-models: "<Resource><Opt:>Schema" - List: Description
- Mixins: "<Attribute>Mixin" - Models: Description
- Models: "<Module><Method><Resource><Opt:Resource><Direction>" ie "OrgPostOrgRequest"
""" """
from typing import Optional from typing import Optional
@ -17,7 +16,7 @@ from src.user.schemas import UserIDMixin
from src.organisation.constants import Status, ContactType from src.organisation.constants import Status, ContactType
class Questionnaire(CustomBaseModel): class OrgQuestionnaire(CustomBaseModel):
question_one: str question_one: str
question_two: str question_two: str
question_three: str question_three: str
@ -28,10 +27,10 @@ class OrgIDMixin(CustomBaseModel):
class OrgPostOrgRequest(CustomBaseModel): class OrgPostOrgRequest(CustomBaseModel):
name: str name: str
intake_questionnaire: Optional[Questionnaire] = None intake_questionnaire: Optional[OrgQuestionnaire] = None
class OrgPatchQuestionnaireRequest(OrgIDMixin): class OrgPatchQuestionnaireRequest(OrgIDMixin):
intake_questionnaire: Questionnaire intake_questionnaire: OrgQuestionnaire
partial: bool partial: bool
class OrgPatchStatusRequest(OrgIDMixin): class OrgPatchStatusRequest(OrgIDMixin):

View file

@ -1,3 +1,11 @@
""" """
Reusable business logic functions for the organisation module Module specific business logic for organisation module
Classes:
- List: Description
- Classes: Description
Functions:
- List: Description
- Functions: Description
""" """

View file

@ -1,3 +1,11 @@
""" """
Non-business logic reusable functions and classes for the organisation module Non-business logic reusable functions and classes for organisation module
Classes:
- List: Description
- Classes: Description
Functions:
- List: Description
- Functions: Description
""" """

View file

@ -17,7 +17,7 @@ from src.service.models import Service
from src.service.utils import generate_api_key from src.service.utils import generate_api_key
from src.service.dependencies import service_model_body_dependency from src.service.dependencies import service_model_body_dependency
from src.service.schemas import ServiceGetServiceResponse, ServicePostServiceRequest, ServicePostServiceResponse, \ from src.service.schemas import ServiceGetServiceResponse, ServicePostServiceRequest, ServicePostServiceResponse, \
ServiceWithKeySchema, ServicePatchKeyResponse, ServicePatchKeyRequest, ServiceDeleteServiceRequest ServiceWithKeyResponse, ServicePatchKeyResponse, ServicePatchKeyRequest, ServiceDeleteServiceRequest
router = APIRouter( router = APIRouter(
tags=["Service"], tags=["Service"],
@ -42,7 +42,7 @@ async def register_service(db: db_dependency, su: super_admin_dependency, servic
if isinstance(e.orig, UniqueViolation): if isinstance(e.orig, UniqueViolation):
raise Conflict(message="Service with this name already exists") raise Conflict(message="Service with this name already exists")
db.commit() db.commit()
response = ServiceWithKeySchema(**service_model.__dict__) response = ServiceWithKeyResponse(**service_model.__dict__)
db.commit() db.commit()
return {"service": response} return {"service": response}
@ -52,7 +52,7 @@ async def regenerate_api_key(db: db_dependency, su: super_admin_dependency, serv
service_model.api_key = key service_model.api_key = key
db.flush() db.flush()
response = ServiceWithKeySchema(**service_model.__dict__) response = ServiceWithKeyResponse(**service_model.__dict__)
db.commit() db.commit()
return {"service": response} return {"service": response}

View file

@ -12,29 +12,29 @@ from src.schemas import CustomBaseModel
class ServiceIDMixin(CustomBaseModel): class ServiceIDMixin(CustomBaseModel):
service_id: int service_id: int
class ServiceSchema(CustomBaseModel): class ServiceResponse(CustomBaseModel):
model_config = ConfigDict(from_attributes=True, extra="ignore") model_config = ConfigDict(from_attributes=True, extra="ignore")
id: int id: int
name: str name: str
class ServiceWithKeySchema(ServiceSchema): class ServiceWithKeyResponse(ServiceResponse):
api_key: str api_key: str
class ServiceGetServiceResponse(CustomBaseModel): class ServiceGetServiceResponse(CustomBaseModel):
services: list[ServiceSchema] services: list[ServiceResponse]
class ServicePostServiceRequest(CustomBaseModel): class ServicePostServiceRequest(CustomBaseModel):
name: str name: str
class ServicePostServiceResponse(CustomBaseModel): class ServicePostServiceResponse(CustomBaseModel):
service: ServiceWithKeySchema service: ServiceWithKeyResponse
class ServicePatchKeyRequest(ServiceIDMixin): class ServicePatchKeyRequest(ServiceIDMixin):
pass pass
class ServicePatchKeyResponse(CustomBaseModel): class ServicePatchKeyResponse(CustomBaseModel):
service: ServiceWithKeySchema service: ServiceWithKeyResponse
class ServiceDeleteServiceRequest(ServiceIDMixin): class ServiceDeleteServiceRequest(ServiceIDMixin):
pass pass