Skip to content

feat(notifications): comment, mention, thread, reaction, fan_club_text_post triggers#851

Open
raymondjacobson wants to merge 1 commit into
mainfrom
api/comment-notifications
Open

feat(notifications): comment, mention, thread, reaction, fan_club_text_post triggers#851
raymondjacobson wants to merge 1 commit into
mainfrom
api/comment-notifications

Conversation

@raymondjacobson
Copy link
Copy Markdown
Member

Summary

Adds the five comment-related notification triggers that apps' src/tasks/entity_manager/entities/comment.py creates directly during ManageEntity processing. The ETL handlers in go-openaudio/pkg/etl/processors/entity_manager/comment_*.go already write the source rows (comments, comment_mentions, comment_threads, comment_reactions) — but the user-facing notification rows had no Go equivalent. handle_comment.sql here previously only updated aggregate_track.comment_count.

Closes the largest remaining notification gap on the path to shutting off the Python discovery indexer (per the broader parity audit).

Triggers (one notification type per file)

File Notification type Source table Pattern
handle_comment_notification.sql comment comments INSERT Deferred (needs comment_threads + comment_mentions)
handle_comment_mention.sql comment_mention comment_mentions INSERT Plain AFTER
handle_comment_thread.sql comment_thread comment_threads INSERT Plain AFTER
handle_comment_reaction.sql comment_reaction comment_reactions INSERT Plain AFTER
handle_fan_club_text_post.sql fan_club_text_post comments INSERT (FanClub) Deferred (needs comment_threads)

Each follows the established repo pattern (one file per trigger, sibling of handle_comment_remix_contest_update.sql). All use ON CONFLICT DO NOTHING against notification.uq_notification(group_id, specifier) for dedup. Notification shape — specifier, group_id, data payload — matches apps verbatim so existing readers/clients keep working.

Why DEFERRABLE INITIALLY DEFERRED

`handle_comment_notification` and `handle_fan_club_text_post` need to look at `comment_threads` (to detect replies) and `comment_mentions` (to detect owner mentions). Those sibling rows are inserted after the `comments` row in the same indexer transaction. Same problem and same fix as `handle_comment_remix_contest_update.sql`.

Intentional gap: karma-based mute

Apps drops the notification when SUM(follower_count) of users who muted the commenter exceeds a configured threshold (1.7M prod, 4k dev). Not ported here — keeps triggers localized and the threshold lives in apps' config, not the DB. Worst-case impact is "spammy commenters notify a few more users than they would have under apps." If that becomes a real noise problem we fold it in.

Skip semantics (mirror apps)

  • comment — skips self-comment, replies (they get comment_thread), owner-mentioned (they get comment_mention), comment_notification_settings.is_muted, muted_users
  • comment_mention — skips self-mention, mention-muted-commenter, owner-mentioned-with-owner-mute
  • comment_thread — skips self-reply, parent author muted thread or replier
  • comment_reaction — skips self-react, comment-author mute, plus apps' track_owner_mention_mute when commenter is entity owner
  • fan_club_text_post — only artist's own top-level posts fan out; recipients = followers ∪ artist-coin holders, excluding artist

Schema dump

Regeneration follows in a separate commit, matching 4da78ab for handle_comment_remix_contest_update.

Test plan

9 DB-backed tests against the test_api template, all passing locally:

  • `TestCommentNotification_NotifiesTrackOwner` — happy path; correct group_id + payload
  • `TestCommentNotification_SkipsSelfComment` — self-comment no-op
  • `TestCommentNotification_SkipsReply` — comment + comment_threads in the same tx → deferred trigger correctly skips
  • `TestCommentMention_NotifiesMentionedUser` — mentioned user gets `comment_mention` with full payload
  • `TestCommentMention_SkipsWhenMentionedMutedCommenter` — `muted_users` gate
  • `TestCommentThread_NotifiesParentAuthor` — parent author gets `comment_thread`, specifier = reply comment_id
  • `TestCommentReaction_NotifiesCommentAuthor` — author gets `comment_reaction`, specifier = reacter user_id
  • `TestFanClubTextPost_FansOutToFollowersAndCoinHolders` — followers ∪ coin holders, deduped via UNION; artist excluded
  • `TestFanClubTextPost_SkipsFanComments` — only artist posts fan out

