feat: contact model restructure
Blank contacts are now generated on org creation and assigned to each contact type. These contacts are linked to the org, only accessible to the org, and removed when the org is removed. With this all contact endpoints have been removed. Contact manipulation is done via the org only.
This commit is contained in:
parent
707482adc2
commit
2b6d923ae1
7 changed files with 146 additions and 165 deletions
|
|
@ -39,6 +39,10 @@ class Organisation(Base):
|
|||
def root_user_email(self):
|
||||
return self.root_user_rel.email if self.root_user_rel else None
|
||||
|
||||
billing_contact_rel = relationship("Contact", foreign_keys=[billing_contact_id])
|
||||
security_contact_rel = relationship("Contact", foreign_keys=[security_contact_id])
|
||||
owner_contact_rel = relationship("Contact", foreign_keys=[owner_contact_id])
|
||||
|
||||
|
||||
class OrgUsers(Base):
|
||||
__tablename__ = "orgusers"
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ from fastapi.params import Path, Query
|
|||
|
||||
from sqlalchemy.sql import exists
|
||||
|
||||
from src.contact.schemas import ContactAddress
|
||||
from src.database import db_dependency
|
||||
from src.contact.models import Contact
|
||||
from src.iam.models import Group
|
||||
|
|
@ -42,9 +43,9 @@ async def get_org_by_id(db: db_dependency, org_model: org_model_dependency, org_
|
|||
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()),
|
||||
"owner_contact": org_model.owner_contact_rel.email,
|
||||
"billing_contact": org_model.billing_contact_rel.email,
|
||||
"security_contact": org_model.security_contact_rel.email,
|
||||
"root_user": org_model.root_user_email
|
||||
}
|
||||
|
||||
|
|
@ -54,11 +55,17 @@ async def get_org_by_id(db: db_dependency, org_model: org_model_dependency, org_
|
|||
@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 = Org(name=org_request.name, intake_questionnaire=org_request.intake_questionnaire)
|
||||
|
||||
org_model.status = "partial" # Status is always set to partial at first, see update_questionnaire() doc
|
||||
|
||||
db.add(org_model)
|
||||
db.flush()
|
||||
for contact_type in ["billing_contact_id", "security_contact_id", "owner_contact_id"]:
|
||||
contact_model = Contact(org_id=org_model.id)
|
||||
db.add(contact_model)
|
||||
db.flush()
|
||||
org_model.__setattr__(contact_type, contact_model.id)
|
||||
db.commit()
|
||||
|
||||
|
||||
|
|
@ -95,26 +102,6 @@ async def update_status(db: db_dependency, status_request: OrgStatusPatchRequest
|
|||
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()
|
||||
|
|
@ -147,29 +134,6 @@ async def delete_organisation_by_id(db: db_dependency, org_id: Annotated[int, Pa
|
|||
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.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
|
||||
|
|
@ -197,3 +161,56 @@ async def remove_user_from_org(db: db_dependency, org_model: org_model_dependenc
|
|||
db.delete(orguser_model)
|
||||
db.commit()
|
||||
pass
|
||||
|
||||
|
||||
@router.get("/{org_id}/contact", response_model=OrgContactGetResponse)
|
||||
async def get_contact(db: db_dependency, org_model: org_model_dependency, contact_type: Annotated[ContactType, Query()], org_id: Annotated[int, Path(gt=0)]):
|
||||
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")
|
||||
|
||||
address = ContactAddress.model_validate(contact_model)
|
||||
|
||||
response = OrgContactGetResponse.model_construct(
|
||||
**contact_model.__dict__,
|
||||
address=address
|
||||
)
|
||||
|
||||
return response
|
||||
|
||||
|
||||
|
||||
@router.patch("/{org_id}/contact")
|
||||
async def update_contact(db: db_dependency, org_model: org_model_dependency, contact_type: Annotated[ContactType, Query()], contact_request: OrgContactPatchRequest, org_id: Annotated[int, Path(gt=0)]):
|
||||
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")
|
||||
|
||||
update_data = contact_request.model_dump(exclude_none=True)
|
||||
for key, value in update_data.items():
|
||||
if hasattr(contact_model, key):
|
||||
setattr(contact_model, key, value)
|
||||
else:
|
||||
raise HTTPException(status_code=422, detail="Invalid keys in update request")
|
||||
db.add(org_model)
|
||||
db.commit()
|
||||
|
|
@ -7,9 +7,11 @@ Models:
|
|||
"""
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import EmailStr, ConfigDict
|
||||
|
||||
from src.schemas import CustomBaseModel
|
||||
from src.organisation.constants import Status, ContactType
|
||||
|
||||
from src.contact.schemas import ContactAddress
|
||||
|
||||
class OrgQuestionnaire(CustomBaseModel):
|
||||
question_one: str
|
||||
|
|
@ -21,10 +23,6 @@ class OrgOrgPostRequest(CustomBaseModel):
|
|||
name: str
|
||||
intake_questionnaire: Optional[OrgQuestionnaire] = None
|
||||
|
||||
billing_contact_id: Optional[int] = None
|
||||
security_contact_id: Optional[int] = None
|
||||
owner_contact_id: Optional[int] = None
|
||||
|
||||
class OrgQuestionnairePatchRequest(CustomBaseModel):
|
||||
intake_questionnaire: OrgQuestionnaire
|
||||
partial: bool
|
||||
|
|
@ -33,8 +31,18 @@ class OrgStatusPatchRequest(CustomBaseModel):
|
|||
status: Status
|
||||
|
||||
class OrgContactPatchRequest(CustomBaseModel):
|
||||
contact_id: int
|
||||
contact_type: ContactType
|
||||
email: Optional[EmailStr] = None
|
||||
first_name: Optional[str] = None
|
||||
last_name: Optional[str] = None
|
||||
phonenumber: Optional[str] = None
|
||||
vat_number: Optional[str] = None
|
||||
post_office_box_number: Optional[str] = None
|
||||
street_address: Optional[str] = None
|
||||
street_address_line_2: Optional[str] = None
|
||||
locality: Optional[str] = None
|
||||
address_region: Optional[str] = None
|
||||
country_code: Optional[str] = None
|
||||
postal_code: Optional[str] = None
|
||||
|
||||
class OrgUserPostRequest(CustomBaseModel):
|
||||
user_id: int
|
||||
|
|
@ -43,16 +51,20 @@ class OrgUserGetResponse(CustomBaseModel):
|
|||
user_id: int
|
||||
|
||||
class OrgContactGetResponse(CustomBaseModel):
|
||||
email: str
|
||||
first_name: str
|
||||
last_name: str
|
||||
phonenumber: str
|
||||
model_config = ConfigDict(from_attributes=True, extra="ignore")
|
||||
|
||||
email: Optional[str] = None
|
||||
first_name: Optional[str] = None
|
||||
last_name: Optional[str] = None
|
||||
phonenumber: Optional[str] = None
|
||||
vat_number: Optional[str] = None
|
||||
|
||||
address: ContactAddress
|
||||
|
||||
class OrgOrgGetResponse(CustomBaseModel):
|
||||
name: str
|
||||
status: Status
|
||||
root_user: Optional[str] = None
|
||||
owner_contact: Optional[OrgContactGetResponse] = None
|
||||
billing_contact: Optional[OrgContactGetResponse] = None
|
||||
security_contact: Optional[OrgContactGetResponse] = None
|
||||
owner_contact: Optional[str] = None
|
||||
billing_contact: Optional[str] = None
|
||||
security_contact: Optional[str] = None
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue