100 lines
2.8 KiB
Python
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))
|