from typing import Dict, Any, Optional, List from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select, func, and_, or_ from app.models.team import Team, TeamMember from app.models.user import User from datetime import datetime import logging logger = logging.getLogger(__name__) class TeamService: def __init__(self, db: AsyncSession): self.db = db async def create_team(self, owner_id: str, name: str, description: str = None) -> Dict[str, Any]: existing = await self.db.execute( select(Team).where(and_(Team.owner_id == owner_id, Team.is_active == True)) ) if existing.scalar_one_or_none(): raise ValueError("You already own an active team") user_result = await self.db.execute(select(User).where(User.id == owner_id)) user = user_result.scalar_one_or_none() if not user: raise ValueError("User not found") max_members = 20 if user.tier == "enterprise" else (10 if user.tier == "pro" else 5) team = Team( name=name, owner_id=owner_id, description=description, max_members=max_members, tier=user.tier, ) self.db.add(team) await self.db.flush() member = TeamMember( team_id=team.id, user_id=owner_id, role="owner", status="active", ) self.db.add(member) await self.db.flush() return await self._to_dict(team, include_members=True) async def get_team(self, team_id: str, user_id: str) -> Optional[Dict[str, Any]]: result = await self.db.execute( select(Team).where(Team.id == team_id) ) team = result.scalar_one_or_none() if not team: return None is_member = await self.db.execute( select(TeamMember).where( and_(TeamMember.team_id == team_id, TeamMember.user_id == user_id) ) ) if not is_member.scalar_one_or_none(): return None return await self._to_dict(team, include_members=True) async def list_user_teams(self, user_id: str) -> List[Dict[str, Any]]: member_result = await self.db.execute( select(TeamMember.team_id).where(TeamMember.user_id == user_id) ) team_ids = [r[0] for r in member_result.all()] if not team_ids: return [] result = await self.db.execute( select(Team).where(Team.id.in_(team_ids), Team.is_active == True) ) teams = result.scalars().all() return [await self._to_dict(t) for t in teams] async def invite_member(self, team_id: str, owner_id: str, user_id: str) -> Dict[str, Any]: team_result = await self.db.execute( select(Team).where(and_(Team.id == team_id, Team.owner_id == owner_id)) ) team = team_result.scalar_one_or_none() if not team: raise ValueError("Team not found or not authorized") member_count = await self.db.execute( select(func.count(TeamMember.id)).where( and_(TeamMember.team_id == team_id, TeamMember.status == "active") ) ) if (member_count.scalar() or 0) >= team.max_members: raise ValueError("Team member limit reached") existing = await self.db.execute( select(TeamMember).where( and_(TeamMember.team_id == team_id, TeamMember.user_id == user_id) ) ) if existing.scalar_one_or_none(): raise ValueError("User is already a member") member = TeamMember( team_id=team_id, user_id=user_id, role="member", invited_by=owner_id, status="active", ) self.db.add(member) await self.db.flush() return {"user_id": user_id, "role": "member", "status": "active"} async def remove_member(self, team_id: str, owner_id: str, user_id: str) -> bool: team_result = await self.db.execute( select(Team).where(and_(Team.id == team_id, Team.owner_id == owner_id)) ) team = team_result.scalar_one_or_none() if not team: return False result = await self.db.execute( select(TeamMember).where( and_(TeamMember.team_id == team_id, TeamMember.user_id == user_id) ) ) member = result.scalar_one_or_none() if not member or member.role == "owner": return False await self.db.delete(member) return True async def leave_team(self, team_id: str, user_id: str) -> bool: result = await self.db.execute( select(TeamMember).where( and_(TeamMember.team_id == team_id, TeamMember.user_id == user_id) ) ) member = result.scalar_one_or_none() if not member or member.role == "owner": return False await self.db.delete(member) return True async def update_role(self, team_id: str, owner_id: str, user_id: str, role: str) -> bool: team_result = await self.db.execute( select(Team).where(and_(Team.id == team_id, Team.owner_id == owner_id)) ) if not team_result.scalar_one_or_none(): return False result = await self.db.execute( select(TeamMember).where( and_(TeamMember.team_id == team_id, TeamMember.user_id == user_id) ) ) member = result.scalar_one_or_none() if not member or member.role == "owner": return False member.role = role await self.db.flush() return True async def _to_dict(self, team: Team, include_members: bool = False) -> Dict[str, Any]: result = { "id": str(team.id), "name": team.name, "owner_id": str(team.owner_id), "description": team.description, "tier": team.tier, "is_active": team.is_active, "created_at": team.created_at.isoformat() if team.created_at else None, } if include_members: members_result = await self.db.execute( select(TeamMember).where(TeamMember.team_id == team.id) ) members = members_result.scalars().all() result["members"] = [ { "user_id": str(m.user_id), "role": m.role, "status": m.status, "joined_at": m.joined_at.isoformat() if m.joined_at else None, } for m in members ] result["member_count"] = len([m for m in members if m.status == "active"]) return result