PMFlow/backend/app/utils/project_members.py

100 lines
2.8 KiB
Python

from __future__ import annotations
import re
from sqlalchemy import and_, select
from sqlalchemy.ext.asyncio import AsyncSession
from ..models import User, UserProject
_ROLE_SPLIT_RE = re.compile(r"[,/|\s]+")
_ROLE_ALIAS_MAP = {
"AI": {"AI"},
"AI_ENGINEER": {"AI"},
"FRONTEND": {"FE"},
"FE": {"FE"},
"BACKEND": {"BE"},
"BE": {"BE"},
"BOSS": {"BOSS"},
"ADMIN": {"ADMIN"},
}
def _parse_role_tokens(raw: str | None) -> set[str]:
tokens = {
token.strip().upper()
for token in _ROLE_SPLIT_RE.split(str(raw or "").strip())
if token.strip()
}
normalized: set[str] = set()
for token in tokens:
normalized.update(_ROLE_ALIAS_MAP.get(token, {token}))
return normalized
def _user_matches_project_role(user_role: str | None, expected_roles: set[str]) -> bool:
if not expected_roles:
return False
return bool(_parse_role_tokens(user_role).intersection(expected_roles))
async def ensure_project_default_members(
db: AsyncSession,
*,
tenant_id: int,
project_id: int,
owner_id: int | None,
assigned_role: str | None,
) -> list[int]:
member_ids: set[int] = set()
if owner_id is not None:
member_ids.add(int(owner_id))
expected_roles = _parse_role_tokens(assigned_role)
if expected_roles:
rows = await db.execute(
select(User.id, User.role).where(
and_(
User.tenant_id == int(tenant_id),
User.status == "active",
User.is_active.is_(True),
)
)
)
for user_id, user_role in rows.all():
if _user_matches_project_role(user_role, expected_roles):
member_ids.add(int(user_id))
if not member_ids:
fallback_rows = await db.execute(
select(User.id).where(
and_(
User.tenant_id == int(tenant_id),
User.status == "active",
User.is_active.is_(True),
)
).order_by(User.id.asc()).limit(1)
)
member_ids.update(int(user_id) for user_id in fallback_rows.scalars().all())
existing_rows = await db.execute(
select(UserProject.user_id).where(
and_(
UserProject.tenant_id == int(tenant_id),
UserProject.project_id == int(project_id),
)
)
)
existing_member_ids = {int(user_id) for user_id in existing_rows.scalars().all()}
for user_id in sorted(member_ids - existing_member_ids):
db.add(
UserProject(
tenant_id=int(tenant_id),
user_id=int(user_id),
project_id=int(project_id),
)
)
return sorted(member_ids.union(existing_member_ids))