Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion apps/posts/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,27 @@ def generate_feed_post(self, quest_id: str) -> None:
if Post.objects.filter(quest=quest).exists():
return

Post.objects.create(
post = Post.objects.create(
character=character,
quest=quest,
content=caption,
img_url=img_url,
)

# 피드가 실제로 올라오면 작성 캐릭터의 주인에게 알림(토스트+알림창)을 남긴다.
# 알림 생성 실패가 피드 생성을 깨뜨리지 않도록 best-effort 로 처리한다.
try:
from apps.users.notification_service import create_feed_post_notification

create_feed_post_notification(
user=character.user,
character_name=character.character_name,
caption=caption,
post_id=post.post_id,
)
except Exception:
logger.warning("feed notification 생성 실패: quest_id=%s", quest_id)


@shared_task(bind=True, max_retries=2)
def generate_character_reply(self, comment_id: str) -> None:
Expand Down
19 changes: 19 additions & 0 deletions apps/users/notification_service.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,30 @@
from __future__ import annotations

from datetime import date
import uuid

from apps.todos.models import Reflection
from apps.users.models import Notification, User

REFLECTION_NOTIFICATION_TYPE = "reflection"
FEED_NOTIFICATION_TYPE = "feed"


def create_feed_post_notification(
*, user: User, character_name: str, caption: str, post_id: uuid.UUID | str
) -> Notification:
"""피드(마을 게시판 글)가 생성되면 작성 캐릭터의 주인에게 알림을 남긴다.

reflection 알림과 동일하게 Notification 한 건을 만들고, 프론트는 폴링으로 받아
토스트 + 알림창에 함께 띄운다. type/data 필드만 쓰므로 스키마 변경이 없다.
"""
return Notification.objects.create(
user=user,
type=FEED_NOTIFICATION_TYPE,
title=f"{character_name}의 새 소식이 도착했어요!",
content=caption,
data={"target": "feed", "post_id": str(post_id)},
)


def create_reflection_notification(
Expand Down
32 changes: 31 additions & 1 deletion tests/test_posts.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from apps.quests.models import Quest
from apps.tags.models import Tag
from apps.todos.models import Todo
from apps.users.models import TokenTransaction, User
from apps.users.models import Notification, TokenTransaction, User


@pytest.mark.django_db
Expand Down Expand Up @@ -50,6 +50,36 @@ def test_generate_feed_post_sends_appearance_payload(
assert Post.objects.filter(quest=quest).exists()


@pytest.mark.django_db
def test_generate_feed_post_creates_feed_notification(
character: Character, quest: Quest, settings
) -> None:
# 피드가 생성되면 작성 캐릭터의 주인에게 type="feed" 알림이 남아야 한다.
settings.AI_SERVICE_URL = "http://ai.test"
settings.AI_SERVICE_TOKEN = "secret-key" # noqa: S105

response = MagicMock()
response.raise_for_status.return_value = None
response.json.return_value = {
"result": {
"image_url": "https://cdn.test/feed.png",
"caption": "오늘도 산책을 완료했어요.",
}
}

with patch("apps.posts.tasks.httpx.post", return_value=response):
from apps.posts.tasks import generate_feed_post

generate_feed_post.run(str(quest.quest_id))

post = Post.objects.get(quest=quest)
note = Notification.objects.get(user=character.user, type="feed")
assert character.character_name in note.title
assert note.content == "오늘도 산책을 완료했어요."
assert note.data["target"] == "feed"
assert note.data["post_id"] == str(post.post_id)


def _make_other_user_post() -> Post:
"""다른 사용자 소유의 게시글을 하나 만든다(스코프 격리 검증용)."""
other = User.objects.create_user(
Expand Down
Loading