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 GuestAwareReadThrottle(ScopedRateThrottle): """همون الگوی GuestAware*Throttle: مهمان با IP کلید می‌خوره.""" def get_cache_key(self, request, view): user = request.user if user and user.is_authenticated and getattr(user, 'is_guest', False): ident = f'guest-ip:{self.get_ident(request)}' elif user and user.is_authenticated: ident = user.pk else: ident = self.get_ident(request) return self.cache_format % {'scope': self.scope, 'ident': ident} class NotificationListView(APIView): """GET /api/notifications — فهرست اعلان‌های کاربر (جدیدترین اول)""" permission_classes = [IsAuthenticated] throttle_scope = 'read' throttle_classes = [GuestAwareReadThrottle] @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] # 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 = [GuestAwareReadThrottle] @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)