from django.db import connection from rest_framework import generics, status from rest_framework.exceptions import NotFound, ValidationError from rest_framework.pagination import CursorPagination, LimitOffsetPagination from rest_framework.response import Response from rest_framework.views import APIView from jobs.models import Job, JobEvent from jobs.serializers import JobCreateSerializer, JobEventSerializer, JobSerializer from jobs.services import create_job, job_stats, retry_failed_job class JobEventCursorPagination(CursorPagination): page_size = 50 page_size_query_param = "limit" max_page_size = 200 ordering = "-id" class JobLimitOffsetPagination(LimitOffsetPagination): default_limit = 25 max_limit = 100 class HealthAPIView(APIView): def get(self, request): try: with connection.cursor() as cursor: cursor.execute("SELECT 1") cursor.fetchone() except Exception as exc: return Response( {"ok": False, "database": "error", "detail": str(exc)}, status=status.HTTP_503_SERVICE_UNAVAILABLE, ) return Response({"ok": True, "database": "ok"}) class JobListCreateAPIView(APIView): def get(self, request): queryset = Job.objects.order_by("-created_at") status_filter = request.query_params.get("status") type_filter = request.query_params.get("type") if status_filter: queryset = queryset.filter(status=status_filter) if type_filter: queryset = queryset.filter(type__icontains=type_filter) paginator = JobLimitOffsetPagination() page = paginator.paginate_queryset(queryset, request, view=self) return paginator.get_paginated_response(JobSerializer(page, many=True).data) def post(self, request): serializer = JobCreateSerializer(data=request.data) serializer.is_valid(raise_exception=True) result = create_job( job_type=serializer.validated_data["type"], payload=serializer.validated_data.get("payload") or {}, priority=serializer.validated_data.get("priority", 50), available_at=serializer.validated_data.get("available_at"), max_attempts=serializer.validated_data.get("max_attempts"), idempotency_key=serializer.validated_data.get("idempotency_key"), ) return Response( JobSerializer(result.job).data, status=status.HTTP_201_CREATED if result.created else status.HTTP_200_OK, ) class JobDetailAPIView(generics.RetrieveAPIView): queryset = Job.objects.all() serializer_class = JobSerializer class JobEventsAPIView(APIView): def get(self, request, pk): if not Job.objects.filter(id=pk).exists(): raise NotFound("Job not found.") events = JobEvent.objects.filter(job_id=pk).order_by("id") return Response(JobEventSerializer(events, many=True).data) class GlobalJobEventsAPIView(APIView): def get(self, request): queryset = JobEvent.objects.order_by("-id") event_type = request.query_params.get("type") job_id = request.query_params.get("job_id") if event_type: queryset = queryset.filter(type=event_type) if job_id: queryset = queryset.filter(job_id=job_id) paginator = JobEventCursorPagination() page = paginator.paginate_queryset(queryset, request, view=self) return paginator.get_paginated_response(JobEventSerializer(page, many=True).data) class JobStatsAPIView(APIView): def get(self, request): return Response(job_stats()) class RetryJobAPIView(APIView): def post(self, request, pk): try: job = retry_failed_job(pk) except Job.DoesNotExist as exc: raise NotFound("Job not found.") from exc except ValueError as exc: raise ValidationError({"detail": str(exc)}) from exc return Response(JobSerializer(job).data)