feat: replace email flows with in-app notifications
Add recipient notification detail and filtering, a queued admin composer, and contextual automatic titles. Retire email delivery, obsolete auth routes, newsletter data, and related templates and settings.
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
"""Schemas for communications-related endpoints."""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
from typing import Literal, Optional
|
||||
|
||||
from ninja import ModelSchema, Schema
|
||||
|
||||
@@ -12,10 +12,6 @@ from apps.communications.models import Announcement, PushNotificationDevice
|
||||
class AnnouncementSchema(ModelSchema):
|
||||
author: AuthorSchema
|
||||
content_html: str
|
||||
deliver_in_app: bool
|
||||
deliver_sms: bool
|
||||
in_app_sent: bool
|
||||
sms_sent: bool
|
||||
|
||||
class Config:
|
||||
model = Announcement
|
||||
@@ -27,7 +23,6 @@ class AnnouncementSchema(ModelSchema):
|
||||
"priority",
|
||||
"is_published",
|
||||
"publish_date",
|
||||
"target_audience",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
]
|
||||
@@ -36,23 +31,6 @@ class AnnouncementSchema(ModelSchema):
|
||||
def resolve_content_html(obj):
|
||||
return obj.content_html
|
||||
|
||||
@staticmethod
|
||||
def resolve_deliver_in_app(obj):
|
||||
return obj.send_email
|
||||
|
||||
@staticmethod
|
||||
def resolve_deliver_sms(obj):
|
||||
return obj.send_push
|
||||
|
||||
@staticmethod
|
||||
def resolve_in_app_sent(obj):
|
||||
return obj.email_sent
|
||||
|
||||
@staticmethod
|
||||
def resolve_sms_sent(obj):
|
||||
return obj.push_sent
|
||||
|
||||
|
||||
class AnnouncementListSchema(Schema):
|
||||
id: int
|
||||
title: str
|
||||
@@ -62,34 +40,25 @@ class AnnouncementListSchema(Schema):
|
||||
author: AuthorSchema
|
||||
is_published: bool
|
||||
publish_date: Optional[datetime] = None
|
||||
target_audience: str
|
||||
deliver_in_app: bool
|
||||
deliver_sms: bool
|
||||
created_at: datetime
|
||||
|
||||
|
||||
class AnnouncementCreateSchema(Schema):
|
||||
title: str
|
||||
content: str
|
||||
announcement_type: str = "general"
|
||||
priority: str = "normal"
|
||||
target_audience: str = "all"
|
||||
announcement_type: Literal["general", "event", "academic", "urgent"] = "general"
|
||||
priority: Literal["low", "normal", "high", "urgent"] = "normal"
|
||||
is_published: bool = False
|
||||
publish_date: Optional[datetime] = None
|
||||
deliver_in_app: bool = True
|
||||
deliver_sms: bool = False
|
||||
|
||||
|
||||
class AnnouncementUpdateSchema(Schema):
|
||||
title: Optional[str] = None
|
||||
content: Optional[str] = None
|
||||
announcement_type: Optional[str] = None
|
||||
priority: Optional[str] = None
|
||||
target_audience: Optional[str] = None
|
||||
announcement_type: Optional[Literal["general", "event", "academic", "urgent"]] = None
|
||||
priority: Optional[Literal["low", "normal", "high", "urgent"]] = None
|
||||
is_published: Optional[bool] = None
|
||||
publish_date: Optional[datetime] = None
|
||||
deliver_in_app: Optional[bool] = None
|
||||
deliver_sms: Optional[bool] = None
|
||||
|
||||
|
||||
class PushDeviceSchema(ModelSchema):
|
||||
@@ -109,13 +78,6 @@ class PushDeviceUpdateSchema(Schema):
|
||||
is_active: bool
|
||||
|
||||
|
||||
class PushNotificationSchema(Schema):
|
||||
title: str
|
||||
body: str
|
||||
data: Optional[dict] = None
|
||||
target_audience: str = "all"
|
||||
|
||||
|
||||
class MessageResponseSchema(Schema):
|
||||
message: str
|
||||
success: bool = True
|
||||
@@ -126,5 +88,3 @@ class AnnouncementStatsSchema(Schema):
|
||||
published_announcements: int
|
||||
draft_announcements: int
|
||||
urgent_announcements: int
|
||||
in_app_sent_count: int
|
||||
sms_sent_count: int
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
from typing import List
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.db.models import Count, Q
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.utils import timezone
|
||||
@@ -16,14 +15,10 @@ from apps.communications.api.schemas import (
|
||||
PushDeviceCreateSchema,
|
||||
PushDeviceSchema,
|
||||
PushDeviceUpdateSchema,
|
||||
PushNotificationSchema,
|
||||
)
|
||||
from apps.communications.models import Announcement, AnnouncementPriority, AnnouncementType, PushNotificationDevice
|
||||
from apps.communications.push_notifications import push_service
|
||||
from apps.communications.tasks import send_announcement_notifications
|
||||
from core.authentication import jwt_auth
|
||||
|
||||
User = get_user_model()
|
||||
communications_router = Router()
|
||||
|
||||
|
||||
@@ -50,9 +45,6 @@ def list_announcements(request, published_only: bool = True):
|
||||
"author": announcement.author,
|
||||
"is_published": announcement.is_published,
|
||||
"publish_date": announcement.publish_date,
|
||||
"target_audience": announcement.target_audience,
|
||||
"deliver_in_app": announcement.send_email,
|
||||
"deliver_sms": announcement.send_push,
|
||||
"created_at": announcement.created_at,
|
||||
}
|
||||
)
|
||||
@@ -82,16 +74,10 @@ def create_announcement(request, payload: AnnouncementCreateSchema):
|
||||
content=payload.content,
|
||||
announcement_type=payload.announcement_type,
|
||||
priority=payload.priority,
|
||||
target_audience=payload.target_audience,
|
||||
is_published=payload.is_published,
|
||||
publish_date=payload.publish_date,
|
||||
send_email=payload.deliver_in_app,
|
||||
send_push=payload.deliver_sms,
|
||||
)
|
||||
|
||||
if announcement.is_published and (announcement.publish_date is None or announcement.publish_date <= timezone.now()):
|
||||
send_announcement_notifications.delay(announcement.id)
|
||||
|
||||
return announcement
|
||||
|
||||
|
||||
@@ -106,17 +92,10 @@ def update_announcement(request, announcement_id: int, payload: AnnouncementCrea
|
||||
announcement.content = payload.content
|
||||
announcement.announcement_type = payload.announcement_type
|
||||
announcement.priority = payload.priority
|
||||
announcement.target_audience = payload.target_audience
|
||||
announcement.is_published = payload.is_published
|
||||
announcement.publish_date = payload.publish_date
|
||||
announcement.send_email = payload.deliver_in_app
|
||||
announcement.send_push = payload.deliver_sms
|
||||
announcement.email_sent = False
|
||||
announcement.push_sent = False
|
||||
announcement.save()
|
||||
|
||||
if announcement.is_published and (announcement.publish_date is None or announcement.publish_date <= timezone.now()):
|
||||
send_announcement_notifications.delay(announcement.id)
|
||||
return announcement
|
||||
|
||||
|
||||
@@ -140,34 +119,10 @@ def get_announcement_stats(request):
|
||||
published_announcements=Count("id", filter=Q(is_published=True)),
|
||||
draft_announcements=Count("id", filter=Q(is_published=False)),
|
||||
urgent_announcements=Count("id", filter=Q(priority="urgent")),
|
||||
in_app_sent_count=Count("id", filter=Q(email_sent=True)),
|
||||
sms_sent_count=Count("id", filter=Q(push_sent=True)),
|
||||
)
|
||||
return stats
|
||||
|
||||
|
||||
@communications_router.post("/newsletter/subscribe/", response=MessageResponseSchema)
|
||||
def subscribe_newsletter(request):
|
||||
return {
|
||||
"message": "خبرنامه ایمیلی حذف شده است. اطلاعرسانیها از این پس درونسایتی و در موارد مهم از طریق پیامک انجام میشود."
|
||||
}
|
||||
|
||||
|
||||
@communications_router.post("/newsletter/unsubscribe/", response=MessageResponseSchema)
|
||||
def unsubscribe_newsletter(request):
|
||||
return {"message": "خبرنامه ایمیلی دیگر فعال نیست."}
|
||||
|
||||
|
||||
@communications_router.get("/newsletter/confirm/{token}/", response=MessageResponseSchema)
|
||||
def confirm_newsletter_subscription(request, token: str):
|
||||
return {"message": "تایید خبرنامه ایمیلی دیگر لازم نیست."}
|
||||
|
||||
|
||||
@communications_router.get("/newsletter/unsubscribe/{token}/", response=MessageResponseSchema)
|
||||
def unsubscribe_newsletter_token(request, token: str):
|
||||
return {"message": "خبرنامه ایمیلی دیگر فعال نیست."}
|
||||
|
||||
|
||||
@communications_router.post("/push-devices/", response=PushDeviceSchema, auth=jwt_auth)
|
||||
def register_push_device(request, payload: PushDeviceCreateSchema):
|
||||
user = request.auth
|
||||
@@ -207,16 +162,6 @@ def update_push_device(request, device_id: int, payload: PushDeviceUpdateSchema)
|
||||
return device
|
||||
|
||||
|
||||
@communications_router.post("/push-notifications/send/", response=MessageResponseSchema, auth=jwt_auth)
|
||||
def send_push_notification(request, payload: PushNotificationSchema):
|
||||
user = request.auth
|
||||
if not _is_staff_user(user):
|
||||
return {"error": "Permission denied"}, 403
|
||||
users = User.objects.filter(is_active=True)
|
||||
if payload.target_audience == "committee":
|
||||
users = users.filter(is_staff=True)
|
||||
total_sent = push_service.send_to_multiple_users(users, payload.title, payload.body, payload.data)
|
||||
return {"message": f"Push notification sent to {total_sent} devices"}
|
||||
|
||||
|
||||
@communications_router.get("/announcement-types/", response=List[dict])
|
||||
|
||||
Reference in New Issue
Block a user