Skip to content

Commit

Permalink
Feat (push notifications): adds api, some bug fixes and additional tasks
Browse files Browse the repository at this point in the history
  • Loading branch information
leandrodesouzadev committed Nov 9, 2023
1 parent 574d9cc commit 399aaf5
Show file tree
Hide file tree
Showing 12 changed files with 180 additions and 4 deletions.
Empty file added src/api/__init__.py
Empty file.
17 changes: 17 additions & 0 deletions src/api/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from django.contrib.auth import decorators as auth_decorators
from django.urls import include, path
from drf_spectacular import views as drf_views

from . import v1

schema_view = drf_views.SpectacularAPIView.as_view()
swagger_view = drf_views.SpectacularSwaggerView.as_view(url_name="schema")
redoc_view = drf_views.SpectacularRedocView.as_view(url_name="schema")

app_name = "api"
urlpatterns = [
path("v1/", include((v1.urls, "api"), namespace="v1")),
path("schema/", auth_decorators.login_required(schema_view), name="schema"),
path("docs/", auth_decorators.login_required(swagger_view), name="docs"),
path("redoc/", auth_decorators.login_required(redoc_view), name="redoc"),
]
1 change: 1 addition & 0 deletions src/api/v1/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import urls
1 change: 1 addition & 0 deletions src/api/v1/push_notifications/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import urls
53 changes: 53 additions & 0 deletions src/api/v1/push_notifications/schemas.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
from rest_framework import serializers

from app.consts import push_notification as push_notification_consts
from app.drf.fields import create_choice_human_field
from app.drf.serializers import inline_serializer
from push_notifications import models


class PushNotificationInputSchema(serializers.Serializer):
only_read = serializers.BooleanField(required=False, allow_null=True)


PushNotificationKindField = create_choice_human_field(constant_class=push_notification_consts.Kind)
PushNotificationStatusField = create_choice_human_field(
constant_class=push_notification_consts.Status
)


class PushNotificationOutputSchema(serializers.ModelSerializer):
kind = PushNotificationKindField()
status = PushNotificationStatusField()
data = inline_serializer(
name="PushNotificationEnrichedDataOutputSchema",
fields={
"id": serializers.CharField(),
"createdAt": serializers.DateTimeField(),
"readAt": serializers.DateTimeField(),
"timeSinceCreated": serializers.DateTimeField(),
"kind": serializers.CharField(),
"meta": serializers.JSONField(),
},
source="enriched_data",
)

class Meta:
model = models.PushNotification
fields = (
"id",
"title",
"description",
"read_at",
"kind",
"status",
"data",
)


class PushNotificationReadManyInputSchema(serializers.Serializer):
ids = serializers.ListField(required=False, allow_null=True)


class PushNotificationReadManyOutputSchema(serializers.Serializer):
read = serializers.IntegerField()
8 changes: 8 additions & 0 deletions src/api/v1/push_notifications/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from rest_framework.routers import SimpleRouter

from . import views

router = SimpleRouter(trailing_slash=False)
router.register("notifications", views.PushNotificationViewSet, basename="push_notifications")

urlpatterns = router.urls
69 changes: 69 additions & 0 deletions src/api/v1/push_notifications/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
from rest_framework.decorators import action
from rest_framework.generics import get_object_or_404
from rest_framework.permissions import IsAuthenticated
from rest_framework.request import Request
from rest_framework.response import Response

from app.drf.openapi import limit_offset_openapi_schema, openapi_schema
from app.drf.viewsets import AppViewSet
from push_notifications import selectors, services

from . import schemas


class PushNotificationViewSet(AppViewSet):
permission_classes = [IsAuthenticated]

@limit_offset_openapi_schema(
wrapped_schema=schemas.PushNotificationOutputSchema,
operation_id="push-notification-list",
summary="Notifications list",
description="Returns a list of notifications",
request=None,
tags=["push notifications"],
add_unauthorized_response=True,
parameter_serializer=schemas.PushNotificationInputSchema,
)
def list(self, request: Request) -> Response:
params = self.get_valid_query_params(srlzr_class=schemas.PushNotificationInputSchema)
qs = selectors.push_notification_get_viewable_qs(user=request.user, filters=params)
return self.get_paginated_response(
queryset=qs, srlzr_class=schemas.PushNotificationOutputSchema
)

