diff --git a/app/consumers.py b/app/consumers.py index a87bb3ba..fbbcc76f 100644 --- a/app/consumers.py +++ b/app/consumers.py @@ -1,33 +1,15 @@ -import json - -from channels.db import database_sync_to_async from channels.exceptions import StopConsumer -from channels.generic.websocket import AsyncWebsocketConsumer +from channels.generic.websocket import AsyncJsonWebsocketConsumer -from app import models +class NotificationsConsumer(AsyncJsonWebsocketConsumer): + groups = ["notification"] -class MyAsyncWebSocketConsumer(AsyncWebsocketConsumer): async def connect(self): - print("Web socket connected") - print("Channel layer ", self.channel_layer) - print("Channel name", self.channel_name) await self.accept() - user = await database_sync_to_async(models.User.objects.get)( - email=self.scope["user"].username - ) - self.group_name = user.first_name - print(self.group_name) - await self.channel_layer.group_add(self.group_name, self.channel_name) async def receive(self, text_data=None, bytes_data=None): print("Message recieved from client...", text_data) - async def notification_send(self, event): - print(event) - await self.send(text_data=json.dumps({"msg": event["message"]})) - - async def disconnect(self, close_code): - print("Web socket disconnect") - await self.channel_layer.group_discard(self.group_name, self.channel_name) - raise StopConsumer() + async def push(self, event): + await self.send_json(content={"notifications": event["message"]}) diff --git a/app/management/commands/test_notify.py b/app/management/commands/test_notify.py new file mode 100644 index 00000000..2f44aaaa --- /dev/null +++ b/app/management/commands/test_notify.py @@ -0,0 +1,14 @@ +from django.core.management.base import BaseCommand + +from app.utils import push_notification + + +class Command(BaseCommand): + help = "Send Test Alert to Websocket" + + def handle(self, *args, **options): + msg = "Sending test notifications" + + push_notification(msg) + + self.stdout.write(self.style.SUCCESS("Done")) diff --git a/app/routing.py b/app/routing.py index f631dae6..0e226b86 100644 --- a/app/routing.py +++ b/app/routing.py @@ -1,7 +1,7 @@ -from django.urls import path +from django.urls import re_path from . import consumers websocket_urlpatterns = [ - path("ws/awc/", consumers.MyAsyncWebSocketConsumer.as_asgi()), + re_path(r"ws/notifications/$", consumers.NotificationsConsumer.as_asgi()), ] diff --git a/app/templates/app/api/home.html b/app/templates/app/api/home.html index 8065782d..91dbddb1 100644 --- a/app/templates/app/api/home.html +++ b/app/templates/app/api/home.html @@ -29,22 +29,25 @@ {% block js_includes %}{% endblock %} {% render_bundle 'main' 'js' %} diff --git a/app/utils.py b/app/utils.py index 42d2cc3a..4d1ad221 100755 --- a/app/utils.py +++ b/app/utils.py @@ -1,5 +1,7 @@ import boto3 +from asgiref.sync import async_to_sync from botocore.exceptions import ClientError +from channels.layers import get_channel_layer from django.conf import settings from django.core.mail import send_mail from django.template.loader import render_to_string @@ -82,3 +84,10 @@ def create_presigned_url(bucket_name, key, expiration=3600): # The response contains the presigned URL return response + + +def push_notification(message): + channel_layer = get_channel_layer() + async_to_sync(channel_layer.group_send)( + "notification", {"type": "push", "message": message} + ) diff --git a/app/views/api.py b/app/views/api.py index 3e21f30e..9a3ef8b0 100755 --- a/app/views/api.py +++ b/app/views/api.py @@ -3,8 +3,6 @@ import requests import slack -from asgiref.sync import async_to_sync -from channels.layers import get_channel_layer from django.conf import settings from django.contrib.auth import authenticate, login, update_session_auth_hash from django.contrib.auth.tokens import PasswordResetTokenGenerator @@ -30,7 +28,12 @@ from waffle import get_waffle_switch_model from app import models, serializers -from app.utils import create_presigned_url, send_email_forget_password, send_leave_email +from app.utils import ( + create_presigned_url, + push_notification, + send_email_forget_password, + send_leave_email, +) from app.views import mixins from project.settings import SLACK_ATTENDACE_CHANNEL, SLACK_SIGNING_SECRET, SLACK_TOKEN @@ -764,11 +767,7 @@ def update(self, request, *args, **kwargs): user = employee.user.first_name status = request.data["status"] message = user + ", Your leave has been " + status - print(message) - channel_layer = get_channel_layer() - async_to_sync(channel_layer.group_send)( - user, {"type": "notification.send", "message": message} - ) + push_notification(message) notification = models.Notification(user=employee.user, message=message) notification.save() return response diff --git a/docker-compose.staging.yml b/docker-compose.staging.yml index 0715087c..b149592e 100644 --- a/docker-compose.staging.yml +++ b/docker-compose.staging.yml @@ -8,6 +8,11 @@ services: - ./env.db volumes: - db_data:/var/lib/postgresql/data + + redis: + image: redis:7.0-alpine + expose: + - 6379 app: build: diff --git a/docker-compose.yml b/docker-compose.yml index 491fd0d5..3566674c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,6 +9,9 @@ services: volumes: - db_data:/var/lib/postgresql/data + redis: + image: redis:7.0-alpine + app: build: context: . @@ -23,6 +26,7 @@ services: - .:/opt/code depends_on: - db + - redis availability: build: diff --git a/env.sample b/env.sample index cb28868a..f3f5c9b7 100644 --- a/env.sample +++ b/env.sample @@ -20,3 +20,5 @@ AWS_ACCESS_KEY_ID= AWS_SECRET_ACCESS_KEY= AWS_DEFAULT_REGION= AWS_STORAGE_BUCKET_NAME= +REDIS_HOST="redis" +REDIS_PORT=6379 diff --git a/frontend/swagger.json b/frontend/swagger.json index eb6227c4..9cb63120 100644 --- a/frontend/swagger.json +++ b/frontend/swagger.json @@ -4843,14 +4843,10 @@ }, "max_hours_per_week": { "type": "integer", - "maximum": 32767, - "minimum": -32768, "nullable": true }, "expected_hours_per_week": { "type": "integer", - "maximum": 32767, - "minimum": -32768, "nullable": true }, "updated_at": { @@ -5152,14 +5148,10 @@ }, "salary": { "type": "integer", - "maximum": 2147483647, - "minimum": 0, "nullable": true }, "leave_count": { - "type": "integer", - "maximum": 2147483647, - "minimum": -2147483648 + "type": "integer" }, "user_allowed": { "type": "boolean" @@ -5176,14 +5168,10 @@ }, "weekly_available_hours": { "type": "integer", - "maximum": 2147483647, - "minimum": -2147483648, "nullable": true }, "monthly_available_hours": { "type": "integer", - "maximum": 2147483647, - "minimum": -2147483648, "nullable": true }, "availability_last_msg": { @@ -5340,14 +5328,10 @@ }, "salary": { "type": "integer", - "maximum": 2147483647, - "minimum": 0, "nullable": true }, "leave_count": { - "type": "integer", - "maximum": 2147483647, - "minimum": -2147483648 + "type": "integer" }, "user_allowed": { "type": "boolean" diff --git a/project/asgi.py b/project/asgi.py index 8a58df67..96aab614 100644 --- a/project/asgi.py +++ b/project/asgi.py @@ -10,6 +10,7 @@ from channels.auth import AuthMiddlewareStack from channels.routing import ProtocolTypeRouter, URLRouter +from channels.security.websocket import AllowedHostsOriginValidator from django.core.asgi import get_asgi_application import app.routing @@ -21,6 +22,8 @@ application = ProtocolTypeRouter( { "http": get_asgi_application(), - "websocket": AuthMiddlewareStack(URLRouter(app.routing.websocket_urlpatterns)), + "websocket": AllowedHostsOriginValidator( + AuthMiddlewareStack(URLRouter(app.routing.websocket_urlpatterns)) + ), } ) diff --git a/project/settings.py b/project/settings.py index 5c2da0d5..feaf49b8 100644 --- a/project/settings.py +++ b/project/settings.py @@ -69,12 +69,12 @@ # Application definition INSTALLED_APPS = [ + "daphne", "django.contrib.admin", "django.contrib.auth", "django.contrib.contenttypes", "django.contrib.sessions", "django.contrib.messages", - "daphne", "django.contrib.staticfiles", "django.contrib.sites", # Internal Apps @@ -126,7 +126,7 @@ "default": { "BACKEND": "channels_redis.core.RedisChannelLayer", "CONFIG": { - "hosts": [("localhost", 6379)], + "hosts": [(env("REDIS_HOST"), env("REDIS_PORT"))], }, }, } diff --git a/requirements.txt b/requirements.txt index 5613ca64..71857a22 100644 --- a/requirements.txt +++ b/requirements.txt @@ -16,7 +16,6 @@ factory-boy==3.2.1 gunicorn==20.1.0 Pillow==9.4.0 psycopg2==2.9.5 -redis==4.5.1 requests==2.28.2 schedule==1.1.0 slackclient==2.9.4