Files
video-gen/video-gen-api/app/services/team_invitation_service.py
T
root 4375e4980c 1、生成邀请码默认过期时间设置一天
2、邀请链接内容在列表没有显示完全,复制也没有生效
3、检查用户通过邀请链接访问是否未注册需要注册登陆,如果有账户直接登陆直接进行申请,如果已经登陆直接弹窗显示是否加入具体团队,避免单用户多次提交申请
4、如果团队负责人有未处理的加入申请,弹窗通知
2026-07-07 10:53:34 +08:00

239 lines
7.8 KiB
Python

from __future__ import annotations
from datetime import datetime, timedelta, timezone
from typing import Any
from fastapi import HTTPException
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
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.utils.id_gen import generate_id
import secrets
def _generate_invite_code() -> str:
"""生成唯一邀请码。"""
return secrets.token_urlsafe(12)
async def _get_team_or_404(db: AsyncSession, team_id: str) -> Team:
result = await db.execute(
select(Team).where(Team.id == team_id, Team.deleted_at.is_(None)).limit(1)
)
team = result.scalar_one_or_none()
if not team:
raise HTTPException(status_code=404, detail="团队不存在")
return team
async def _assert_is_manager(db: AsyncSession, user_id: str, team_id: str) -> None:
team = await _get_team_or_404(db, team_id)
if team.manager_id != user_id:
raise HTTPException(status_code=403, detail="只有团队管理人才能操作")
async def create_invitation(
db: AsyncSession,
team_id: str,
created_by: str,
max_uses: int | None = None,
expires_at: datetime | None = None,
) -> TeamInvitation:
"""创建邀请码(仅团队管理人)。"""
await _assert_is_manager(db, created_by, team_id)
code = _generate_invite_code()
if expires_at is None:
expires_at = datetime.now(timezone.utc) + timedelta(days=1)
invitation = TeamInvitation(
id=generate_id(),
team_id=team_id,
code=code,
created_by=created_by,
status="active",
max_uses=max_uses,
expires_at=expires_at,
)
db.add(invitation)
await db.flush()
await db.refresh(invitation)
return invitation
async def get_invitation_by_code(db: AsyncSession, code: str) -> TeamInvitation | None:
"""根据邀请码获取邀请记录(验证有效)。"""
result = await db.execute(
select(TeamInvitation).where(
TeamInvitation.code == code,
TeamInvitation.deleted_at.is_(None),
).limit(1)
)
invitation = result.scalar_one_or_none()
if not invitation or invitation.status != "active":
return None
if invitation.expires_at and invitation.expires_at < datetime.now(timezone.utc):
return None
if invitation.max_uses and invitation.use_count >= invitation.max_uses:
return None
return invitation
async def revoke_invitation(db: AsyncSession, invitation_id: str, revoked_by: str) -> None:
"""撤销邀请码。"""
result = await db.execute(
select(TeamInvitation).where(
TeamInvitation.id == invitation_id,
TeamInvitation.deleted_at.is_(None),
).limit(1)
)
invitation = result.scalar_one_or_none()
if not invitation:
raise HTTPException(status_code=404, detail="邀请码不存在")
await _assert_is_manager(db, revoked_by, invitation.team_id)
invitation.status = "revoked"
await db.flush()
async def create_join_request(
db: AsyncSession,
user_id: str,
invitation_code: str,
) -> TeamJoinRequest:
"""通过邀请码创建加入申请。"""
invitation = await get_invitation_by_code(db, invitation_code)
if not invitation:
raise HTTPException(status_code=400, detail="邀请码无效或已过期/已用完")
# 验证用户存在
user_result = await db.execute(
select(User).where(User.id == user_id, User.deleted_at.is_(None)).limit(1)
)
user = user_result.scalar_one_or_none()
if not user:
raise HTTPException(status_code=404, detail="用户不存在")
# 检查是否已在该团队
if user.team_id == invitation.team_id:
raise HTTPException(status_code=400, detail="您已在此团队中")
# 检查是否已有待审批的申请
existing = await db.execute(
select(TeamJoinRequest).where(
TeamJoinRequest.user_id == user_id,
TeamJoinRequest.team_id == invitation.team_id,
TeamJoinRequest.status == "pending",
).limit(1)
)
if existing.scalar_one_or_none():
raise HTTPException(status_code=400, detail="您已提交申请,请等待审批")
# 检查是否已有待审批的其他团队申请
existing_any = await db.execute(
select(TeamJoinRequest).where(
TeamJoinRequest.user_id == user_id,
TeamJoinRequest.status == "pending",
).limit(1)
)
if existing_any.scalar_one_or_none():
raise HTTPException(status_code=400, detail="您已有一个待审批的团队申请,请先等待处理")
request = TeamJoinRequest(
id=generate_id(),
team_id=invitation.team_id,
user_id=user_id,
invitation_id=invitation.id,
status="pending",
)
db.add(request)
# 增加邀请码使用次数
invitation.use_count = (invitation.use_count or 0) + 1
if invitation.max_uses and invitation.use_count >= invitation.max_uses:
invitation.status = "revoked"
await db.flush()
return request
async def handle_join_request(
db: AsyncSession,
request_id: str,
manager_id: str,
action: str,
note: str | None = None,
) -> None:
"""审批/拒绝加入申请(仅团队管理人)。"""
result = await db.execute(
select(TeamJoinRequest).where(TeamJoinRequest.id == request_id).limit(1)
)
request = result.scalar_one_or_none()
if not request:
raise HTTPException(status_code=404, detail="申请不存在")
if request.status != "pending":
raise HTTPException(status_code=400, detail="该申请已处理")
await _assert_is_manager(db, manager_id, request.team_id)
if action == "approve":
# 检查用户是否已在其他团队
user_result = await db.execute(
select(User).where(User.id == request.user_id, User.deleted_at.is_(None)).limit(1)
)
user = user_result.scalar_one_or_none()
if not user:
raise HTTPException(status_code=404, detail="用户不存在")
if user.team_id and user.team_id != request.team_id:
raise HTTPException(status_code=400, detail="用户已在其他团队中,无法加入")
user.team_id = request.team_id
request.status = "approved"
elif action == "reject":
request.status = "rejected"
request.note = note
else:
raise HTTPException(status_code=400, detail="无效操作")
request.handled_by = manager_id
await db.flush()
async def get_pending_requests(db: AsyncSession, team_id: str) -> list[dict[str, Any]]:
"""获取团队待审批的申请列表。"""
result = await db.execute(
select(TeamJoinRequest, User.username, User.phone)
.join(User, User.id == TeamJoinRequest.user_id)
.where(
TeamJoinRequest.team_id == team_id,
TeamJoinRequest.status == "pending",
)
.order_by(TeamJoinRequest.created_at.asc())
)
rows = result.all()
return [
{
"id": req.id,
"team_id": req.team_id,
"user_id": req.user_id,
"username": username,
"phone": phone,
"status": req.status,
"note": req.note,
"created_at": req.created_at,
}
for req, username, phone in rows
]
async def get_invitations_for_team(db: AsyncSession, team_id: str) -> list[TeamInvitation]:
"""获取团队的所有邀请码。"""
result = await db.execute(
select(TeamInvitation).where(
TeamInvitation.team_id == team_id,
TeamInvitation.deleted_at.is_(None),
).order_by(TeamInvitation.created_at.desc())
)
return list(result.scalars().all())