Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Creat notification FastAPI #465

Closed
wants to merge 2 commits into from
Closed
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
42 changes: 42 additions & 0 deletions ara/controller/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
from fastapi import APIRouter, Depends, HTTPException
from ara.controller.authentication import get_current_user
from ara.service.notification_service import NotificationService
from ara.domain.user import User
from ara.domain.exceptions import EntityDoesNotExist
from pydantic import BaseModel
Comment on lines +1 to +6
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

해당 파일은 ara/controller/notification/notification_controller.py로 빼주세요


router = APIRouter()
notification_service = NotificationService()

class NotificationRead(BaseModel):
notification_id: int

@router.get("/notifications")
async def list_notifications(current_user: User = Depends(get_current_user)):
notifications = notification_service.get_notifications_for_user(current_user)
return notifications

@router.post("/notifications/{notification_id}/read")
async def mark_notification_as_read(notification_id: int, current_user: User = Depends(get_current_user)):
try:
notification_service.mark_notification_as_read(notification_id, current_user)
except EntityDoesNotExist:
raise HTTPException(status_code=404, detail="Notification not found")
except PermissionError:
raise HTTPException(status_code=403, detail="You are not allowed to mark this notification as read")
return {"message": "Notification marked as read successfully"}

@router.post("/notifications/read-all")
async def mark_all_notifications_as_read(current_user: User = Depends(get_current_user)):
notification_service.mark_all_notifications_as_read(current_user)
return {"message": "All notifications marked as read successfully"}

@router.post("/notifications/send-push-notification")
async def send_push_notification(notification: NotificationRead, current_user: User = Depends(get_current_user)):
try:
notification_service.send_push_notification(notification.notification_id, current_user)
except EntityDoesNotExist:
raise HTTPException(status_code=404, detail="Notification not found")
except PermissionError:
raise HTTPException(status_code=403, detail="You are not allowed to send push notification for this notification")
return {"message": "Push notification sent successfully"}
Empty file added ara/controller/api2.py
Empty file.
42 changes: 42 additions & 0 deletions ara/controller/notification/notification_controller.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
from fastapi import APIRouter, Depends, HTTPException
from ara.controller.authentication import get_current_user
from ara.service.notification.notification_service import NotificationService
from ara.domain.user import User
from ara.domain.exceptions import EntityDoesNotExist
from pydantic import BaseModel

router = APIRouter()
notification_service = NotificationService()

class NotificationRead(BaseModel):
notification_id: int

@router.get("/notifications")
async def list_notifications(current_user: User = Depends(get_current_user)):
notifications = notification_service.get_notifications_for_user(current_user)
return notifications

@router.post("/notifications/{notification_id}/read")
async def mark_notification_as_read(notification_id: int, current_user: User = Depends(get_current_user)):
try:
notification_service.mark_notification_as_read(notification_id, current_user)
except EntityDoesNotExist:
raise HTTPException(status_code=404, detail="Notification not found")
except PermissionError:
raise HTTPException(status_code=403, detail="You are not allowed to mark this notification as read")
return {"message": "Notification marked as read successfully"}

@router.post("/notifications/read-all")
async def mark_all_notifications_as_read(current_user: User = Depends(get_current_user)):
notification_service.mark_all_notifications_as_read(current_user)
return {"message": "All notifications marked as read successfully"}

@router.post("/notifications/send-push-notification")
async def send_push_notification(notification: NotificationRead, current_user: User = Depends(get_current_user)):
try:
notification_service.send_push_notification(notification.notification_id, current_user)
except EntityDoesNotExist:
raise HTTPException(status_code=404, detail="Notification not found")
except PermissionError:
raise HTTPException(status_code=403, detail="You are not allowed to send push notification for this notification")
return {"message": "Push notification sent successfully"}
2 changes: 2 additions & 0 deletions ara/domain/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
class EntityDoesNotExist(Exception):
pass
14 changes: 14 additions & 0 deletions ara/domain/notification.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from pydantic import BaseModel
from typing import Optional

class Notification(BaseModel):
id: int
type: str
title: str
content: str
related_article_id: Optional[int]
related_comment_id: Optional[int]
is_read: bool

class Config:
orm_mode = True
Empty file.
6 changes: 6 additions & 0 deletions ara/domain/notification/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from enum import IntFlag, auto

class NameType(IntFlag):
REGULAR = auto()
ANONYMOUS = auto()
REALNAME = auto()
12 changes: 12 additions & 0 deletions ara/domain/notification/notification_domain.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from ara.domain.notification.type import NotificationInfo
from ara.infra.notification.notification_infra import NotificationInfra

