from django.shortcuts import get_object_or_404 from drf_spectacular.utils import extend_schema from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from rest_framework.throttling import ScopedRateThrottle from rest_framework.views import APIView from .models import Notification from .serializers import ( MarkAllReadResponseSerializer, MarkReadResponseSerializer, NotificationListResponseSerializer, ) # سقفِ تعدادِ اعلانِ برگشتی؛ جلوی رشدِ نامحدودِ پاسخ برای کاربرِ قدیمی رو # می‌گیره. صفحه‌بندیِ واقعی (cursor) وقتی فرانت infinite-scroll اضافه کنه. NOTIFICATIONS_MAX = 100 class NotificationListView(APIView): """GET /api/notifications — فهرست اعلان‌های کاربر (جدیدترین اول)""" permission_classes = [IsAuthenticated] throttle_scope = 'read' throttle_classes = [ScopedRateThrottle] @extend_schema(responses=NotificationListResponseSerializer, tags=['Notifications (notifications)']) def get(self, request): qs = Notification.objects.filter(user_id=request.user.id) # Meta.ordering: جدیدترین اول unread_count = qs.filter(is_read=False).count() data = {'items': list(qs[:NOTIFICATIONS_MAX]), 'unreadCount': unread_count} return Response(NotificationListResponseSerializer(data).data) class NotificationMarkReadView(APIView): """PATCH /api/notifications/{id}/read — علامت‌گذاری یک اعلان به‌عنوان خوانده‌شده""" permission_classes = [IsAuthenticated] # همان سقفِ read؛ «همه را خوانده کن» برای همین جدا ساخته شد تا ۷۵ اعلان ۷۵ درخواست نشود. throttle_scope = 'read' throttle_classes = [ScopedRateThrottle] # request=None لازم است: بدونِ آن spectacular نمی‌تواند بدنه‌ی PATCH را حدس # بزند و **کلِ ویو را از اسکیما کنار می‌گذارد** — یعنی این مسیر در قراردادی # که فرانت از آن کلاینت می‌سازد اصلاً دیده نمی‌شود. @extend_schema(request=None, responses=MarkReadResponseSerializer, tags=['Notifications (notifications)']) def patch(self, request, id): notification = get_object_or_404(Notification, pk=id, user_id=request.user.id) if not notification.is_read: notification.is_read = True notification.save(update_fields=['is_read']) return Response(MarkReadResponseSerializer({'success': True}).data) class NotificationMarkAllReadView(APIView): """ PATCH /api/notifications/read-all — همه‌ی اعلان‌های کاربر خوانده‌شده. دکمه‌ی «همه خوانده شد» پیش از این برای هر اعلان یک درخواستِ جدا می‌زد؛ حسابی با ۷۵ اعلان یعنی ۷۵ درخواستِ هم‌زمان که سهمِ throttleِ read را (همان که /home و بقیه مصرف می‌کنند) یک‌جا می‌خورد. """ permission_classes = [IsAuthenticated] throttle_scope = 'read' throttle_classes = [ScopedRateThrottle] @extend_schema(request=None, responses=MarkAllReadResponseSerializer, tags=['Notifications (notifications)']) def patch(self, request): updated = Notification.objects.filter( user_id=request.user.id, is_read=False, ).update(is_read=True) return Response(MarkAllReadResponseSerializer({'success': True, 'updated': updated}).data)