feat: integrate governed address contacts
This commit is contained in:
@@ -11,6 +11,10 @@ from sqlalchemy.orm import Session
|
||||
from govoplan_mail.backend.schemas import (
|
||||
MailAddressLookupCandidate,
|
||||
MailAddressLookupResponse,
|
||||
MailAddressWriteTarget,
|
||||
MailAddressWriteTargetResponse,
|
||||
MailContactCreateRequest,
|
||||
MailContactCreateResponse,
|
||||
MailConnectionTestResponse,
|
||||
MailBounceObservationListResponse,
|
||||
MailBounceObservationResponse,
|
||||
@@ -149,6 +153,7 @@ MAIL_CREDENTIAL_RESOURCE = "mail_credential"
|
||||
MAILBOX_MESSAGES_CURSOR_SCOPE = "mail.mailbox.messages.v1"
|
||||
DEFAULT_MAILBOX_MESSAGE_LIMIT = 50
|
||||
CAPABILITY_ADDRESSES_LOOKUP = "addresses.lookup"
|
||||
CAPABILITY_ADDRESSES_CONTACT_WRITER = "addresses.contact_writer"
|
||||
bounce_provider = SqlMailBounceProcessingProvider()
|
||||
|
||||
|
||||
@@ -416,6 +421,15 @@ def _capability_payload(value: object) -> dict[str, Any]:
|
||||
"source_ref",
|
||||
"source_revision",
|
||||
"provenance",
|
||||
"address_book_label",
|
||||
"operation",
|
||||
"allowed",
|
||||
"reason",
|
||||
"message",
|
||||
"scope_type",
|
||||
"scope_id",
|
||||
"read_only",
|
||||
"required_scopes",
|
||||
):
|
||||
if hasattr(value, key):
|
||||
payload[key] = getattr(value, key)
|
||||
@@ -1361,6 +1375,78 @@ def lookup_mail_addresses(
|
||||
)
|
||||
|
||||
|
||||
@router.get("/address-write-targets", response_model=MailAddressWriteTargetResponse)
|
||||
def list_mail_address_write_targets(
|
||||
session: Session = Depends(get_session),
|
||||
principal: ApiPrincipal = Depends(get_api_principal),
|
||||
) -> MailAddressWriteTargetResponse:
|
||||
_require_scope(principal, "mail:profile:use")
|
||||
capability = _registry_capability(CAPABILITY_ADDRESSES_CONTACT_WRITER)
|
||||
if capability is None or not hasattr(capability, "list_write_targets"):
|
||||
return MailAddressWriteTargetResponse(available=False, targets=[])
|
||||
targets = getattr(capability, "list_write_targets")(session, principal, operation="create_contact")
|
||||
return MailAddressWriteTargetResponse(
|
||||
available=True,
|
||||
targets=[MailAddressWriteTarget.model_validate(_capability_payload(target)) for target in targets],
|
||||
)
|
||||
|
||||
|
||||
@router.post(
|
||||
"/address-contacts",
|
||||
response_model=MailContactCreateResponse,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
)
|
||||
def create_mail_address_contact(
|
||||
payload: MailContactCreateRequest,
|
||||
session: Session = Depends(get_session),
|
||||
principal: ApiPrincipal = Depends(get_api_principal),
|
||||
) -> MailContactCreateResponse:
|
||||
_require_scope(principal, "mail:profile:use")
|
||||
capability = _registry_capability(CAPABILITY_ADDRESSES_CONTACT_WRITER)
|
||||
if capability is None or not all(
|
||||
hasattr(capability, method)
|
||||
for method in ("can_write_to_address_book", "create_contact")
|
||||
):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_409_CONFLICT,
|
||||
detail="Address-book contact writing is not available.",
|
||||
)
|
||||
decision = getattr(capability, "can_write_to_address_book")(
|
||||
session,
|
||||
principal,
|
||||
address_book_id=payload.address_book_id,
|
||||
operation="create_contact",
|
||||
)
|
||||
decision_payload = _capability_payload(decision)
|
||||
if decision_payload.get("allowed") is not True:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
|
||||
detail=str(decision_payload.get("message") or "The selected address book is not writable."),
|
||||
)
|
||||
try:
|
||||
result = getattr(capability, "create_contact")(
|
||||
session,
|
||||
principal,
|
||||
address_book_id=payload.address_book_id,
|
||||
payload={
|
||||
"display_name": payload.display_name or payload.email,
|
||||
"emails": [{"label": "Mail", "email": payload.email, "is_primary": True}],
|
||||
},
|
||||
provenance={
|
||||
"consumer_module": MAIL_MODULE_ID,
|
||||
"consumer_workflow": "mailbox_add_contact",
|
||||
},
|
||||
)
|
||||
session.commit()
|
||||
except ValueError as exc:
|
||||
session.rollback()
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
|
||||
detail=str(exc),
|
||||
) from exc
|
||||
return MailContactCreateResponse.model_validate(_capability_payload(result))
|
||||
|
||||
|
||||
@router.get("/settings/delta", response_model=MailSettingsDeltaResponse)
|
||||
def mail_settings_delta(
|
||||
scope_type: str = Query(default="tenant"),
|
||||
|
||||
Reference in New Issue
Block a user