feat(blog): notify users about comment activity

This commit is contained in:
2026-06-12 11:20:47 +03:30
parent 41f9be4c7e
commit 029f0c7b8d

View File

@@ -1,5 +1,6 @@
from __future__ import annotations from __future__ import annotations
import logging
import mimetypes import mimetypes
from pathlib import Path from pathlib import Path
from typing import List, Optional from typing import List, Optional
@@ -39,12 +40,14 @@ from apps.blog.permissions import (
can_review_blog_posts, can_review_blog_posts,
can_write_blog_posts, can_write_blog_posts,
) )
from apps.notifications.services import notify_user
from core.api.schemas import ErrorSchema, MessageSchema from core.api.schemas import ErrorSchema, MessageSchema
from core.authentication import jwt_auth from core.authentication import jwt_auth
blog_router = Router() blog_router = Router()
User = get_user_model() User = get_user_model()
logger = logging.getLogger(__name__)
IMAGE_EXTENSIONS = {".jpg", ".jpeg", ".png", ".gif", ".webp", ".bmp", ".tiff", ".svg"} IMAGE_EXTENSIONS = {".jpg", ".jpeg", ".png", ".gif", ".webp", ".bmp", ".tiff", ".svg"}
VIDEO_EXTENSIONS = {".mp4", ".webm", ".ogg", ".mov", ".mkv", ".avi"} VIDEO_EXTENSIONS = {".mp4", ".webm", ".ogg", ".mov", ".mkv", ".avi"}
@@ -240,6 +243,73 @@ def _interaction_payload(post: Post, user) -> BlogInteractionSchema:
) )
def _frontend_blog_url(post: Post) -> str:
root = getattr(settings, "FRONTEND_ROOT", "/") or "/"
if not root.endswith("/"):
root = f"{root}/"
return f"{root}blog/{post.slug}"
def _blog_moderator_ids() -> set[int]:
return set(
User.objects.filter(is_active=True)
.filter(
Q(is_staff=True)
| Q(is_superuser=True)
| Q(groups__permissions__content_type__app_label="blog", groups__permissions__codename="moderate_blog_comment")
| Q(user_permissions__content_type__app_label="blog", user_permissions__codename="moderate_blog_comment")
)
.distinct()
.values_list("id", flat=True)
)
def _post_author_ids(post: Post) -> set[int]:
author_ids = set(post.writers.values_list("id", flat=True))
if post.author_id:
author_ids.add(post.author_id)
return author_ids
def _notify_blog_comment(comment: Comment) -> None:
post = comment.post
actor_id = comment.author_id
action_url = _frontend_blog_url(post)
excluded_ids = {actor_id}
if comment.parent_id and comment.parent.author_id != actor_id:
notify_user(
comment.parent.author_id,
{
"type": "blog_reply",
"title": "پاسخ جدید",
"message": f"به کامنت شما در «{post.title}» پاسخ داده شد.",
"level": "info",
"action_url": action_url,
"entity_type": "blog_comment",
"entity_id": comment.id,
"meta": {"post_id": post.id, "post_slug": post.slug, "parent_id": comment.parent_id},
},
)
excluded_ids.add(comment.parent.author_id)
recipient_ids = (_blog_moderator_ids() | _post_author_ids(post)) - excluded_ids
for user_id in recipient_ids:
notify_user(
user_id,
{
"type": "blog_comment",
"title": "کامنت جدید",
"message": f"برای «{post.title}» کامنت جدید ثبت شد.",
"level": "info",
"action_url": action_url,
"entity_type": "blog_post",
"entity_id": post.id,
"meta": {"post_id": post.id, "post_slug": post.slug, "comment_id": comment.id},
},
)
@blog_router.get("/admin/writers", response={200: List[AuthorSchema], 403: ErrorSchema}, auth=jwt_auth) @blog_router.get("/admin/writers", response={200: List[AuthorSchema], 403: ErrorSchema}, auth=jwt_auth)
def list_blog_writers(request): def list_blog_writers(request):
if not (request.auth.is_superuser or request.auth.is_staff or can_review_blog_posts(request.auth)): if not (request.auth.is_superuser or request.auth.is_staff or can_review_blog_posts(request.auth)):
@@ -619,6 +689,10 @@ def create_comment(request, slug: str, data: CommentCreateSchema):
if data.parent_id: if data.parent_id:
parent = get_object_or_404(Comment, id=data.parent_id, post=post, is_approved=True, is_hidden=False, is_deleted=False) parent = get_object_or_404(Comment, id=data.parent_id, post=post, is_approved=True, is_hidden=False, is_deleted=False)
comment = Comment.objects.create(post=post, author=request.auth, content=data.content, parent=parent) comment = Comment.objects.create(post=post, author=request.auth, content=data.content, parent=parent)
try:
_notify_blog_comment(comment)
except Exception:
logger.exception("Failed to send blog comment notifications for comment=%s", comment.id)
return 201, comment return 201, comment