diff --git a/docker-compose.yml b/docker-compose.yml index 700bf1b..b9bc35b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -16,3 +16,22 @@ services: command: bash -c "pip install -r requirements.txt && python manage.py collectstatic --noinput && python manage.py migrate && gunicorn zod_bank.wsgi -b 0.0.0.0:8000 -t 300 --log-level=info" volumes: - .:/usr/src/app + + broker: + image: rabbitmq:3.7 + container_name: rabbitmq + volumes: + - .:/usr/src/app + ports: + - 5673:5673 + + worker: + build: . + image: celery + container_name: dev_celery + restart: "always" + command: bash -c " celery -A zod_bank.celery worker --concurrency=1 -B -l DEBUG -E" + volumes: + - .:/usr/src/app + depends_on: + - broker diff --git a/guardian/views.py b/guardian/views.py index b0e2c34..2776d38 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -1,11 +1,11 @@ """Views of Guardian""" -"""Third party Django app""" + +# django imports from rest_framework.permissions import IsAuthenticated from rest_framework import viewsets, status from rest_framework.pagination import PageNumberPagination from django.contrib.auth.models import User from django.utils import timezone -"""Import Django app""" # Import guardian's model, # Import junior's model, @@ -19,7 +19,6 @@ from django.utils import timezone # Import account's serializer # Import account's task - from .serializers import (UserSerializer, CreateGuardianSerializer, TaskSerializer, TaskDetailsSerializer, TopJuniorSerializer, ApproveJuniorSerializer, ApproveTaskSerializer) from .models import Guardian, JuniorTask @@ -31,6 +30,8 @@ from account.utils import custom_response, custom_error_response from base.messages import ERROR_CODE, SUCCESS_CODE from base.constants import NUMBER from .utils import upload_image_to_alibaba +from notifications.constants import REGISTRATION +from notifications.utils import send_notification """ Define APIs """ # Define Signup API, @@ -62,6 +63,7 @@ class SignupViewset(viewsets.ModelViewSet): user_type=str(request.data['user_type']), expired_at=expiry) """Send email to the register user""" send_otp_email(request.data['email'], otp) + send_notification(REGISTRATION, None, request.auth.payload['user_id'], {}) return custom_response(SUCCESS_CODE['3001'], response_status=status.HTTP_200_OK) return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST) diff --git a/notifications/admin.py b/notifications/admin.py index 7bd27a7..382e97b 100644 --- a/notifications/admin.py +++ b/notifications/admin.py @@ -3,4 +3,10 @@ notification admin file """ from django.contrib import admin -# Register your models here. +from notifications.models import Notification + + +@admin.register(Notification) +class NotificationAdmin(admin.ModelAdmin): + """Notification Admin""" + list_display = ['id', 'notification_type', 'notification_to', 'data', 'is_read'] diff --git a/notifications/constants.py b/notifications/constants.py new file mode 100644 index 0000000..b72ed61 --- /dev/null +++ b/notifications/constants.py @@ -0,0 +1,16 @@ +""" +notification constants file +""" +REGISTRATION = 1 +TEST_NOTIFICATION = 99 + +NOTIFICATION_DICT = { + REGISTRATION: { + "title": "Successfully registered!", + "body": "You have registered successfully. Now login and complete your profile." + }, + TEST_NOTIFICATION: { + "title": "Test Notification", + "body": "This notification is for testing purpose" + } +} diff --git a/notifications/migrations/0001_initial.py b/notifications/migrations/0001_initial.py new file mode 100644 index 0000000..ea73b4a --- /dev/null +++ b/notifications/migrations/0001_initial.py @@ -0,0 +1,30 @@ +# Generated by Django 4.2.2 on 2023-07-19 07:40 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Notification', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('notification_type', models.CharField(blank=True, max_length=50, null=True)), + ('data', models.JSONField(blank=True, default=dict, null=True)), + ('is_read', models.BooleanField(default=False)), + ('created_at', models.DateTimeField(default=django.utils.timezone.now)), + ('notification_from', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='from_notification', to=settings.AUTH_USER_MODEL)), + ('notification_to', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='to_notification', to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/notifications/models.py b/notifications/models.py index 90a6f02..5d6d02c 100644 --- a/notifications/models.py +++ b/notifications/models.py @@ -1,6 +1,24 @@ """ notification models file """ +# django imports from django.db import models +from django.contrib.auth import get_user_model +from django.utils import timezone -# Create your models here. +USER = get_user_model() + + +class Notification(models.Model): + """ used to save the notifications """ + notification_type = models.CharField(max_length=50, blank=True, null=True) + notification_to = models.ForeignKey(USER, related_name='to_notification', on_delete=models.CASCADE) + notification_from = models.ForeignKey(USER, related_name='from_notification', on_delete=models.SET_NULL, + blank=True, null=True) + data = models.JSONField(default=dict, blank=True, null=True) + is_read = models.BooleanField(default=False) + created_at = models.DateTimeField(default=timezone.now) + + def __str__(self): + """ string representation """ + return f"{self.notification_to.id} | {self.notification_to.email}" diff --git a/notifications/utils.py b/notifications/utils.py index d68756c..893947a 100644 --- a/notifications/utils.py +++ b/notifications/utils.py @@ -1,12 +1,25 @@ """ notifications utils file """ +import constant as constant # third party imports from fcm_django.models import FCMDevice +from celery import shared_task +from firebase_admin.messaging import Message, Notification as FirebaseNotification + +# django imports +from django.contrib.auth import get_user_model + +from account.models import UserNotification +from notifications.constants import NOTIFICATION_DICT +from notifications.models import Notification # local imports +User = get_user_model() + + def register_fcm_token(user_id, registration_id, device_id, device_type): """ used to register the fcm device token""" device, _ = FCMDevice.objects.update_or_create(device_id=device_id, @@ -25,3 +38,35 @@ def remove_fcm_token(user_id: int, access_token: str, registration_id) -> None: FCMDevice.objects.filter(user_id=user_id).delete() except Exception as e: print(e) + + +def get_basic_detail(notification_type, from_user_id, to_user_id): + """ used to get the basic details """ + notification_data = NOTIFICATION_DICT[notification_type] + from_user = User.objects.get(id=from_user_id) if from_user_id else None + to_user = User.objects.get(id=to_user_id) + return notification_data, from_user, to_user + + +@shared_task() +def send_notification(notification_type, from_user_id, to_user_id, extra_data): + """ used to send the push for the given notification type """ + (notification_data, from_user, to_user) = get_basic_detail(notification_type, from_user_id, to_user_id) + print(notification_data, to_user) + user_notification_type = UserNotification.objects.filter(user=to_user).first() + # data = notification_data.data + data = notification_data + Notification.objects.create(notification_type=notification_type, notification_from=from_user, + notification_to=to_user, data=data) + if user_notification_type.push_notification: + data.update({'badge': Notification.objects.filter(notification_to=to_user, is_read=False).count()}) + send_push(to_user, data) + + +def send_push(user, data): + """ used to send push notification to specific user """ + # if user.push_notification: + notification_data = data.pop('data', None) + user.fcmdevice_set.filter(active=True).send_message( + Message(notification=FirebaseNotification(data['title'], data['body']), data=notification_data) + ) diff --git a/notifications/views.py b/notifications/views.py index 8ac3786..d9e5fd7 100644 --- a/notifications/views.py +++ b/notifications/views.py @@ -8,9 +8,12 @@ from rest_framework.decorators import action from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response +# local imports from account.utils import custom_response from base.messages import SUCCESS_CODE +from notifications.constants import TEST_NOTIFICATION from notifications.serializers import RegisterDevice +from notifications.utils import send_notification class NotificationViewSet(viewsets.GenericViewSet): @@ -28,3 +31,12 @@ class NotificationViewSet(viewsets.GenericViewSet): serializer.is_valid(raise_exception=True) serializer.save() return custom_response(SUCCESS_CODE["3000"]) + + @action(methods=['get'], detail=False, url_path='test', url_name='test', serializer_class=None) + def send_test_notification(self, request): + """ + to send test notification + :return: + """ + send_notification.delay(TEST_NOTIFICATION, None, request.auth.payload['user_id'], {}) + return custom_response(SUCCESS_CODE["3000"]) diff --git a/zod-bank-fcm-credentials.json b/zod-bank-fcm-credentials.json new file mode 100644 index 0000000..75b13ab --- /dev/null +++ b/zod-bank-fcm-credentials.json @@ -0,0 +1,13 @@ +{ + "type": "service_account", + "project_id": "zod-bank-ebb2a", + "private_key_id": "f1115f1b1fece77ba7a119b70e2502ce0777a7d4", + "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCvLWEobyVN7D4+\nSZ4NwcugwuVk9MV7CjhQGB8lqzf/1Z0plBytjpjM75+orFk5n2tnOgTxsWCqR1R7\nLry4x2QH3HgJebd/TZTIyfepcAeuzUVhq9prgWVRsvxjpihMPZufA/Q1GEX5TBwX\nEasBW91Qwas2NBhUrzotnUBxOshVB4zCo3Ls9dbAN9O2O6paUMvcofSsRZ9XkB6P\nFFKy6nbQ3Bo+Lw3ntUfG1JQgkkxto2Vudiiq6J2dE6Eih2acEhEezQoJVpkMK+si\nlGp88T3j8nTx3o6ol99ke+3ZejPVE5sUbuhokSV/tS1Goy3whP+ys9lQtIyt3/mJ\nlmkoB9ShAgMBAAECggEAAk3H0GFF08C3EBWyDqH5dbLXbtH92e/UeNAMNcV4mGbY\n5GKGFywmEVmg2N22EPqa/s+Y6QxxrD9u9G1+EhbnIIx7olOBkScJ9C0c9CWztCon\ntPaofd1E1cI7I7UfVTg2ZLAHrBN4H70eadYc4va8bBtHj0EHYONz7S8sEBQ1Qna2\nIQuCEWY6MzhwCNEFIJd8ikd0GnkAJCuInK3F+2c37kugdgjRKxkTIfWmhNIfyWn3\ngoui5wltuuDKETVj9VUMyN6P7kffIJzS4mMRm/9F92scpPRbK+X1TrHpFsO826oP\nUuSBi5oOZYNyAvMBXGdnemoUNtAE+xg4kqwYJu5T0QKBgQDlbO08yH5MSLUxXZfL\nkCYg1mCUC4ySpL/qVFNuyvY+ex9gIg6gj4vEpK8s1lt0yvxrTng/MXxMMg7o9dO8\nmSlv/665twk/7NfkexJPWs1ph+54tud3Sa0TiVw6U5ObPMr1EYc7RnoELDR9Exc1\nUDJB+T3f3SGJicZn4pALyx132wKBgQDDd9lhqWLXJeG7JkInhLywuSVmnk077hY8\nxAfHqlO+/EEkM/YBtUvtC4KNiSkVftQwSM80NRiHlPvlWZ0G8l9RAfM/xxL/XUq6\nOu1fW1uJuukJgXFePV9SQLzfgd1fk1f/dKuiPSNhCzgTD7dFks0Ip6FD6/PCdN7x\neqRUqDccMwKBgQCTk1mW+7CiCTLkKjv2KScdgEhncnZd7bO1W8C/R7bVwgUQpVeb\nWDqjpvs3cDssCVYNAFDA9Wfq61hD6bzlV/AbpvARbfd5MzQ8OB4zBUmUVGfFJoIF\nbVLzeivlKNWNybETqs6+Bjt+a6Dnw1vuY0OwxE5Urb1g50rEkCvwKhsueQKBgQDB\nUt3rG46oX80cPkCbuUquNs/o6JRWu6m+u9s9/RYLBI6g8ctT8S2A6ytaNNgvbFsM\nzlYwunriTdW9Bp6p6jmfcyBUad4+NtTbz8BJ2Z91Xylwv1eS73xBa8nh/R0nlCEq\nhQfj1DgTmPcC0z5eT0z+TFzRQqK6JsEBcFzrZdvrxQKBgQDGEtWElG5XoDwnwO5e\nmfFLRKHfG+Xa6FChl2eeDIGOxv/Y6SMwT68t0gtnDFQGtcIMvC+kC/1Rv1v8yrOD\nCzEToh91Fw1c1eayNnsLq07qYnVWoMa7xFFSDhtBAnepqlU+81qaDvp+yILTIYSP\nwUuk3NpdJs2LkMetm5zJC9PJ2w==\n-----END PRIVATE KEY-----\n", + "client_email": "firebase-adminsdk-umcph@zod-bank-ebb2a.iam.gserviceaccount.com", + "client_id": "102629742363778142120", + "auth_uri": "https://accounts.google.com/o/oauth2/auth", + "token_uri": "https://oauth2.googleapis.com/token", + "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", + "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-umcph%40zod-bank-ebb2a.iam.gserviceaccount.com", + "universe_domain": "googleapis.com" +} diff --git a/zod_bank/celery.py b/zod_bank/celery.py new file mode 100644 index 0000000..177c318 --- /dev/null +++ b/zod_bank/celery.py @@ -0,0 +1,34 @@ +""" +Celery Basic configuration +""" + +# python imports +import os + +# third party imports +from celery import Celery +from dotenv import load_dotenv +from celery.schedules import crontab + +# OR, the same with increased verbosity: +load_dotenv(verbose=True) + +env_path = os.path.join(os.path.abspath(os.path.join('.env', os.pardir)), '.env') + +load_dotenv(dotenv_path=env_path) + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', os.getenv('SETTINGS')) + +# celery app +app = Celery() + +app.config_from_object('django.conf:settings') + +# Load task modules from all registered Django apps. +app.autodiscover_tasks() + + +@app.task(bind=True) +def debug_task(self): + """ celery debug task """ + print(f'Request: {self.request!r}')