1、生成邀请码默认过期时间设置一天

2、邀请链接内容在列表没有显示完全,复制也没有生效
3、检查用户通过邀请链接访问是否未注册需要注册登陆,如果有账户直接登陆直接进行申请,如果已经登陆直接弹窗显示是否加入具体团队,避免单用户多次提交申请
4、如果团队负责人有未处理的加入申请,弹窗通知
This commit is contained in:
2026-07-07 10:53:34 +08:00
parent ab995ce3cf
commit 4375e4980c
13 changed files with 502 additions and 218 deletions
+39 -3
View File
@@ -8,7 +8,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
from starlette.responses import StreamingResponse
from app.config import settings
from app.dependencies import get_current_user, get_db
from app.dependencies import get_current_user, get_db, get_optional_current_user
from app.models.team import Team
from app.models.team_invitation import TeamInvitation
from app.models.team_join_request import TeamJoinRequest
@@ -189,7 +189,7 @@ async def join_by_code(
@router.get("/join-info", )
async def get_join_info(
code: str = Query(...),
current_user: User = Depends(get_current_user),
current_user: User | None = Depends(get_optional_current_user),
db: AsyncSession = Depends(get_db),
):
"""验证邀请码并返回团队信息(用于加入页面展示)。"""
@@ -202,16 +202,52 @@ async def get_join_info(
)
team_name = team.scalar_one_or_none() or ""
already_in_team = current_user.team_id == invitation.team_id
already_in_team = current_user and current_user.team_id == invitation.team_id
has_pending_request = False
if current_user:
from app.models.team_join_request import TeamJoinRequest
pending = await db.execute(
select(TeamJoinRequest).where(
TeamJoinRequest.user_id == current_user.id,
TeamJoinRequest.team_id == invitation.team_id,
TeamJoinRequest.status == "pending",
).limit(1)
)
has_pending = pending.scalar_one_or_none()
has_pending_request = has_pending is not None
return JoinTeamInfoOut(
team_name=team_name,
team_id=invitation.team_id,
valid=True,
already_in_team=already_in_team,
has_pending_request=has_pending_request,
)
@router.get("/join-info/public", )
async def get_join_info_public(
code: str = Query(...),
db: AsyncSession = Depends(get_db),
):
"""公开接口:验证邀请码并返回团队信息(无需登录)。"""
invitation = await team_invitation_service.get_invitation_by_code(db, code)
if not invitation:
return {"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 ""
return {
"team_name": team_name,
"team_id": invitation.team_id,
"valid": True,
}
@router.get("/join-requests", )
async def list_join_requests(
current_user: User = Depends(get_current_user),
+25
View File
@@ -70,6 +70,31 @@ async def get_current_user(
return current_user
async def get_optional_current_user(
credentials: HTTPAuthorizationCredentials | None = Depends(security),
db: AsyncSession = Depends(get_db),
) -> User | None:
if not credentials:
return None
user_id = decode_access_token(credentials.credentials)
if not user_id:
return None
if user_id.startswith("captcha:"):
return None
result = await db.execute(select(User).where(User.id == user_id).limit(1))
user = result.scalar_one_or_none()
if not user or not user.is_active:
return None
if user_must_set_password(user):
return None
return user
async def get_admin_user(
current_user: User = Depends(get_current_user_allow_password_pending),
) -> User:
@@ -31,3 +31,4 @@ class JoinTeamInfoOut(BaseModel):
team_id: str
valid: bool
already_in_team: bool = False
has_pending_request: bool = False
@@ -1,6 +1,6 @@
from __future__ import annotations
from datetime import datetime, timezone
from datetime import datetime, timedelta, timezone
from typing import Any
from fastapi import HTTPException
@@ -46,6 +46,8 @@ async def create_invitation(
"""创建邀请码(仅团队管理人)。"""
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,