class NotificationDomain:
def __init__(self) -> None:
self.notification_infra = NotificationInfra()

def get_all_notifications(self) -> list[NotificationInfo]:
return self.notification_infra.get_all_notifications()

class Config:
orm_mode = True
11 changes: 11 additions & 0 deletions ara/domain/notification/type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from pydantic import BaseModel
from typing import Optional

class NotificationInfo(BaseModel):
id: int
type: str
title: str
content: str
related_article_id: int | None
related_comment_id: Optional[int]
is_read: bool
24 changes: 24 additions & 0 deletions ara/infra/notification.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from fastapi import HTTPException
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

해당 파일 도 경로가 틀렸습니다 제가 올린 board refactoring PR 봐주세요

from sqlalchemy.orm import Session
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sqlalchemy 안쓰는걸로 알고 있는데.... 요건 뭔가요?

from ara.domain.notification import Notification
from ara.domain.user import User
from ara.domain.exceptions import EntityDoesNotExist
from typing import List

class NotificationRepository:
def __init__(self, db: Session):
self.db = db

def get(self, notification_id: int) -> Notification:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def get(self, notification_id: int) -> Notification:
def get_by_id(self, notification_id: int) -> Notification:

함수이름은 파라미터를 보지 않아도 딱봐도 알 수 있게 작성하는게 좋아요 👍

notification = self.db.query(Notification).filter(Notification.id == notification_id).first()
if not notification:
raise HTTPException(status_code=404, detail="Notification not found")
return notification

def save(self, notification: Notification):
self.db.add(notification)
self.db.commit()

def get_notifications_for_user(self, user: User) -> List[Notification]:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def get_notifications_for_user(self, user: User) -> List[Notification]:
def get_notifications_by_user(self, user: User) -> List[Notification]:

notifications = self.db.query(Notification).filter(Notification.user_id == user.id).all()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ORM 사용해야 합니다..

return notifications
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

파일 끝에 엔터로 끝내주세요!!
@injoonH 요거 우리 precommit에서 체크 안하나요?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EditorConfig가 돌고 있을 텐데 이상하네요 👀

Empty file.
56 changes: 56 additions & 0 deletions ara/infra/notification/notification_infra.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
from typing import List
import logging
from ara.domain.notification.type import NotificationInfo
from ara.infra.django_infra import AraDjangoInfra
from apps.core.models import Notification, NotificationReadLog


class NotificationInfra(AraDjangoInfra[Notification]):
def __init__(self) -> None:
super().__init__(Notification)

def get_all_notifications(self) -> list[NotificationInfo]:

queryset = Notification.objects.filter(
notification_read_log_set__read_by=self.request.user,
).select_related(
"related_article",
"related_comment",
).prefetch_related(
"related_article__attachments",
NotificationReadLog.prefetch_my_notification_read_log(
self.request.user
),
)

notifications_info = [self._to_notification_info(notification) for notification in queryset]
return notifications_info


def _to_notification_info(self, notification: Notification) -> NotificationInfo:
return NotificationInfo(
id=notification.id,
type=notification.type,
title=notification.title,
content=notification.content,
related_article_id=notification.related_article_id,
related_comment_id=notification.related_comment_id,
is_read=False
)

def read_all_notifications(self) -> None:
notifications = self.get_all_notifications()
NotificationReadLog.objects.filter(notification__in=notifications, read_by=self.request.user).update(is_read=True)

def read_notification(self) -> None:
try:
notification_read_log = self.get_object().notification_read_log_set.get(
read_by=self.request.user,
)

notification_read_log.is_read = True

notification_read_log.save()
except (Notification.DoesNotExist, NotificationReadLog.DoesNotExist) as e:
logging.error(f"Failed to read notification: {e}")

77 changes: 77 additions & 0 deletions ara/service/notification.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
from fastapi import HTTPException
from sqlalchemy.orm import Session
from ara.infra.notification import NotificationRepository
from ara.domain.notification import Notification
from ara.domain.user import User
from ara.domain.exceptions import EntityDoesNotExist
from ara.infra.firebase import fcm_notify_comment # assuming fcm_notify_comment is imported from correct path
from ara.domain.article import Article # Import Article and UserProfile from appropriate paths
from ara.domain.user_profile import UserProfile
from ara.domain.comment import Comment
Comment on lines +8 to +10
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이거 domain이 안보이는데...github branch 꼬인거 같아요


