feat(notifications): comment, mention, thread, reaction, fan_club_text_post triggers#851
Open
raymondjacobson wants to merge 1 commit into
Open
feat(notifications): comment, mention, thread, reaction, fan_club_text_post triggers#851raymondjacobson wants to merge 1 commit into
raymondjacobson wants to merge 1 commit into
Conversation
…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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds the five comment-related notification triggers that apps'
src/tasks/entity_manager/entities/comment.pycreates directly during ManageEntity processing. The ETL handlers ingo-openaudio/pkg/etl/processors/entity_manager/comment_*.goalready write the source rows (comments,comment_mentions,comment_threads,comment_reactions) — but the user-facing notification rows had no Go equivalent.handle_comment.sqlhere previously only updatedaggregate_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)
handle_comment_notification.sqlcommentcommentsINSERThandle_comment_mention.sqlcomment_mentioncomment_mentionsINSERThandle_comment_thread.sqlcomment_threadcomment_threadsINSERThandle_comment_reaction.sqlcomment_reactioncomment_reactionsINSERThandle_fan_club_text_post.sqlfan_club_text_postcommentsINSERT (FanClub)Each follows the established repo pattern (one file per trigger, sibling of
handle_comment_remix_contest_update.sql). All useON CONFLICT DO NOTHINGagainstnotification.uq_notification(group_id, specifier)for dedup. Notification shape —specifier,group_id,datapayload — 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 getcomment_thread), owner-mentioned (they getcomment_mention),comment_notification_settings.is_muted,muted_userscomment_mention— skips self-mention, mention-muted-commenter, owner-mentioned-with-owner-mutecomment_thread— skips self-reply, parent author muted thread or repliercomment_reaction— skips self-react, comment-author mute, plus apps'track_owner_mention_mutewhen commenter is entity ownerfan_club_text_post— only artist's own top-level posts fan out; recipients = followers ∪ artist-coin holders, excluding artistSchema dump
Regeneration follows in a separate commit, matching 4da78ab for
handle_comment_remix_contest_update.Test plan
9 DB-backed tests against the
test_apitemplate, all passing locally:`go build ./...` and `go vet ./api/...` clean.
🤖 Generated with Claude Code