mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-30 03:07:00 -05:00
feat: Implement MFA authentication, add ride statistics model, and update various services, APIs, and tests across the application.
This commit is contained in:
@@ -8,14 +8,11 @@ import json
|
||||
import logging
|
||||
import queue
|
||||
import threading
|
||||
import time
|
||||
from typing import Generator
|
||||
from collections.abc import Generator
|
||||
|
||||
from django.http import StreamingHttpResponse, JsonResponse
|
||||
from django.views import View
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from rest_framework.views import APIView
|
||||
from django.http import JsonResponse, StreamingHttpResponse
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.views import APIView
|
||||
|
||||
from apps.moderation.permissions import CanViewModerationData
|
||||
from apps.moderation.signals import submission_status_changed
|
||||
@@ -27,15 +24,15 @@ logger = logging.getLogger(__name__)
|
||||
class SSEBroadcaster:
|
||||
"""
|
||||
Manages SSE connections and broadcasts events to all clients.
|
||||
|
||||
|
||||
Uses a simple subscriber pattern where each connected client
|
||||
gets its own queue of events to consume.
|
||||
"""
|
||||
|
||||
|
||||
def __init__(self):
|
||||
self._subscribers: list[queue.Queue] = []
|
||||
self._lock = threading.Lock()
|
||||
|
||||
|
||||
def subscribe(self) -> queue.Queue:
|
||||
"""Create a new subscriber queue and register it."""
|
||||
client_queue = queue.Queue()
|
||||
@@ -43,14 +40,14 @@ class SSEBroadcaster:
|
||||
self._subscribers.append(client_queue)
|
||||
logger.debug(f"SSE client subscribed. Total clients: {len(self._subscribers)}")
|
||||
return client_queue
|
||||
|
||||
|
||||
def unsubscribe(self, client_queue: queue.Queue):
|
||||
"""Remove a subscriber queue."""
|
||||
with self._lock:
|
||||
if client_queue in self._subscribers:
|
||||
self._subscribers.remove(client_queue)
|
||||
logger.debug(f"SSE client unsubscribed. Total clients: {len(self._subscribers)}")
|
||||
|
||||
|
||||
def broadcast(self, event_data: dict):
|
||||
"""Send an event to all connected clients."""
|
||||
with self._lock:
|
||||
@@ -68,7 +65,7 @@ sse_broadcaster = SSEBroadcaster()
|
||||
def handle_submission_status_changed(sender, payload, **kwargs):
|
||||
"""
|
||||
Signal handler that broadcasts submission status changes to SSE clients.
|
||||
|
||||
|
||||
Connected to the submission_status_changed signal from signals.py.
|
||||
"""
|
||||
sse_broadcaster.broadcast(payload)
|
||||
@@ -82,14 +79,14 @@ submission_status_changed.connect(handle_submission_status_changed)
|
||||
class ModerationSSEView(APIView):
|
||||
"""
|
||||
Server-Sent Events endpoint for real-time moderation updates.
|
||||
|
||||
|
||||
Provides a streaming response that sends submission status changes
|
||||
as they occur. Clients should connect to this endpoint and keep
|
||||
the connection open to receive real-time updates.
|
||||
|
||||
|
||||
Response format (SSE):
|
||||
data: {"submission_id": 1, "new_status": "CLAIMED", ...}
|
||||
|
||||
|
||||
Usage:
|
||||
const eventSource = new EventSource('/api/moderation/sse/')
|
||||
eventSource.onmessage = (event) => {
|
||||
@@ -97,22 +94,22 @@ class ModerationSSEView(APIView):
|
||||
// Handle update
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
permission_classes = [IsAuthenticated, CanViewModerationData]
|
||||
|
||||
|
||||
def get(self, request):
|
||||
"""
|
||||
Establish SSE connection and stream events.
|
||||
|
||||
|
||||
Sends a heartbeat every 30 seconds to keep the connection alive.
|
||||
"""
|
||||
def event_stream() -> Generator[str, None, None]:
|
||||
def event_stream() -> Generator[str]:
|
||||
client_queue = sse_broadcaster.subscribe()
|
||||
|
||||
|
||||
try:
|
||||
# Send initial connection event
|
||||
yield f"data: {json.dumps({'type': 'connected', 'message': 'SSE connection established'})}\n\n"
|
||||
|
||||
|
||||
while True:
|
||||
try:
|
||||
# Wait for event with timeout for heartbeat
|
||||
@@ -120,13 +117,13 @@ class ModerationSSEView(APIView):
|
||||
yield f"data: {json.dumps(event)}\n\n"
|
||||
except queue.Empty:
|
||||
# Send heartbeat to keep connection alive
|
||||
yield f": heartbeat\n\n"
|
||||
yield ": heartbeat\n\n"
|
||||
except GeneratorExit:
|
||||
# Client disconnected
|
||||
sse_broadcaster.unsubscribe(client_queue)
|
||||
finally:
|
||||
sse_broadcaster.unsubscribe(client_queue)
|
||||
|
||||
|
||||
response = StreamingHttpResponse(
|
||||
event_stream(),
|
||||
content_type='text/event-stream'
|
||||
@@ -134,17 +131,17 @@ class ModerationSSEView(APIView):
|
||||
response['Cache-Control'] = 'no-cache'
|
||||
response['X-Accel-Buffering'] = 'no' # Disable nginx buffering
|
||||
response['Connection'] = 'keep-alive'
|
||||
|
||||
|
||||
return response
|
||||
|
||||
|
||||
class ModerationSSETestView(APIView):
|
||||
"""
|
||||
Test endpoint to manually trigger an SSE event.
|
||||
|
||||
|
||||
This is useful for testing the SSE connection without making
|
||||
actual state transitions.
|
||||
|
||||
|
||||
POST /api/moderation/sse/test/
|
||||
{
|
||||
"submission_id": 1,
|
||||
@@ -153,9 +150,9 @@ class ModerationSSETestView(APIView):
|
||||
"previous_status": "PENDING"
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
permission_classes = [IsAuthenticated, CanViewModerationData]
|
||||
|
||||
|
||||
def post(self, request):
|
||||
"""Broadcast a test event."""
|
||||
test_payload = {
|
||||
@@ -168,9 +165,9 @@ class ModerationSSETestView(APIView):
|
||||
"changed_by": request.user.username,
|
||||
"test": True,
|
||||
}
|
||||
|
||||
|
||||
sse_broadcaster.broadcast(test_payload)
|
||||
|
||||
|
||||
return JsonResponse({
|
||||
"status": "ok",
|
||||
"message": f"Test event broadcast to {len(sse_broadcaster._subscribers)} clients",
|
||||
|
||||
Reference in New Issue
Block a user