"""平台超管后台 · 跨团队端点。所有视图统一挂 IsPlatformAdmin,非超管一律 403,写操作记审计。""" from django.db.models import Q from rest_framework import status from rest_framework.decorators import api_view, permission_classes from rest_framework.response import Response from apps.accounts.audit import log_admin_action from apps.accounts.models import Invitation from apps.accounts.permissions import IsPlatformAdmin from apps.accounts.serializers import InvitationSerializer from apps.common.pagination import DefaultPagination @api_view(["GET", "POST"]) @permission_classes([IsPlatformAdmin]) def admin_invitations(request): """GET 列所有邀请码(跨团队,可按 kind/status/search 过滤,分页); POST 平台超管发「开团队码」(create_team,不绑团队,新用户凭码开新团队当 owner)。""" if request.method == "GET": qs = Invitation.objects.select_related("team", "used_by").order_by("-created_at") kind = request.query_params.get("kind") if kind in {Invitation.Kind.JOIN_TEAM, Invitation.Kind.CREATE_TEAM}: qs = qs.filter(kind=kind) st = request.query_params.get("status") if st in dict(Invitation.Status.choices): qs = qs.filter(status=st) search = (request.query_params.get("search") or "").strip() if search: qs = qs.filter(Q(code__icontains=search) | Q(team__name__icontains=search)) paginator = DefaultPagination() page = paginator.paginate_queryset(qs, request) return paginator.get_paginated_response(InvitationSerializer(page, many=True).data) email = str(request.data.get("email") or "").strip() invite = Invitation.objects.create( kind=Invitation.Kind.CREATE_TEAM, team=None, email=email, created_by=request.user, ) log_admin_action( request, "invite.issue_create_team", target_type="invitation", target_id=invite.id, target_name=invite.code, after={"kind": invite.kind, "email": email}, ) return Response(InvitationSerializer(invite).data, status=status.HTTP_201_CREATED) @api_view(["POST"]) @permission_classes([IsPlatformAdmin]) def admin_revoke_invitation(request, invite_id): """撤销一个待用邀请码(任意团队)。已用/已撤销/已过期则原样返回(幂等)。""" invite = Invitation.objects.select_related("team").filter(id=invite_id).first() if invite is None: return Response({"detail": "not found"}, status=status.HTTP_404_NOT_FOUND) if invite.status == Invitation.Status.PENDING: invite.status = Invitation.Status.REVOKED invite.save(update_fields=["status", "updated_at"]) log_admin_action( request, "invite.revoke", target_type="invitation", target_id=invite.id, target_name=invite.code, ) return Response(InvitationSerializer(invite).data)