diff --git a/apps/account/serializers/notification.py b/apps/account/serializers/notification.py index d9823e9..87cd938 100644 --- a/apps/account/serializers/notification.py +++ b/apps/account/serializers/notification.py @@ -23,4 +23,14 @@ class NotificationSendSerializer(serializers.Serializer): data = serializers.DictField(required=False) account_id = serializers.CharField(required=True) user_type = serializers.CharField(required=True) - service = serializers.ChoiceField(choices=Notification.ServiceChoices.choices, default=Notification.ServiceChoices.IMAM_JAVAD) \ No newline at end of file + service = serializers.ChoiceField(choices=Notification.ServiceChoices.choices, default=Notification.ServiceChoices.IMAM_JAVAD) + + +class AdminNotificationSerializer(serializers.ModelSerializer): + user_fullname = serializers.CharField(source='user.fullname', read_only=True) + user_email = serializers.CharField(source='user.email', read_only=True) + user_id = serializers.IntegerField(source='user.id', read_only=True) + + class Meta: + model = Notification + fields = ['id', 'title', 'message', 'is_read', 'service', 'created_at', 'updated_at', 'user_fullname', 'user_email', 'user_id'] \ No newline at end of file diff --git a/apps/account/urls.py b/apps/account/urls.py index c29ea45..140451b 100644 --- a/apps/account/urls.py +++ b/apps/account/urls.py @@ -9,6 +9,7 @@ from apps.geolocation_package.views.region_info import RegionInfoView admin_router = SimpleRouter() admin_router.register(r'users', views.AdminUserViewSet, basename='admin-users') +admin_router.register(r'notifications', views.AdminNotificationViewSet, basename='admin-notifications') urlpatterns = [ path('location-info/', IPGeolocationAPIView.as_view(), name='location-info'), diff --git a/apps/account/views/notification.py b/apps/account/views/notification.py index 632177b..be1a41a 100644 --- a/apps/account/views/notification.py +++ b/apps/account/views/notification.py @@ -4,10 +4,16 @@ from rest_framework.authentication import TokenAuthentication from drf_yasg.utils import swagger_auto_schema from drf_yasg import openapi from rest_framework.permissions import IsAuthenticated -from apps.account.serializers import NotificationSerializer, NotificationSendSerializer +from apps.account.serializers import NotificationSerializer, NotificationSendSerializer, AdminNotificationSerializer from apps.account.models import Notification, User from apps.account.tasks import send_notification from utils.pagination import StandardResultsSetPagination +from django.db.models import Q +from rest_framework.viewsets import ModelViewSet +from apps.account.permissions import IsSuperAdmin +import logging + +logger = logging.getLogger(__name__) @@ -63,49 +69,22 @@ class NotificationReadAllView(generics.GenericAPIView): authentication_classes = [TokenAuthentication] queryset = Notification.objects.all() - @swagger_auto_schema( - operation_description="Mark all notifications as read for the authenticated user or merchant account.", + operation_description="Mark all notifications as read for the authenticated user.", tags=['Notifications'], - manual_parameters=[ - openapi.Parameter( - 'service', - openapi.IN_QUERY, - description="Filter notifications to mark as read by service (imam-javad or doboodi)", - type=openapi.TYPE_STRING, - enum=['imam-javad', 'doboodi'], - required=False - ) - ], responses={ 200: "All notifications marked as read", } ) def get(self, request, *args, **kwargs): user = request.user - service = request.query_params.get('service', None) - # Get base queryset for user's notifications - notifications = Notification.objects.filter(user=user) + # Mark all of the user's notifications as read + Notification.objects.filter(user=user).update(is_read=True) + + return Response({'status': 'all notifications marked as read'}, status=status.HTTP_200_OK) + - # Apply service filtering based on query parameter - if service == 'doboodi': - # If service is doboodi, only mark doboodi notifications as read - notifications = notifications.filter(service=Notification.ServiceChoices.DOBOODI) - status_message = 'all doboodi notifications marked as read' - else: - # Default: mark all imam-javad notifications as read (exclude doboodi) - notifications = notifications.exclude(service=Notification.ServiceChoices.DOBOODI) - status_message = 'all imam-javad notifications marked as read' - - # Update the filtered notifications - notifications.update(is_read=True) - - return Response({'status': status_message}, status=status.HTTP_200_OK) - - - - class SendNotificationView(generics.GenericAPIView): @swagger_auto_schema( @@ -157,3 +136,109 @@ class SendNotificationView(generics.GenericAPIView): return Response({ 'error': str(e) }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + + +class AdminNotificationViewSet(ModelViewSet): + permission_classes = [IsSuperAdmin] + authentication_classes = [TokenAuthentication] + serializer_class = AdminNotificationSerializer + pagination_class = StandardResultsSetPagination + + def get_queryset(self): + queryset = Notification.objects.all().select_related('user') + + # Filter by service if provided + service = self.request.query_params.get('service', None) + if service: + queryset = queryset.filter(service=service) + + # Search by user name or email or notification title/message + search = self.request.query_params.get('search', None) + if search: + queryset = queryset.filter( + Q(title__icontains=search) | + Q(message__icontains=search) | + Q(user__fullname__icontains=search) | + Q(user__email__icontains=search) + ) + + # Date filtering + date_after = self.request.query_params.get('date_after', None) + if date_after: + queryset = queryset.filter(created_at__date__gte=date_after) + + date_before = self.request.query_params.get('date_before', None) + if date_before: + queryset = queryset.filter(created_at__date__lte=date_before) + + # User filtering + user_param = self.request.query_params.get('user', None) + if user_param: + queryset = queryset.filter(user_id=user_param) + + # Read status filtering (is_read=true/false) + is_read_param = self.request.query_params.get('is_read', None) + if is_read_param is not None: + if is_read_param.lower() == 'true': + queryset = queryset.filter(is_read=True) + elif is_read_param.lower() == 'false': + queryset = queryset.filter(is_read=False) + + return queryset.order_by('-created_at') + + def create(self, request, *args, **kwargs): + title = request.data.get('title') + message = request.data.get('message') + service = request.data.get('service', 'imam-javad') + send_to_all = request.data.get('send_to_all', False) + user_id = request.data.get('user_id', None) + + if not title or not message: + return Response({'error': 'Title and message are required.'}, status=status.HTTP_400_BAD_REQUEST) + + if send_to_all: + # Get all active users + users = User.objects.filter(is_active=True) + notifications_to_create = [] + for u in users: + notifications_to_create.append(Notification( + title=title, + message=message, + service=service, + user=u + )) + Notification.objects.bulk_create(notifications_to_create) + + # Send push notifications + fcm_tokens = list(users.exclude(fcm__isnull=True).exclude(fcm='').values_list('fcm', flat=True)) + if fcm_tokens: + try: + send_notification(fcm_tokens, title, message, {'service': service}) + except Exception as e: + logger.error(f"Failed to send push notifications: {e}") + + return Response({'message': f'Notification created for {len(notifications_to_create)} users.'}, status=status.HTTP_201_CREATED) + else: + if not user_id: + return Response({'error': 'user_id is required when send_to_all is false.'}, status=status.HTTP_400_BAD_REQUEST) + try: + user = User.objects.get(id=user_id) + except User.DoesNotExist: + return Response({'error': 'User not found.'}, status=status.HTTP_404_NOT_FOUND) + + notif = Notification.objects.create( + title=title, + message=message, + service=service, + user=user + ) + + # Send push notification + if user.fcm: + try: + send_notification([user.fcm], title, message, {'service': service}) + except Exception as e: + logger.error(f"Failed to send push notification: {e}") + + serializer = self.get_serializer(notif) + return Response(serializer.data, status=status.HTTP_201_CREATED)