From a1d55e14a2eff2f529ad3b95f16a8a26faf7a178 Mon Sep 17 00:00:00 2001 From: Raymond Jacobson Date: Mon, 29 Jun 2026 11:13:59 -0700 Subject: [PATCH 1/2] Use entity_id for event subscription reads --- api/v1_events_followers.go | 11 +++++------ api/v1_events_followers_test.go | 16 +++++++++++++++- .../handle_comment_remix_contest_update.sql | 2 +- ddl/functions/handle_event.sql | 2 +- ddl/functions/handle_track.sql | 4 ++-- jobs/create_remix_contest_notifications.go | 4 ++-- jobs/create_remix_contest_notifications_test.go | 2 +- sql/01_schema.sql | 8 ++++---- 8 files changed, 31 insertions(+), 18 deletions(-) diff --git a/api/v1_events_followers.go b/api/v1_events_followers.go index 22cfdebb..e32ba6df 100644 --- a/api/v1_events_followers.go +++ b/api/v1_events_followers.go @@ -121,10 +121,9 @@ func (app *ApiServer) v1EventsFollowers(c *fiber.Ctx) error { return fiber.NewError(fiber.StatusBadRequest, "invalid event id") } - // `subscriptions.user_id` mirrors the event id (legacy column), so the - // `USING (user_id)` shortcut from v1UsersFollowers would be ambiguous - // here — qualify both joins explicitly against the user being looked - // up (subscriber_id). + // `subscriptions.user_id` is only the legacy event-id mirror for Event + // subscriptions, so qualify joins against the real user being looked up + // (subscriber_id) and key the event lookup by entity_id. sql := ` SELECT subscriptions.subscriber_id FROM subscriptions @@ -158,7 +157,7 @@ func (app *ApiServer) v1EventFollowState(c *fiber.Ctx) error { SELECT COUNT(*) FROM subscriptions WHERE entity_type = 'Event' - AND user_id = $1 + AND entity_id = $1 AND is_current = true AND is_delete = false `, eventID).Scan(&followerCount); err != nil { @@ -173,7 +172,7 @@ func (app *ApiServer) v1EventFollowState(c *fiber.Ctx) error { SELECT EXISTS ( SELECT 1 FROM subscriptions WHERE entity_type = 'Event' - AND user_id = $1 + AND entity_id = $1 AND subscriber_id = $2 AND is_current = true AND is_delete = false diff --git a/api/v1_events_followers_test.go b/api/v1_events_followers_test.go index 80931071..6f5e937f 100644 --- a/api/v1_events_followers_test.go +++ b/api/v1_events_followers_test.go @@ -116,10 +116,11 @@ func TestEventFollowState_ZeroFollowers(t *testing.T) { func TestEventFollowState_CountsOnlyLiveEventSubscriptions(t *testing.T) { // follower_count MUST: - // - count rows with entity_type='Event' and matching user_id=event_id + // - count rows with entity_type='Event' and matching entity_id=event_id // - only include current & non-deleted rows // - NOT count rows with entity_type='User' (legacy user-follows) even if // they happen to target the same numeric id + // - NOT count Event rows where only the legacy user_id mirror matches // - NOT count stale (is_current=false) rows app := emptyTestApp(t) @@ -194,6 +195,19 @@ func TestEventFollowState_CountsOnlyLiveEventSubscriptions(t *testing.T) { "blocknumber": 101, "txhash": "tx4", }, + // An Event subscription whose legacy mirror matches this event + // but canonical entity_id points elsewhere — must NOT be counted. + { + "subscriber_id": 4, + "user_id": 200, + "entity_type": "Event", + "entity_id": 999, + "is_current": true, + "is_delete": false, + "blockhash": "bh5", + "blocknumber": 101, + "txhash": "tx5", + }, }, }) diff --git a/ddl/functions/handle_comment_remix_contest_update.sql b/ddl/functions/handle_comment_remix_contest_update.sql index 46ca6a3f..e67ea795 100644 --- a/ddl/functions/handle_comment_remix_contest_update.sql +++ b/ddl/functions/handle_comment_remix_contest_update.sql @@ -67,7 +67,7 @@ begin select s.subscriber_id from subscriptions s where s.entity_type = 'Event' - and s.user_id = new.entity_id + and s.entity_id = new.entity_id and s.is_current = true and s.is_delete = false and s.subscriber_id <> event_host_id diff --git a/ddl/functions/handle_event.sql b/ddl/functions/handle_event.sql index 351a0a99..229ae0d5 100644 --- a/ddl/functions/handle_event.sql +++ b/ddl/functions/handle_event.sql @@ -36,7 +36,7 @@ begin select sub.subscriber_id as user_id from subscriptions sub where sub.entity_type = 'Event' - and sub.user_id = new.event_id + and sub.entity_id = new.event_id and sub.is_current = true and sub.is_delete = false ) as users_to_notify diff --git a/ddl/functions/handle_track.sql b/ddl/functions/handle_track.sql index c0ec2fbe..32b54790 100644 --- a/ddl/functions/handle_track.sql +++ b/ddl/functions/handle_track.sql @@ -167,7 +167,7 @@ begin select s.subscriber_id as user_id from subscriptions s where s.entity_type = 'Event' - and s.user_id = contest_event_id + and s.entity_id = contest_event_id and s.is_current = true and s.is_delete = false union @@ -239,7 +239,7 @@ begin select sub.subscriber_id as user_id from subscriptions sub where sub.entity_type = 'Event' - and sub.user_id = e.event_id + and sub.entity_id = e.event_id and sub.is_current = true and sub.is_delete = false ) u on true diff --git a/jobs/create_remix_contest_notifications.go b/jobs/create_remix_contest_notifications.go index f4175a41..ee4bce97 100644 --- a/jobs/create_remix_contest_notifications.go +++ b/jobs/create_remix_contest_notifications.go @@ -152,7 +152,7 @@ func (j *RemixContestNotificationsJob) fanEnded(ctx context.Context, now time.Ti AND t.remix_of->'tracks' @> jsonb_build_array(jsonb_build_object('parent_track_id', e.entity_id)) UNION SELECT s.subscriber_id FROM subscriptions s - WHERE s.user_id = e.event_id AND s.entity_type = 'Event' + WHERE s.entity_id = e.event_id AND s.entity_type = 'Event' AND s.is_current AND NOT s.is_delete UNION SELECT f.follower_user_id FROM follows f @@ -203,7 +203,7 @@ func (j *RemixContestNotificationsJob) fanEndingSoon(ctx context.Context, now ti AND sv.is_current AND NOT sv.is_delete UNION SELECT s.subscriber_id FROM subscriptions s - WHERE s.user_id = e.event_id AND s.entity_type = 'Event' + WHERE s.entity_id = e.event_id AND s.entity_type = 'Event' AND s.is_current AND NOT s.is_delete ) aud WHERE e.event_type = 'remix_contest' AND NOT e.is_deleted diff --git a/jobs/create_remix_contest_notifications_test.go b/jobs/create_remix_contest_notifications_test.go index 254828ba..1a4a0e12 100644 --- a/jobs/create_remix_contest_notifications_test.go +++ b/jobs/create_remix_contest_notifications_test.go @@ -120,7 +120,7 @@ func seedRemixContest(t *testing.T, pool *pgxpool.Pool, endDate time.Time) { {"user_id": 4, "save_item_id": 1000, "save_type": "track"}, }, "subscriptions": { - {"subscriber_id": 5, "user_id": 500, "entity_type": "Event", "entity_id": 1000}, + {"subscriber_id": 5, "user_id": 500, "entity_type": "Event", "entity_id": 500}, }, }) } diff --git a/sql/01_schema.sql b/sql/01_schema.sql index c701ea6f..15da93ad 100644 --- a/sql/01_schema.sql +++ b/sql/01_schema.sql @@ -2783,7 +2783,7 @@ begin select s.subscriber_id from subscriptions s where s.entity_type = 'Event' - and s.user_id = new.entity_id + and s.entity_id = new.entity_id and s.is_current = true and s.is_delete = false and s.subscriber_id <> event_host_id @@ -3052,7 +3052,7 @@ begin select sub.subscriber_id as user_id from subscriptions sub where sub.entity_type = 'Event' - and sub.user_id = new.event_id + and sub.entity_id = new.event_id and sub.is_current = true and sub.is_delete = false ) as users_to_notify @@ -4991,7 +4991,7 @@ begin select s.subscriber_id as user_id from subscriptions s where s.entity_type = 'Event' - and s.user_id = contest_event_id + and s.entity_id = contest_event_id and s.is_current = true and s.is_delete = false union @@ -5063,7 +5063,7 @@ begin select sub.subscriber_id as user_id from subscriptions sub where sub.entity_type = 'Event' - and sub.user_id = e.event_id + and sub.entity_id = e.event_id and sub.is_current = true and sub.is_delete = false ) u on true From dbb5a58aa2d7e216228f4e40486e09a63ed66b54 Mon Sep 17 00:00:00 2001 From: Raymond Jacobson Date: Mon, 29 Jun 2026 11:34:53 -0700 Subject: [PATCH 2/2] Bump go-openaudio for event subscriptions --- go.mod | 4 ++-- go.sum | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 34f2cb9c..861eb9bb 100644 --- a/go.mod +++ b/go.mod @@ -6,8 +6,8 @@ require ( connectrpc.com/connect v1.18.1 github.com/AlecAivazis/survey/v2 v2.3.7 github.com/Doist/unfurlist v0.0.0-20250409100812-515f2735f8e5 - github.com/OpenAudio/go-openaudio v1.4.1-0.20260618012656-5aa118b67bc1 - github.com/OpenAudio/go-openaudio/pkg/etl v1.4.1-0.20260618012656-5aa118b67bc1 + github.com/OpenAudio/go-openaudio v1.5.1-0.20260629182311-eb976ab7d10a + github.com/OpenAudio/go-openaudio/pkg/etl v1.5.1-0.20260629182311-eb976ab7d10a github.com/aquasecurity/esquery v0.2.0 github.com/axiomhq/axiom-go v0.23.0 github.com/axiomhq/hyperloglog v0.2.5 diff --git a/go.sum b/go.sum index 568f49f8..3070d3f0 100644 --- a/go.sum +++ b/go.sum @@ -22,8 +22,12 @@ github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEV github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= github.com/OpenAudio/go-openaudio v1.4.1-0.20260618012656-5aa118b67bc1 h1:nfnIHX+rbMcm1Xdr4AkfinF5ms/RQVKXtV/3S5gqO3w= github.com/OpenAudio/go-openaudio v1.4.1-0.20260618012656-5aa118b67bc1/go.mod h1:wiFXmVbIUkN2D5lRshknaARCKhzbHtCBKRCZe6UOnVs= +github.com/OpenAudio/go-openaudio v1.5.1-0.20260629182311-eb976ab7d10a h1:Rmk8XrfEwdmDnISU87CnaRjv0u7c8aKJRNmR0gAVvWg= +github.com/OpenAudio/go-openaudio v1.5.1-0.20260629182311-eb976ab7d10a/go.mod h1:lLRvUF5oWkxOyZx8rp/ecqxuMo3yzPvuvJbLSfvxguQ= github.com/OpenAudio/go-openaudio/pkg/etl v1.4.1-0.20260618012656-5aa118b67bc1 h1:DeKyFpz5BFelTSFRqSnu58RfDDwn38BCtYfBsoUg3gg= github.com/OpenAudio/go-openaudio/pkg/etl v1.4.1-0.20260618012656-5aa118b67bc1/go.mod h1:qOtZr4+Xkp/Zp+yM4axDcKdYF03+xA98PjhwjFQMGvg= +github.com/OpenAudio/go-openaudio/pkg/etl v1.5.1-0.20260629182311-eb976ab7d10a h1:brZAgN0X5AWXgnoXywusHFCQ28H3lxIDNw4llsY89JQ= +github.com/OpenAudio/go-openaudio/pkg/etl v1.5.1-0.20260629182311-eb976ab7d10a/go.mod h1:GOtIlg6gDySPB0ekNVP1SPrlky6EfIigP2MGZ9g+ih4= github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA= github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8= github.com/VictoriaMetrics/fastcache v1.12.2 h1:N0y9ASrJ0F6h0QaC3o6uJb3NIZ9VKLjCM7NQbSmF7WI=