mirror of
https://github.com/discourse/discourse.git
synced 2026-08-12 05:37:26 +08:00
Notification badge counts could get "stuck" — showing a number higher than the actual visible notifications. This happened because count queries used lightweight SQL (only filtering deleted topics), while display queries applied full access control via Guardian. When a user lost access to a topic — removed from a group/PM, or category permissions changed — the notification persisted in counts but was filtered from the displayed list. Additionally, the `visible` scope had a logic error: the LEFT JOIN condition `topics.id IS NULL OR topics.deleted_at IS NULL` incorrectly included notifications for hard-deleted topics. The fix introduces `NotificationQuery` (following the `BookmarkQuery` pattern) — a centralized class that handles all notification filtering at the SQL level, ensuring counts always match displayed notifications. Access control uses a CASE on topic archetype: PMs check topic_allowed_users + topic_allowed_groups; regular topics check category read_restricted against the user's secure_category_ids. Shared drafts are excluded when the user lacks access. Admins without suppress_secured_categories_from_admin skip all checks. Badge notifications are handled via Ruby post-processing when enable_badges is false. To prevent DB bloat from orphaned notifications, model callbacks enqueue DeleteInaccessibleNotifications when access is revoked: - TopicAllowedUser after_destroy — enqueues with topic_id - TopicAllowedGroup after_destroy — enqueues with topic_id - GroupUser after_destroy — enqueues with user_id + group_id - Category after_update — enqueues with category_id when permissions change The job supports three modes: topic_id (checks each user via Guardian), category_id (iterates topics, delegates to topic_id mode), and user_id + group_id (finds affected topics, bulk-deletes where user lost access). Redo of #27589 Ref - t/121464 |
||
|---|---|---|
| .. | ||
| concerns | ||
| onceoff | ||
| regular | ||
| scheduled | ||
| base.rb | ||