agentic.email/backend/app/services/mail_groups.py
2026-10-05 04:53:45 +05:30

177 lines
5.1 KiB
Python

"""Distribution group management."""
from __future__ import annotations
from sqlalchemy import func, select
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload
from app.models import Account, MailAlias, MailGroup, MailGroupMember
from app.schemas import MailGroupCreate, MailGroupEmailCheck, MailGroupRead
def _group_read(group: MailGroup) -> MailGroupRead:
return MailGroupRead(
id=group.id,
name=group.name,
email=group.email,
group_type=group.group_type,
owner_id=group.owner_id,
send_permission=group.send_permission,
member_count=len(group.members),
created_at=group.created_at,
)
async def check_group_email_available(
session: AsyncSession,
company_domain: str,
email: str,
) -> MailGroupEmailCheck:
normalized = str(email).lower()
if not normalized.endswith(f"@{company_domain}"):
return MailGroupEmailCheck(
email=normalized,
available=False,
conflict_type="invalid_domain",
)
account = await session.scalar(
select(Account).where(func.lower(Account.email) == normalized)
)
if account:
return MailGroupEmailCheck(
email=normalized,
available=False,
conflict_type="user",
)
group = await session.scalar(
select(MailGroup).where(func.lower(MailGroup.email) == normalized)
)
if group:
return MailGroupEmailCheck(
email=normalized,
available=False,
conflict_type="group",
)
alias = await session.scalar(
select(MailAlias).where(
MailAlias.company_domain == company_domain,
func.lower(MailAlias.alias_address) == normalized,
)
)
if alias:
return MailGroupEmailCheck(
email=normalized,
available=False,
conflict_type="alias",
)
return MailGroupEmailCheck(email=normalized, available=True)
async def list_mail_groups(
session: AsyncSession,
company_domain: str,
) -> tuple[list[MailGroupRead], dict[str, int]]:
result = await session.scalars(
select(MailGroup)
.options(selectinload(MailGroup.members))
.where(MailGroup.company_domain == company_domain)
.order_by(MailGroup.created_at.desc())
)
groups = list(result.all())
items = [_group_read(group) for group in groups]
counts = {
"all": len(items),
"distribution": sum(1 for item in items if item.group_type == "distribution"),
"dynamic": sum(1 for item in items if item.group_type == "dynamic"),
}
return items, counts
async def _validate_member_ids(
session: AsyncSession,
company_domain: str,
member_ids: list,
) -> list[Account]:
if not member_ids:
return []
result = await session.scalars(
select(Account).where(
Account.id.in_(member_ids),
Account.company_domain == company_domain,
Account.member_status != "suspended",
)
)
members = list(result.all())
if len(members) != len(set(member_ids)):
raise ValueError("One or more selected members are invalid.")
return members
async def create_mail_group(
session: AsyncSession,
*,
manager: Account,
payload: MailGroupCreate,
) -> MailGroupRead:
company_domain = manager.company_domain
if company_domain is None:
raise ValueError("Company domain is required.")
email = str(payload.email).lower()
if not email.endswith(f"@{company_domain}"):
raise ValueError(f"Group email must use @{company_domain}.")
existing = await session.scalar(
select(MailGroup).where(func.lower(MailGroup.email) == email)
)
if existing:
raise ValueError("That group email address is already in use.")
owner = await session.scalar(
select(Account).where(
Account.id == payload.owner_id,
Account.company_domain == company_domain,
Account.member_status != "suspended",
)
)
if owner is None:
raise ValueError("Group owner must be an active member of your organization.")
availability = await check_group_email_available(session, company_domain, email)
if not availability.available:
raise ValueError("That group email address is already in use.")
members = await _validate_member_ids(
session,
company_domain,
payload.member_ids,
)
group = MailGroup(
company_domain=company_domain,
name=payload.name.strip(),
email=email,
group_type=payload.group_type,
owner_id=owner.id,
send_permission=payload.send_permission,
description=payload.description.strip() if payload.description else None,
created_by_id=manager.id,
)
session.add(group)
await session.flush()
for member in members:
session.add(
MailGroupMember(
group_id=group.id,
account_id=member.id,
)
)
await session.flush()
await session.refresh(group, attribute_names=["members"])
return _group_read(group)