class NotificationService:
def __init__(self, notification_repo: NotificationRepository):
self.notification_repo = notification_repo

def get_display_name(self, article: Article, profile: UserProfile) -> str:
"""
Returns the display name for an article based on its name type and user profile.
"""
if article.name_type == NameType.REALNAME:
return profile.realname
elif article.name_type == NameType.REGULAR:
return profile.nickname
else:
return "익명"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이런 상수는 constant.py에다가 넣어서 작성해 주세요 나중에 못찾습니다!


async def notify_commented(self, comment: Comment) -> None:
"""
Notifies users when a comment is added.
"""
article = comment.parent_article if comment.parent_article else comment.parent_comment.parent_article

if comment.created_by != article.created_by:
await self._notify_article_commented(article, comment)

if comment.parent_comment and comment.created_by != comment.parent_comment.created_by:
await self._notify_comment_commented(article, comment)

async def _notify_article_commented(self, parent_article: Article, comment: Comment) -> None:
"""
Notifies the user when a comment is added to their article.
"""
name = self.get_display_name(parent_article, comment.created_by.profile)
title = f"{name} 님이 새로운 댓글을 작성했습니다."

# Save the notification
notification = Notification(
type="article_commented",
title=title,
content=comment.content[:32], # Truncate content if necessary
related_article=parent_article,
related_comment=None
)
await self.notification_repo.save(notification)

# Send push notification
await fcm_notify_comment(parent_article.created_by, title, comment.content[:32], f"post/{parent_article.id}")

async def _notify_comment_commented(self, parent_article: Article, comment: Comment) -> None:
"""
Notifies the user when a comment is added to their comment.
"""
name = self.get_display_name(parent_article, comment.created_by.profile)
title = f"{name} 님이 새로운 대댓글을 작성했습니다."

# Save the notification
notification = Notification(
type="comment_commented",
title=title,
content=comment.content[:32], # Truncate content if necessary
related_article=parent_article,
related_comment=comment.parent_comment
)
await self.notification_repo.save(notification)

# Send push notification
await fcm_notify_comment(comment.parent_comment.created_by, title, comment.content[:32], f"post/{parent_article.id}")
69 changes: 69 additions & 0 deletions ara/service/notification/notification_service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
from fastapi import HTTPException
from ara.infra.notification.notification_infra import NotificationRepository
from ara.domain.notification.notification_domain import Notification
from ara.domain.exceptions import EntityDoesNotExist
from ara.domain.article import Article, NameType
from ara.domain.user_profile import UserProfile
from ara.domain.comment import Comment

class NotificationService:
def __init__(self, notification_repo: NotificationRepository):
self.notification_repo = notification_repo

def get_display_name(self, article: Article, profile: UserProfile) -> str:

if article.name_type == NameType.REALNAME:
return profile.realname
elif article.name_type == NameType.REGULAR:
return profile.nickname
else:
return "익명"

async def notify_commented(self, comment: Comment) -> None:

article = comment.parent_article if comment.parent_article else comment.parent_comment.parent_article

if comment.created_by != article.created_by:
await self._notify_article_commented(article, comment)

if comment.parent_comment and comment.created_by != comment.parent_comment.created_by:
await self._notify_comment_commented(article, comment)

async def _notify_article_commented(self, parent_article: Article, comment: Comment) -> None:

name = self.get_display_name(parent_article, comment.created_by.profile)
title = f"{name} 님이 새로운 댓글을 작성했습니다."

notification = Notification(
id=None,
type="article_commented",
title=title,
content=comment.content[:32],
related_article=parent_article,
related_comment=None
)
await self.notification_repo.save(notification)

# Send push notification
await fcm_notify_comment(parent_article.created_by, title, comment.content[:32], f"post/{parent_article.id}")

async def _notify_comment_commented(self, parent_article: Article, comment: Comment) -> None:
"""
Notifies the user when a comment is added to their comment.
"""
name = self.get_display_name(parent_article, comment.created_by.profile)
title = f"{name} 님이 새로운 대댓글을 작성했습니다."

# Save the notification
notification = Notification(
id=None, # Since it's a new notification, let the database generate the ID
type="comment_commented",
title=title,
content=comment.content[:32], # Truncate content if necessary
related_article=parent_article,
related_comment=comment.parent_comment
)
await self.notification_repo.save(notification)

# Send push notification
await fcm_notify_comment(comment.parent_comment.created_by, title, comment.content[:32], f"post/{parent_article.id}")
Loading