109 lines
3 KiB
Python
109 lines
3 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, MailGroup, MailGroupMember
|
|
from app.schemas import MailGroupCreate, MailGroupRead
|
|
|
|
|
|
def _group_read(group: MailGroup) -> MailGroupRead:
|
|
return MailGroupRead(
|
|
id=group.id,
|
|
name=group.name,
|
|
email=group.email,
|
|
group_type=group.group_type,
|
|
member_count=len(group.members),
|
|
created_at=group.created_at,
|
|
)
|
|
|
|
|
|
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.")
|
|
|
|
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,
|
|
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)
|