-
Notifications
You must be signed in to change notification settings - Fork 1
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 | ||
|
||
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"} |
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"} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
class EntityDoesNotExist(Exception): | ||
pass |
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 |
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() |
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 |
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 |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,24 @@ | ||||||
from fastapi import HTTPException | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 해당 파일 도 경로가 틀렸습니다 제가 올린 board refactoring PR 봐주세요 |
||||||
from sqlalchemy.orm import Session | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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: | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
함수이름은 파라미터를 보지 않아도 딱봐도 알 수 있게 작성하는게 좋아요 👍 |
||||||
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]: | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
notifications = self.db.query(Notification).filter(Notification.user_id == user.id).all() | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ORM 사용해야 합니다.. |
||||||
return notifications | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 파일 끝에 엔터로 끝내주세요!! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. EditorConfig가 돌고 있을 텐데 이상하네요 👀 |
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}") | ||
|
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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 "익명" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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}") |
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}") |
There was a problem hiding this comment.
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
로 빼주세요