@openapi_schema(
summary="Read many notifications",
description="Reads a list of notifications",
request=schemas.PushNotificationReadManyInputSchema,
responses={200: schemas.PushNotificationReadManyOutputSchema},
tags=["push notifications"],
operation_id="push-notifications-read",
add_bad_request_response=True,
add_unauthorized_response=True,
)
@action(methods=["PATCH"], detail=False, url_path="read")
def read_many(self, request: Request) -> Response:
data = self.get_valid_data(srlzr_class=schemas.PushNotificationReadManyInputSchema)
updated = services.push_notification_read_many(reader=request.user, ids=data.get("ids"))
return Response(data={"read": updated})

@openapi_schema(
summary="Read notification",
description="Reads a single notification",
request=None,
responses={200: schemas.PushNotificationOutputSchema},
tags=["push notifications"],
operation_id="push-notification-read",
add_not_found_response=True,
add_bad_request_response=True,
add_unauthorized_response=True,
)
@action(methods=["PATCH"], detail=True)
def read(self, request: Request, id: int) -> Response:
notification = get_object_or_404(
selectors.push_notification_get_viewable_qs(user=request.user),
pk=id,
)
notification = services.push_notification_read(push_notification=notification)
out_srlzr = schemas.PushNotificationOutputSchema(instance=notification)
return Response(data=out_srlzr.data)
10 changes: 10 additions & 0 deletions src/api/v1/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from django.urls import include, path

from . import push_notifications

urlpatterns = [
path(
"notifications/",
include((push_notifications.urls, "push_notifications"), namespace="push_notifications"),
),
]
2 changes: 1 addition & 1 deletion src/app/settings/conf.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import os
from pathlib import Path
from typing import Any, Dict, List, Tuple
from typing import Any, Dict, List

import environ
from django.utils.translation import gettext_lazy as _
Expand Down
1 change: 1 addition & 0 deletions src/app/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,5 @@
path("api/schema/", auth_decorators.login_required(schema_view), name="schema"),
path("api/docs/", auth_decorators.login_required(swagger_view), name="docs"),
path("api/redoc/", auth_decorators.login_required(redoc_view), name="redoc"),
path("api/", include("api.urls", namespace="api")),
]
16 changes: 13 additions & 3 deletions src/push_notifications/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from app.models import BaseModel
from users.models import User

from . import models
from . import models, selectors


def push_notification_create(
Expand All @@ -31,8 +31,8 @@ def push_notification_create(
push_notification = models.PushNotification(
user=user,
kind=kind,
title=title,
description=description,
title=title[: consts.push_notification.MaxSize.TITLE_MAX_SIZE_ANDROID],
description=description[: consts.push_notification.MaxSize.DESCRIPTION_MAX_SIZE_ANDROID],
data=data,
status=consts.push_notification.Status.CREATED,
source_object=source_object,
Expand Down Expand Up @@ -229,3 +229,13 @@ def push_notification_read(
push_notification.status = consts.push_notification.Status.READ
push_notification.save()
return push_notification


def push_notification_read_many(*, reader: User, ids: Sequence[int]) -> int:
"""Reads a sequence of ids visible for the given `reader`. If `ids` is empty
reads all unread notifications.
Returns the updated notification count"""
qs = selectors.push_notification_get_viewable_qs(user=reader).filter(read_at__isnull=True)
if ids:
qs = qs.filter(pk__in=ids)
return qs.update(read_at=timezone.now(), status=consts.push_notification.Status.READ)
6 changes: 6 additions & 0 deletions src/push_notifications/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@
from . import models, services


@task()
def push_notification_send(notification_ids: list[int]):
notifications = models.PushNotification.objects.filter(id__in=notification_ids)
services.push_notification_send(notifications=notifications)


@task()
def push_notification_handle_delivery_failure(notification_id: int):
notification = models.PushNotification.objects.get(pk=notification_id)
Expand Down

0 comments on commit 399aaf5

Please sign in to comment.