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))