from __future__ import annotations from typing import Any, Dict from urllib.parse import urlencode from django.core.paginator import Paginator from django.db.models import Q from django.http import HttpRequest, HttpResponse from django.shortcuts import get_object_or_404, redirect from django.views import View from inertia import render from labs.models import APIKey, User from .base import PermissionRequiredAdminMixin class APIKeyListView(PermissionRequiredAdminMixin, View): """List API keys with search by user email and pagination.""" permission_required = "labs.view_apikey" paginate_by = 20 component = "api-keys/Index" def get_queryset(self): queryset = APIKey.objects.select_related("user").order_by("-created_at") query = self.request.GET.get("q", "").strip() if query: queryset = queryset.filter( Q(user__email__icontains=query) | Q(description__icontains=query) ) return queryset def serialize_api_key(self, api_key: APIKey) -> Dict[str, Any]: return { "id": api_key.id, "display": api_key.api_key_display, "description": api_key.description, "user_id": api_key.user_id, "user_email": api_key.user.email, "created_at": api_key.created_at.isoformat(), } def get(self, request: HttpRequest) -> HttpResponse: queryset = self.get_queryset() paginator = Paginator(queryset, self.paginate_by) page_number = request.GET.get("page", 1) page_obj = paginator.get_page(page_number) api_keys = [self.serialize_api_key(k) for k in page_obj] # Check user status (normalize and case-insensitive lookup) check_email_raw = request.GET.get("check_email", "").strip() check_email = User.objects.normalize_email(check_email_raw) if check_email_raw else "" check_result = None if check_email: user = User.objects.filter(email__iexact=check_email).first() if user is not None: user_keys = APIKey.objects.filter(user=user) check_result = { "email": check_email, "found": True, "has_key": user_keys.exists(), "keys": [self.serialize_api_key(k) for k in user_keys.select_related("user")], } else: check_result = { "email": check_email, "found": False, "has_key": False, "keys": [], } generated_key = request.session.pop("generated_api_key", None) return render( request, self.component, { "api_keys": api_keys, "filters": { "q": request.GET.get("q", "").strip(), "check_email": check_email, }, "pagination": { "page": page_obj.number, "per_page": paginator.per_page, "total": paginator.count, "num_pages": paginator.num_pages, }, "check_result": check_result, "generated_key": generated_key, }, ) class APIKeyGenerateView(PermissionRequiredAdminMixin, View): """Generate a new API key for a user.""" permission_required = ["labs.view_apikey", "labs.add_apikey"] def post(self, request: HttpRequest) -> HttpResponse: email_raw = request.POST.get("email", "").strip() description = request.POST.get("description", "").strip() or "Admin-generated" if not email_raw: request.session["flash_error"] = "Email is required" return redirect("/admin/api-keys/") email = User.objects.normalize_email(email_raw) user = User.objects.filter(email__iexact=email).first() if user is None: request.session["flash_error"] = f"User with email '{email_raw}' not found" return redirect("/admin/api-keys/") raw_key = APIKey.objects.create_api_key(user, description) request.session["flash_success"] = f"API key generated for {email}" request.session["generated_api_key"] = raw_key return redirect(f"/admin/api-keys/?{urlencode({'check_email': email})}") class APIKeyDeleteView(PermissionRequiredAdminMixin, View): """Delete an API key.""" permission_required = ["labs.view_apikey", "labs.delete_apikey"] def post(self, request: HttpRequest, pk: int) -> HttpResponse: api_key = get_object_or_404(APIKey, pk=pk) email = api_key.user.email api_key.delete() request.session["flash_success"] = f"API key for {email} deleted" return redirect("/admin/api-keys/")