from __future__ import annotations from datetime import datetime from fastapi import APIRouter, Depends, HTTPException, Query from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from app.config import settings from app.dependencies import get_current_user, get_db from app.models.team import Team from app.models.team_invitation import TeamInvitation from app.models.team_join_request import TeamJoinRequest from app.models.user import User from app.schemas.team_invitation import TeamInvitationCreate, TeamInvitationOut from app.schemas.team_join_request import ( JoinByCodeRequest, JoinRequestHandle, JoinRequestOut, JoinTeamInfoOut, ) from app.schemas.team_manager import ( ManagedTeamOut, ManagerTransferRequest, SetManagerRequest, TeamMemberOut, ) from app.services import team_invitation_service from app.services.team_manager_service import ( get_managed_team, get_team_members, is_team_manager, transfer_credits_to_member, ) router = APIRouter(prefix="/team", tags=["team"]) def _build_invite_link(code: str) -> str: base = getattr(settings, "FRONTEND_URL", "") or getattr(settings, "BASE_URL", "") return f"{base}/join-team?code={code}" # ── 获取当前用户管理的团队 ────────────────────────────── @router.get("/managed", response_model=ManagedTeamOut) async def get_managed_team_info( current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): team = await get_managed_team(db, current_user.id) if not team: raise HTTPException(status_code=404, detail="您不是任何团队的管理人") from sqlalchemy import func from app.enums.user import UserType member_count = (await db.execute( select(func.count(User.id)).where( User.user_type == UserType.FRONTEND.value, User.team_id == team.id, ) )).scalar() or 0 return { "id": team.id, "name": team.name, "code": team.code, "description": team.description, "status": team.status, "member_count": int(member_count), "manager_id": team.manager_id, "manager_name": current_user.username, } # ── 团队成员列表 ────────────────────────────────────── @router.get("/members") async def list_team_members( page: int = Query(1, ge=1), page_size: int = Query(20, ge=1, le=100), current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): team = await get_managed_team(db, current_user.id) if not team: raise HTTPException(status_code=403, detail="只有团队管理人可查看") return await get_team_members(db, team.id, page=page, page_size=page_size) # ── 转账积分给成员 ──────────────────────────────────── @router.post("/members/{member_id}/credits") async def transfer_credits( member_id: str, req: ManagerTransferRequest, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): await transfer_credits_to_member( db, current_user.id, req.target_user_id, req.amount, req.description, ) return {"message": "ok"} # ── 邀请码管理 ──────────────────────────────────────── @router.post("/invitations", response_model=TeamInvitationOut) async def create_invitation( req: TeamInvitationCreate, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): team = await get_managed_team(db, current_user.id) if not team: raise HTTPException(status_code=403, detail="只有团队管理人可创建邀请码") expires_at = None if req.expires_at: try: expires_at = datetime.fromisoformat(req.expires_at) except ValueError: raise HTTPException(status_code=400, detail="过期时间格式错误") invitation = await team_invitation_service.create_invitation( db, team.id, current_user.id, req.max_uses, expires_at ) return { "id": invitation.id, "team_id": invitation.team_id, "code": invitation.code, "status": invitation.status, "max_uses": invitation.max_uses, "use_count": invitation.use_count, "expires_at": invitation.expires_at, "invite_link": _build_invite_link(invitation.code), "created_at": invitation.created_at, } @router.get("/invitations", response_model=list[TeamInvitationOut]) async def list_invitations( current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): team = await get_managed_team(db, current_user.id) if not team: raise HTTPException(status_code=403, detail="只有团队管理人可查看") invitations = await team_invitation_service.get_invitations_for_team(db, team.id) return [ { "id": inv.id, "team_id": inv.team_id, "code": inv.code, "status": inv.status, "max_uses": inv.max_uses, "use_count": inv.use_count, "expires_at": inv.expires_at, "invite_link": _build_invite_link(inv.code), "created_at": inv.created_at, } for inv in invitations ] @router.delete("/invitations/{invitation_id}") async def revoke_invitation( invitation_id: str, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): await team_invitation_service.revoke_invitation(db, invitation_id, current_user.id) return {"message": "ok"} # ── 加入申请 ────────────────────────────────────────── @router.post("/join") async def join_by_code( req: JoinByCodeRequest, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): await team_invitation_service.create_join_request(db, current_user.id, req.invitation_code) return {"message": "申请已提交,请等待团队管理人审批"} @router.get("/join-info", response_model=JoinTeamInfoOut) async def get_join_info( code: str = Query(...), current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): """验证邀请码并返回团队信息(用于加入页面展示)。""" invitation = await team_invitation_service.get_invitation_by_code(db, code) if not invitation: return JoinTeamInfoOut(team_name="", team_id="", valid=False) team = await db.execute( select(Team.name).where(Team.id == invitation.team_id, Team.deleted_at.is_(None)).limit(1) ) team_name = team.scalar_one_or_none() or "" already_in_team = current_user.team_id == invitation.team_id return JoinTeamInfoOut( team_name=team_name, team_id=invitation.team_id, valid=True, already_in_team=already_in_team, ) @router.get("/join-requests", response_model=list[JoinRequestOut]) async def list_join_requests( current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): team = await get_managed_team(db, current_user.id) if not team: raise HTTPException(status_code=403, detail="只有团队管理人可查看") requests = await team_invitation_service.get_pending_requests(db, team.id) # 获取团队名 team_name_result = await db.execute( select(Team.name).where(Team.id == team.id).limit(1) ) team_name = team_name_result.scalar_one_or_none() or "" return [ JoinRequestOut( id=r["id"], team_id=r["team_id"], team_name=team_name, user_id=r["user_id"], username=r["username"], phone=r.get("phone"), status=r["status"], note=r.get("note"), created_at=r.get("created_at"), ) for r in requests ] @router.post("/join-requests/{request_id}") async def handle_join_request( request_id: str, req: JoinRequestHandle, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): await team_invitation_service.handle_join_request( db, request_id, current_user.id, req.action, req.note ) return {"message": "ok"}