`go build ./...` and `go vet ./api/...` clean.

🤖 Generated with Claude Code

…t_post triggers

Adds the five comment-related notification triggers that apps'
src/tasks/entity_manager/entities/comment.py creates directly from
Python during ManageEntity processing. The ETL handlers in
go-openaudio (pkg/etl/processors/entity_manager/comment_*.go) already
write the source rows — comments, comment_mentions, comment_threads,
comment_reactions — but the user-facing notifications had no Go
equivalent. handle_comment.sql here previously only updated
aggregate_track.comment_count.

Closes the largest remaining notification gap on the path to shutting
off the Python discovery indexer.

New trigger files (one notification type each, all on the same
table-per-trigger pattern as handle_comment_remix_contest_update.sql):

  handle_comment_notification.sql   → `comment`
    Fires on comments INSERT, deferred. Notifies entity owner (track
    owner / event host / fan-club artist) of a new top-level comment.
    Skips: self-comment, replies (comment_threads exists), owner-
    mentioned (comment_mentions for owner — they get comment_mention
    instead), and comment_notification_settings / muted_users mutes.

  handle_comment_mention.sql        → `comment_mention`
    Fires on comment_mentions INSERT. Notifies the mentioned user.
    Skips: self-mention, mention has muted the commenter, owner is
    mentioned AND owner muted notifications on the entity.

  handle_comment_thread.sql         → `comment_thread`
    Fires on comment_threads INSERT. Notifies the parent comment
    author. Skips: self-reply, parent author muted the thread or
    the replier.

  handle_comment_reaction.sql       → `comment_reaction`
    Fires on comment_reactions INSERT. Notifies the comment author.
    Skips: self-react, comment author muted notifications on the
    comment or the reacter, plus apps' track_owner_mention_mute
    when the commenter is the entity owner.

  handle_fan_club_text_post.sql     → `fan_club_text_post`
    Fires on comments INSERT (FanClub entity_type), deferred. Fans
    out to (followers ∪ artist-coin holders) − {artist}. One row per
    recipient with specifier=recipient_id (unique constraint on
    (group_id, specifier) dedupes).

Why DEFERRABLE INITIALLY DEFERRED on the comments INSERT triggers:
"Top-level" is determined by the absence of comment_threads for this
comment_id, and "owner is mentioned" by the presence of a
comment_mentions row. Both sibling rows are inserted AFTER the comments
row in the same indexer transaction. Same pattern as
handle_comment_remix_contest_update.sql.

Intentionally deferred (matches apps but not ported here): the karma-
based mute check that drops a notification when SUM(follower_count) of
users who muted the commenter exceeds a threshold (1.7M prod, 4k dev).
Keeps the triggers localized; the threshold lives in apps' config not
the DB. If notification noise becomes a problem we can fold it in.

Notification payload shapes match apps verbatim (specifier, group_id,
data) so existing notification readers / clients keep working.

Schema dump regeneration follows in a separate commit (cf. 4da78ab
for the handle_comment_remix_contest_update precedent).

Tests (api/v1_comment_notifications_test.go — 9 tests, all DB-backed):
- TestCommentNotification_NotifiesTrackOwner — happy path: track owner
  receives `comment` with the correct group_id and payload
- TestCommentNotification_SkipsSelfComment — self-comment no-op
- TestCommentNotification_SkipsReply — reply inserted with comment_threads
  in the same tx → deferred trigger correctly skips
- TestCommentMention_NotifiesMentionedUser — mentioned user gets
  `comment_mention` with entity_user_id / comment_user_id
- TestCommentMention_SkipsWhenMentionedMutedCommenter — muted_users gates
- TestCommentThread_NotifiesParentAuthor — parent author gets
  `comment_thread`, specifier = reply comment_id
- TestCommentReaction_NotifiesCommentAuthor — author gets
  `comment_reaction`, specifier = reacter user_id
- TestFanClubTextPost_FansOutToFollowersAndCoinHolders — recipients =
  follower ∪ coin holder, deduped via UNION; artist excluded
- TestFanClubTextPost_SkipsFanComments — only artist's own top-level
  posts trigger fan-out

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant