From 34203e5fd516211caf3a339dd8960f07513e851e Mon Sep 17 00:00:00 2001 From: rizwanisready Date: Tue, 7 May 2024 22:26:13 +0530 Subject: [PATCH] subscription alert --- goodtimes/webhook.py | 17 +++- manage_notifications/api/cron_views.py | 66 ++++++++++++++ manage_notifications/api/serializers.py | 37 +++++++- manage_notifications/api/urls.py | 12 ++- manage_notifications/api/views.py | 33 +++++++ .../migrations/0003_inappnotification.py | 86 +++++++++++++++++++ ...e_inappnotification_created_at_and_more.py | 21 +++++ manage_notifications/models.py | 21 +++++ 8 files changed, 289 insertions(+), 4 deletions(-) create mode 100644 manage_notifications/api/cron_views.py create mode 100644 manage_notifications/migrations/0003_inappnotification.py create mode 100644 manage_notifications/migrations/0004_remove_inappnotification_created_at_and_more.py diff --git a/goodtimes/webhook.py b/goodtimes/webhook.py index d5e6cf7..5c6b8e3 100644 --- a/goodtimes/webhook.py +++ b/goodtimes/webhook.py @@ -5,6 +5,7 @@ from datetime import timedelta from django.utils import timezone from onesignal_sdk.client import Client as OneSignalClient from accounts.models import IAmPrincipal, IAmPrincipalOtp, IAmPrincipalType +from manage_notifications.models import InAppNotification, NotificationCategoryChoices from manage_referrals.models import ( GoodTimeCoins, ReferralRecord, @@ -40,6 +41,14 @@ class NotificationService: response = self.client.send_notification(notification_payload) return response + def save_notification(self, principal, title, message, notification_category): + InAppNotification.objects.create( + principal=principal, + title=title, + message=message, + notification_category=notification_category, + ) + def payment_success_notification( self, principal, subscription, principal_subscription, amount ): @@ -48,18 +57,21 @@ class NotificationService: end_date = principal_subscription.end_date message = f"Your payment for {subscription} of ${amount} was successfully processed. Your subscription is valid till {end_date}" self.send_notification(title, message, principal.player_id) + self.save_notification(principal, title, message, NotificationCategoryChoices.TRANSACTION) def referral_received_notification(self, principal, amount, email): print("referral_received_notification: ", principal.player_id) title = "Congratulations! You got a referral G-Token." message = f"Your referral {email} has subscribed to GoodTimesApp. You have received {amount} (£)" self.send_notification(title, message, principal.player_id) + self.save_notification(principal, title, message, NotificationCategoryChoices.REFERRAL) def payment_failed_notification(self, principal, subscription, amount): print("payment_failed_notification: ", principal.player_id) title = "Payment Failed!" message = f"Your payment for {subscription} of ${amount} was failed." self.send_notification(title, message, principal.player_id) + self.save_notification(principal, title, message, NotificationCategoryChoices.TRANSACTION) class WebhookService: @@ -257,7 +269,10 @@ class PaymentProcessingService: referral_service.credit_referral_reward_if_applicable() print("Third Part Done....!!!!!") self.notification_service.payment_success_notification( - self.principal, self.subscription, self.principal_subscription, self.transaction.amount + self.principal, + self.subscription, + self.principal_subscription, + self.transaction.amount, ) def handle_failure(self): diff --git a/manage_notifications/api/cron_views.py b/manage_notifications/api/cron_views.py new file mode 100644 index 0000000..84edf61 --- /dev/null +++ b/manage_notifications/api/cron_views.py @@ -0,0 +1,66 @@ +from goodtimes.utils import ApiResponse +from rest_framework.views import APIView +from rest_framework import status +from django.utils import timezone +from django.conf import settings +from datetime import timedelta +from onesignal_sdk.client import Client as OneSignalClient +from manage_notifications.models import ( + IAmPrincipalNotificationSettings, + InAppNotification, + NotificationCategoryChoices, +) +from manage_subscriptions.models import SubscriptionStatus + + +class OneWeekSubscriptionAlertView(APIView): + authentication_classes = [] + permission_classes = [] + + def post(self, request, *args, **kwargs): + self.client = OneSignalClient( + app_id=settings.ONE_SIGNAL_APP_ID, rest_api_key=settings.ONE_SIGNAL_API_KEY + ) + eligible_principals = self.eligible_principals() + + for principal in eligible_principals: + notification_title = "Subscription Expiry Reminder" + notification_message = "Your subscription is going to expire in a week." + notification_category = NotificationCategoryChoices.SUBSCRIPTION + + # Send notification to principal + notification_payload = { + "headings": {"en": notification_title}, + "contents": {"en": notification_message}, + "include_player_ids": [principal.player_id], + } + response = self.client.send_notification(notification_payload) + + # Save notification to InAppNotification table + in_app_notification = InAppNotification( + principal=principal, + title=notification_title, + message=notification_message, + notification_category=notification_category, + ) + in_app_notification.save() + + return ApiResponse.success( + message="Notifications sent successfully", + data="Notifications sent successfully", + status=status.HTTP_200_OK, + ) + + def eligible_principals(self): + one_week_from_now = timezone.now().date() + timedelta(days=7) + + eligible_principals = IAmPrincipalNotificationSettings.objects.filter( + principal__principalsubscription__end_date=one_week_from_now, + principal__principalsubscription__status=SubscriptionStatus.ACTIVE, + principal__principalsubscription__cancelled=False, + principal__principalsubscription__deleted=False, + notification_category=NotificationCategoryChoices.SUBSCRIPTION, + is_enabled=True, + ).select_related("principal") + + return eligible_principals diff --git a/manage_notifications/api/serializers.py b/manage_notifications/api/serializers.py index 55974d3..6f6beb8 100644 --- a/manage_notifications/api/serializers.py +++ b/manage_notifications/api/serializers.py @@ -1,5 +1,9 @@ from rest_framework import serializers -from manage_notifications.models import IAmPrincipalNotificationSettings, NotificationCategoryChoices +from manage_notifications.models import ( + IAmPrincipalNotificationSettings, + InAppNotification, + NotificationCategoryChoices, +) class IAmPrincipalNotificationSettingsSerializer(serializers.ModelSerializer): @@ -10,4 +14,33 @@ class IAmPrincipalNotificationSettingsSerializer(serializers.ModelSerializer): class Meta: model = IAmPrincipalNotificationSettings - fields = ['id', 'notification_category', 'notification_category_display', 'is_enabled'] + fields = [ + "id", + "notification_category", + "notification_category_display", + "is_enabled", + ] + + +class InAppNotificationSerializer(serializers.ModelSerializer): + notification_category = serializers.ChoiceField( + choices=NotificationCategoryChoices.choices + ) + + class Meta: + model = InAppNotification + fields = [ + "id", + "title", + "message", + "notification_category", + "created_on", + "principal", + ] + + def to_representation(self, instance): + representation = super().to_representation(instance) + representation["notification_category"] = ( + NotificationCategoryChoices.name_for_value(instance.notification_category) + ) + return representation diff --git a/manage_notifications/api/urls.py b/manage_notifications/api/urls.py index 64466c0..d302f2c 100644 --- a/manage_notifications/api/urls.py +++ b/manage_notifications/api/urls.py @@ -1,5 +1,5 @@ from django.urls import path -from . import views +from . import views, cron_views app_name = "manage_notifications_api" @@ -14,4 +14,14 @@ urlpatterns = [ views.UserNotificationsAPIView.as_view(), name="user-notifications", ), + path( + "in-app-notifications/", + views.InAppNotificationListAPIView.as_view(), + name="in_app_notifications", + ), + path( + "one-week-alert/", + cron_views.OneWeekSubscriptionAlertView.as_view(), + name="in_app_notifications", + ), ] diff --git a/manage_notifications/api/views.py b/manage_notifications/api/views.py index 329a3c0..1e557c7 100644 --- a/manage_notifications/api/views.py +++ b/manage_notifications/api/views.py @@ -1,14 +1,18 @@ from rest_framework import status from rest_framework.views import APIView from django.conf import settings +from rest_framework import generics from manage_notifications.api.serializers import ( IAmPrincipalNotificationSettingsSerializer, + InAppNotificationSerializer, ) from manage_notifications.models import ( IAmPrincipalNotificationSettings, + InAppNotification, NotificationCategoryChoices, ) from goodtimes import constants +from rest_framework.pagination import LimitOffsetPagination from goodtimes.utils import ApiResponse from rest_framework.permissions import IsAuthenticated from rest_framework_simplejwt.authentication import JWTAuthentication @@ -73,3 +77,32 @@ class UserNotificationsAPIView(APIView): message=constants.SUCCESS, status=status.HTTP_200_OK, ) + + +class InAppNotificationListAPIView(generics.ListAPIView): + serializer_class = InAppNotificationSerializer + pagination_class = LimitOffsetPagination # Add this line + + permission_classes = [IsAuthenticated] + authentication_classes = [JWTAuthentication] + + def get_queryset(self): + queryset = InAppNotification.objects.filter( + principal=self.request.user, deleted=False, active=True + ) + return queryset + + def list(self, request, *args, **kwargs): + queryset = self.filter_queryset(self.get_queryset()) + + page = self.paginate_queryset(queryset) + if page is not None: + serializer = self.get_serializer(page, many=True) + return self.get_paginated_response(serializer.data) + + serializer = self.get_serializer(queryset, many=True) + return ApiResponse.success( + message="InAppNotifications retrieved successfully", + data=serializer.data, + status=status.HTTP_200_OK, + ) diff --git a/manage_notifications/migrations/0003_inappnotification.py b/manage_notifications/migrations/0003_inappnotification.py new file mode 100644 index 0000000..53cb092 --- /dev/null +++ b/manage_notifications/migrations/0003_inappnotification.py @@ -0,0 +1,86 @@ +# Generated by Django 5.0.2 on 2024-05-07 12:34 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ( + "manage_notifications", + "0002_alter_pushnotification_notification_category_and_more", + ), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name="InAppNotification", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("active", models.BooleanField(default=True)), + ("deleted", models.BooleanField(default=False)), + ("created_on", models.DateTimeField(auto_now_add=True)), + ("modified_on", models.DateTimeField(auto_now=True)), + ("title", models.CharField(max_length=255)), + ("message", models.TextField()), + ( + "notification_category", + models.CharField( + choices=[ + ("general", "General"), + ("transaction", "Transaction"), + ("referral", "Referral"), + ("subscription", "Subscription"), + ("event", "Event"), + ("promotions", "Promotions"), + ], + max_length=50, + ), + ), + ("created_at", models.DateTimeField(auto_now_add=True)), + ("read_at", models.DateTimeField(blank=True, null=True)), + ( + "created_by", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(class)s_created", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "modified_by", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(class)s_modified", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "principal", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="in_app_notifications", + to=settings.AUTH_USER_MODEL, + ), + ), + ], + options={ + "db_table": "in_app_notifications", + }, + ), + ] diff --git a/manage_notifications/migrations/0004_remove_inappnotification_created_at_and_more.py b/manage_notifications/migrations/0004_remove_inappnotification_created_at_and_more.py new file mode 100644 index 0000000..6c60aeb --- /dev/null +++ b/manage_notifications/migrations/0004_remove_inappnotification_created_at_and_more.py @@ -0,0 +1,21 @@ +# Generated by Django 5.0.2 on 2024-05-07 13:41 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("manage_notifications", "0003_inappnotification"), + ] + + operations = [ + migrations.RemoveField( + model_name="inappnotification", + name="created_at", + ), + migrations.RemoveField( + model_name="inappnotification", + name="read_at", + ), + ] diff --git a/manage_notifications/models.py b/manage_notifications/models.py index b7cde3a..2f27be6 100644 --- a/manage_notifications/models.py +++ b/manage_notifications/models.py @@ -55,3 +55,24 @@ class IAmPrincipalNotificationSettings(BaseModel): def __str__(self): return f"{self.principal.first_name} - {self.notification_category}" + + +class InAppNotification(BaseModel): + """ + Model for in-app notifications + """ + principal = models.ForeignKey( + IAmPrincipal, on_delete=models.CASCADE, related_name="in_app_notifications" + ) + title = models.CharField(max_length=255) + message = models.TextField() + notification_category = models.CharField( + max_length=50, + choices=NotificationCategoryChoices.choices, + ) + + def __str__(self): + return f"{self.title} - {self.notification_category}" + + class Meta: + db_table = "in_app_notifications"