207 lines
6.5 KiB
Python
207 lines
6.5 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
|
|
from fastapi import APIRouter, Body, Depends, Query
|
|
from sqlalchemy import func, select
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from app.dependencies import get_admin_user, get_db
|
|
from app.enums.team import TEAM_STATUS_LABELS, TeamStatus
|
|
from app.enums.user import UserType
|
|
from app.models.team import Team
|
|
from app.models.user import User
|
|
from app.schemas.team import TeamCreate, TeamListOut, TeamOptionOut, TeamUpdate
|
|
from app.schemas.team_manager import SetManagerRequest
|
|
from app.services.credit.team_subscription_service import (
|
|
list_member_period_usage,
|
|
list_team_subscriptions_for_management,
|
|
)
|
|
from app.services.operation_log import log_operation
|
|
from app.services.team_manager_service import get_manager_history, set_team_manager
|
|
from app.services.team_service import create_team, list_team_options, list_teams, soft_delete_team, update_team
|
|
|
|
router = APIRouter(prefix="/admin/teams", tags=["admin-teams"])
|
|
|
|
|
|
async def _team_detail_payload(db: AsyncSession, team: Team) -> dict:
|
|
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
|
|
manager_name = None
|
|
if team.manager_id:
|
|
manager_name = (await db.execute(
|
|
select(User.username).where(User.id == team.manager_id).limit(1)
|
|
)).scalar_one_or_none()
|
|
status = team.status or TeamStatus.ACTIVE.value
|
|
return {
|
|
"id": team.id,
|
|
"name": team.name,
|
|
"code": team.code,
|
|
"description": team.description,
|
|
"status": status,
|
|
"status_label": TEAM_STATUS_LABELS.get(status, "其他状态"),
|
|
"is_read_only": status == TeamStatus.DISABLED.value,
|
|
"team_credit_frozen": status == TeamStatus.DISABLED.value,
|
|
"sort_order": team.sort_order or 0,
|
|
"member_count": int(member_count),
|
|
"created_at": team.created_at,
|
|
"updated_at": team.updated_at,
|
|
"manager_id": team.manager_id,
|
|
"manager_name": manager_name,
|
|
"first_subscription_paid_at": team.first_subscription_paid_at,
|
|
}
|
|
|
|
|
|
@router.get("", response_model=TeamListOut)
|
|
async def list_admin_teams(
|
|
page: int = Query(1, ge=1),
|
|
page_size: int = Query(20, ge=1, le=500),
|
|
keyword: str | None = Query(None),
|
|
status: str | None = Query(None, pattern="^(active|disabled)$"),
|
|
admin: User = Depends(get_admin_user),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
del admin
|
|
return await list_teams(db, page=page, page_size=page_size, keyword=keyword, status=status)
|
|
|
|
|
|
@router.get("/options", response_model=list[TeamOptionOut])
|
|
async def list_admin_team_options(
|
|
include_disabled: bool = Query(True),
|
|
admin: User = Depends(get_admin_user),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
del admin
|
|
return await list_team_options(db, include_disabled=include_disabled)
|
|
|
|
|
|
@router.post("")
|
|
async def create_admin_team(
|
|
req: TeamCreate,
|
|
admin: User = Depends(get_admin_user),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
team = await create_team(db, req)
|
|
await log_operation(
|
|
db,
|
|
admin.id,
|
|
admin.username,
|
|
f"创建团队 {team.name}",
|
|
"POST",
|
|
"/admin/teams",
|
|
detail=json.dumps({"after": req.model_dump()}, ensure_ascii=False),
|
|
)
|
|
return await _team_detail_payload(db, team)
|
|
|
|
|
|
@router.get("/{team_id}")
|
|
async def get_admin_team(
|
|
team_id: str,
|
|
admin: User = Depends(get_admin_user),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
del admin
|
|
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:
|
|
from fastapi import HTTPException
|
|
raise HTTPException(status_code=404, detail="团队不存在")
|
|
return await _team_detail_payload(db, team)
|
|
|
|
|
|
@router.put("/{team_id}")
|
|
async def update_admin_team(
|
|
team_id: str,
|
|
req: TeamUpdate,
|
|
admin: User = Depends(get_admin_user),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
team, before, after = await update_team(db, team_id, req)
|
|
await log_operation(
|
|
db,
|
|
admin.id,
|
|
admin.username,
|
|
f"修改团队 {team.name}",
|
|
"PUT",
|
|
f"/admin/teams/{team_id}",
|
|
detail=json.dumps({"before": before, "after": after}, ensure_ascii=False),
|
|
)
|
|
return await _team_detail_payload(db, team)
|
|
|
|
|
|
@router.put("/{team_id}/manager")
|
|
async def set_team_manager_endpoint(
|
|
team_id: str,
|
|
req: SetManagerRequest = Body(...),
|
|
admin: User = Depends(get_admin_user),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
team = await set_team_manager(db, team_id, req.user_id)
|
|
manager_name = (await db.execute(
|
|
select(User.username).where(User.id == req.user_id).limit(1)
|
|
)).scalar_one_or_none()
|
|
await log_operation(
|
|
db,
|
|
admin.id,
|
|
admin.username,
|
|
f"更换团队队长 {team.name}: {manager_name or req.user_id}",
|
|
"PUT",
|
|
f"/admin/teams/{team_id}/manager",
|
|
detail=json.dumps({"manager_id": req.user_id}, ensure_ascii=False),
|
|
)
|
|
return await _team_detail_payload(db, team)
|
|
|
|
|
|
@router.get("/{team_id}/subscriptions")
|
|
async def list_admin_team_subscriptions(
|
|
team_id: str,
|
|
admin: User = Depends(get_admin_user),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
del admin
|
|
return await list_team_subscriptions_for_management(db, team_id=team_id)
|
|
|
|
|
|
@router.get("/{team_id}/member-usage")
|
|
async def list_admin_team_member_usage(
|
|
team_id: str,
|
|
subscription_id: str | None = Query(None),
|
|
admin: User = Depends(get_admin_user),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
del admin
|
|
return await list_member_period_usage(db, team_id=team_id, subscription_id=subscription_id)
|
|
|
|
|
|
@router.get("/{team_id}/manager-history")
|
|
async def list_admin_team_manager_history(
|
|
team_id: str,
|
|
admin: User = Depends(get_admin_user),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
del admin
|
|
return await get_manager_history(db, team_id)
|
|
|
|
|
|
@router.delete("/{team_id}")
|
|
async def delete_admin_team(
|
|
team_id: str,
|
|
admin: User = Depends(get_admin_user),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
team, before = await soft_delete_team(db, team_id)
|
|
await log_operation(
|
|
db,
|
|
admin.id,
|
|
admin.username,
|
|
f"删除团队 {team.name}",
|
|
"DELETE",
|
|
f"/admin/teams/{team_id}",
|
|
detail=json.dumps({"before": before, "after": {"deleted_at": str(team.deleted_at)}}, ensure_ascii=False),
|
|
)
|
|
return {"message": "团队已删除"}
|