fix: handle follower and shout notifications in notifications_seen_thread
All checks were successful
Deploy on push / deploy (push) Successful in 3m13s
All checks were successful
Deploy on push / deploy (push) Successful in 3m13s
- Add support for marking follower notifications as seen (thread='followers') - Add support for marking new shout notifications as seen - Use enum constants (NotificationAction, NotificationEntity) instead of strings - Improve thread ID parsing to support different formats - Remove obsolete TODO about notification_id offset - Better error handling with logger.warning() instead of exceptions Resolves TODOs on lines 253 and 286 in resolvers/notifier.py
This commit is contained in:
18
CHANGELOG.md
18
CHANGELOG.md
@@ -1,5 +1,23 @@
|
||||
# Changelog
|
||||
|
||||
## [0.9.31] - 2025-10-04
|
||||
|
||||
### ✅ Fixed: Notifications TODOs
|
||||
- **Уведомления о followers**: Добавлена обработка уведомлений о подписчиках в `notifications_seen_thread`
|
||||
- Теперь при клике на группу "followers" все уведомления о подписках помечаются как прочитанные
|
||||
- Исправлена обработка thread ID `"followers"` отдельно от shout/reaction threads
|
||||
- **Уведомления о новых публикациях**: Добавлена обработка уведомлений о новых shouts в `notifications_seen_thread`
|
||||
- При открытии публикации уведомления о ней тоже помечаются как прочитанные
|
||||
- Исправлена логика парсинга thread ID для поддержки разных форматов
|
||||
- **Code Quality**: Использованы enum константы (`NotificationAction`, `NotificationEntity`) вместо строк
|
||||
- **Убраны устаревшие TODO**: Удален TODO про `notification_id` как offset (текущая логика с timestamp работает корректно)
|
||||
|
||||
### Technical Details
|
||||
- `core/resolvers/notifier.py`: расширена функция `notifications_seen_thread` для поддержки всех типов уведомлений
|
||||
- Добавлена обработка `thread == "followers"` для уведомлений о подписках
|
||||
- Добавлена обработка `NotificationEntity.SHOUT` для уведомлений о новых публикациях
|
||||
- Улучшена обработка ошибок с `logger.warning()` вместо исключений
|
||||
|
||||
## [0.9.30] - 2025-10-02
|
||||
|
||||
### 🔧 Fixed
|
||||
|
||||
@@ -50,10 +50,10 @@ def get_entity_field_name(entity_type: str) -> str:
|
||||
ValueError: Unknown entity_type: invalid
|
||||
"""
|
||||
entity_field_mapping = {
|
||||
"author": "following", # AuthorFollower.following -> Author
|
||||
"topic": "topic", # TopicFollower.topic -> Topic
|
||||
"author": "following", # AuthorFollower.following -> Author
|
||||
"topic": "topic", # TopicFollower.topic -> Topic
|
||||
"community": "community", # CommunityFollower.community -> Community
|
||||
"shout": "shout" # ShoutReactionsFollower.shout -> Shout
|
||||
"shout": "shout", # ShoutReactionsFollower.shout -> Shout
|
||||
}
|
||||
if entity_type not in entity_field_mapping:
|
||||
msg = f"Unknown entity_type: {entity_type}"
|
||||
|
||||
@@ -120,7 +120,7 @@ def get_notifications_grouped(author_id: int, after: int = 0, limit: int = 10, o
|
||||
shout_id = shout.get("id")
|
||||
author_id = shout.get("created_by")
|
||||
thread_id = f"shout-{shout_id}"
|
||||
|
||||
|
||||
with local_session() as session:
|
||||
author = session.query(Author).where(Author.id == author_id).first()
|
||||
shout = session.query(Shout).where(Shout.id == shout_id).first()
|
||||
@@ -155,7 +155,7 @@ def get_notifications_grouped(author_id: int, after: int = 0, limit: int = 10, o
|
||||
thread_id = f"shout-{shout_id}"
|
||||
if reply_id and reaction.get("kind", "").lower() == "comment":
|
||||
thread_id = f"shout-{shout_id}::{reply_id}"
|
||||
|
||||
|
||||
existing_group = groups_by_thread.get(thread_id)
|
||||
if existing_group:
|
||||
existing_group["seen"] = False
|
||||
@@ -215,7 +215,7 @@ async def load_notifications(_: None, info: GraphQLResolveInfo, after: int, limi
|
||||
if author_id:
|
||||
groups_list = get_notifications_grouped(author_id, after, limit)
|
||||
notifications = sorted(groups_list, key=lambda group: group.get("updated_at", 0), reverse=True)
|
||||
|
||||
|
||||
# Считаем реальное количество сгруппированных уведомлений
|
||||
total = len(notifications)
|
||||
unread = sum(1 for n in notifications if not n.get("seen", False))
|
||||
@@ -250,7 +250,7 @@ async def notification_mark_seen(_: None, info: GraphQLResolveInfo, notification
|
||||
@mutation.field("notifications_seen_after")
|
||||
@login_required
|
||||
async def notifications_seen_after(_: None, info: GraphQLResolveInfo, after: int) -> dict:
|
||||
# TODO: use latest loaded notification_id as input offset parameter
|
||||
"""Mark all notifications after given timestamp as seen."""
|
||||
error = None
|
||||
try:
|
||||
author_id = info.context.get("author", {}).get("id")
|
||||
@@ -278,18 +278,64 @@ async def notifications_seen_thread(_: None, info: GraphQLResolveInfo, thread: s
|
||||
error = None
|
||||
author_id = info.context.get("author", {}).get("id")
|
||||
if author_id:
|
||||
[shout_id, reply_to_id] = thread.split(":")
|
||||
with local_session() as session:
|
||||
# Convert Unix timestamp to datetime for PostgreSQL compatibility
|
||||
after_datetime = datetime.fromtimestamp(after, tz=UTC) if after else None
|
||||
|
||||
# TODO: handle new follower and new shout notifications
|
||||
# Handle different thread types: shout reactions, followers, or new shouts
|
||||
if thread == "followers":
|
||||
# Mark follower notifications as seen
|
||||
query_conditions = [
|
||||
Notification.entity == NotificationEntity.FOLLOWER.value,
|
||||
]
|
||||
if after_datetime:
|
||||
query_conditions.append(Notification.created_at > after_datetime)
|
||||
|
||||
follower_notifications = session.query(Notification).where(and_(*query_conditions)).all()
|
||||
for n in follower_notifications:
|
||||
try:
|
||||
ns = NotificationSeen(notification=n.id, viewer=author_id)
|
||||
session.add(ns)
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to mark follower notification as seen: {e}")
|
||||
session.commit()
|
||||
return {"error": None}
|
||||
|
||||
# Handle shout and reaction notifications
|
||||
thread_parts = thread.split(":")
|
||||
if len(thread_parts) < 2:
|
||||
return {"error": "Invalid thread format"}
|
||||
|
||||
shout_id = thread_parts[0]
|
||||
reply_to_id = thread_parts[1] if len(thread_parts) > 1 else None
|
||||
|
||||
# Query for new shout notifications in this thread
|
||||
shout_query_conditions = [
|
||||
Notification.entity == NotificationEntity.SHOUT.value,
|
||||
Notification.action == NotificationAction.CREATE.value,
|
||||
]
|
||||
if after_datetime:
|
||||
shout_query_conditions.append(Notification.created_at > after_datetime)
|
||||
|
||||
shout_notifications = session.query(Notification).where(and_(*shout_query_conditions)).all()
|
||||
|
||||
# Mark relevant shout notifications as seen
|
||||
for n in shout_notifications:
|
||||
payload = orjson.loads(str(n.payload))
|
||||
if str(payload.get("id")) == shout_id:
|
||||
try:
|
||||
ns = NotificationSeen(notification=n.id, viewer=author_id)
|
||||
session.add(ns)
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to mark shout notification as seen: {e}")
|
||||
|
||||
# Query for reaction notifications
|
||||
if after_datetime:
|
||||
new_reaction_notifications = (
|
||||
session.query(Notification)
|
||||
.where(
|
||||
Notification.action == "create",
|
||||
Notification.entity == "reaction",
|
||||
Notification.action == NotificationAction.CREATE.value,
|
||||
Notification.entity == NotificationEntity.REACTION.value,
|
||||
Notification.created_at > after_datetime,
|
||||
)
|
||||
.all()
|
||||
@@ -297,8 +343,8 @@ async def notifications_seen_thread(_: None, info: GraphQLResolveInfo, thread: s
|
||||
removed_reaction_notifications = (
|
||||
session.query(Notification)
|
||||
.where(
|
||||
Notification.action == "delete",
|
||||
Notification.entity == "reaction",
|
||||
Notification.action == NotificationAction.DELETE.value,
|
||||
Notification.entity == NotificationEntity.REACTION.value,
|
||||
Notification.created_at > after_datetime,
|
||||
)
|
||||
.all()
|
||||
@@ -307,16 +353,16 @@ async def notifications_seen_thread(_: None, info: GraphQLResolveInfo, thread: s
|
||||
new_reaction_notifications = (
|
||||
session.query(Notification)
|
||||
.where(
|
||||
Notification.action == "create",
|
||||
Notification.entity == "reaction",
|
||||
Notification.action == NotificationAction.CREATE.value,
|
||||
Notification.entity == NotificationEntity.REACTION.value,
|
||||
)
|
||||
.all()
|
||||
)
|
||||
removed_reaction_notifications = (
|
||||
session.query(Notification)
|
||||
.where(
|
||||
Notification.action == "delete",
|
||||
Notification.entity == "reaction",
|
||||
Notification.action == NotificationAction.DELETE.value,
|
||||
Notification.entity == NotificationEntity.REACTION.value,
|
||||
)
|
||||
.all()
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user