fix(notifications): personalize embedded users in related response#14366
Merged
Conversation
On initial app load the current user's follow relationship to other accounts (e.g. @audius) renders as not-following in lists hydrated from notification responses. Visiting that profile directly fixes the state. The bug: the /notifications/{user_id} endpoint embeds users in the related field, and the backend personalizes them via getMyId(c) which reads ?user_id= from the query string, not the URL path. The SDK was only sending the id in the path, so MyID resolved to 0 and the SQL short-circuited does_current_user_follow to false for every embedded user. The tan-query cache primed those entries on a cold notifications fetch and sticky-cached the wrong follow state until a personalized profile fetch overwrote them. The accompanying api PR adds an optional user_id query param to both /notifications/{user_id} and /notifications/{user_id}/playlist_updates. This change: - Regenerates @audius/sdk from the updated spec. The openapi generator renamed the new query param to `userId2` to avoid colliding with the path's `userId`. Wire format is correct (`queryParameters['user_id']`), the field name is just awkward — see PR description for follow-up options. - Threads the current user id through both call sites (useNotifications.ts, usePlaylistUpdates.ts) as `userId2` so the backend can compute personalization for related.users. Path and query carry the same value in the normal flow; they may diverge when a manager reads a managed user's notifications. The UsersApi.ts diff is incidental drift: getUserForYouFeed was removed upstream and our generated copy was stale. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
🦋 Changeset detectedLatest commit: 77d303f The changes in this PR will be included in the next version bump. This PR includes changesets to release 4 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
3 tasks
raymondjacobson
added a commit
to AudiusProject/api
that referenced
this pull request
May 20, 2026
) ## Summary Rename the OpenAPI path placeholder on the two notifications routes from `{user_id}` to `{id}`, matching the convention used by every `/users/{id}/...` route. Concrete URL is unchanged. - `/notifications/{user_id}` → `/notifications/{id}` - `/notifications/{user_id}/playlist_updates` → `/notifications/{id}/playlist_updates` ## Why Follow-up to #837, which added a `user_id` query parameter to both routes for personalization of embedded `related.users`. With the path placeholder *also* named `user_id`, `openapi-generator` (typescript-fetch) emitted a colliding TS field (`userId2`) in the generated `GetNotificationsRequest` / `GetPlaylistUpdatesRequest`. The wire format was correct but the public SDK type surface was awkward. Renaming the path placeholder to `id` lets the generator produce a clean shape: ```ts GetNotificationsRequest { id: string; userId?: string; ... } ``` The Fiber router matches `/notifications/:userId` by position, not by name, so no Go change is required and the concrete URL clients send is unchanged. ## Breaking change This **is** a breaking change for external `@audius/sdk` consumers of `getNotifications` / `getPlaylistUpdates`: the path field renames from `userId` to `id`. Server behaviour, wire format, and URL paths are unchanged. In-monorepo call sites will be updated in the corresponding apps PR ([AudiusProject/apps#14366](AudiusProject/apps#14366)) after this merges. ## Test plan - [x] Spec validates in CI. - [x] Hitting `/notifications/<hashed-id>?user_id=<hashed-id>` post-deploy returns the same response as before. - [x] Regenerating the apps SDK against the merged spec produces a clean `GetNotificationsRequest` with `id` (path) and `userId` (query), no `userId2`. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Contributor
🌐 Web preview readyPreview URL: https://audius-web-preview-pr-14366.audius.workers.dev Unique preview for this PR (deployed from this branch). |
Picks up AudiusProject/api#838, which renamed the OpenAPI path placeholder on /notifications/{user_id} and /notifications/{user_id}/playlist_updates to /notifications/{id} and /notifications/{id}/playlist_updates. With the collision against the query param gone, the generator now emits clean shapes: GetNotificationsRequest { id: string; userId?: string; ... } GetPlaylistUpdatesRequest { id: string; userId?: string } Update the three call sites to use { id, userId } instead of the previous awkward { userId, userId2 }. Concrete URL is unchanged. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
3 tasks
raymondjacobson
added a commit
that referenced
this pull request
May 20, 2026
…14367) ## Summary Three more hooks were priming un-personalized users into the shared user cache — same bug class as the notifications fix in [#14366](#14366). API spec change merged in [AudiusProject/api#839](AudiusProject/api#839); this PR consumes it. | Hook | SDK call | Prime | | --- | --- | --- | | `useAllRemixContests` | `events.getRemixContests` | `primeRelatedData` | | `useUserRemixContests` | `users.getContestsByUser` | `primeRelatedData` | | `useNewAlbumReleases` | `playlists.getPlaylistsNewReleases` | `primeCollectionData` → `primeUserData` | In every case the backend handler already calls `app.getMyId(c)` to feed `MyID` into the `Parallel` query that hydrates embedded users — the query param just wasn't being sent because the SDK request type didn't expose it. With no value, `MyID = 0` and the SQL [short-circuited](https://github.com/AudiusProject/api/blob/main/api/dbv1/get_users.sql.go#L129-L136) `does_current_user_follow` to false for every embedded user. Clients then primed those into a shared cache that other surfaces read. ## Changes | File | Change | | --- | --- | | `packages/sdk/src/sdk/api/generated/default/apis/EventsApi.ts` | Add `userId?: string` to `GetRemixContestsRequest`; map to `queryParameters['user_id']`. | | `packages/sdk/src/sdk/api/generated/default/apis/UsersApi.ts` | Add `userId?: string` to `GetContestsByUserRequest`; map to `queryParameters['user_id']`. Path field is still `id` (the contest host); query `userId` is the requester. | | `packages/sdk/src/sdk/api/generated/default/apis/PlaylistsApi.ts` | Add `userId?: string` to `GetPlaylistsNewReleasesRequest`; map to `queryParameters['user_id']`. | | `packages/common/src/api/tan-query/events/useAllRemixContests.ts` | Pull `currentUserId` via `useCurrentUserId`; pass as `userId`. | | `packages/common/src/api/tan-query/events/useUserRemixContests.ts` | Same. Note this hook already took a `userId` prop for the contest host — that maps to the path `id`. `currentUserId` is separately threaded as the query. | | `packages/common/src/api/tan-query/collection/useNewAlbumReleases.ts` | Pull `currentUserId`; pass as `userId`. | | `.changeset/personalization-three-endpoints.md` | Minor `@audius/sdk` bump for the new optional fields. | ## Note on the SDK files in this diff I edited the generated `*.ts` files by hand here because `npm install` in my environment was blocked by a date-pinned registry constraint, which broke `node_modules` and stopped both `gen.js` and the rollup build from running. The edits are deterministic — they replicate the exact pattern openapi-generator already produces for analogous endpoints (e.g. `GetTrendingPlaylistsRequest` in the same `PlaylistsApi.ts`). A fresh `npm run gen` on a clean checkout should produce the same diff. Worth a quick visual confirmation during review. ## Test plan - [ ] CI green (typecheck/lint/tests). - [ ] On a fresh app load with a signed-in account: explore → contests carousel, an artist's contests tab, and browse → new albums all show correct Following state for surfaced artists without first having to navigate to each profile. - [ ] Network inspector on each of the three endpoints carries `?user_id=<hashed-current-user-id>` and `does_current_user_follow: true` for users actually followed in `related.users` / collection owners. 🤖 Generated with [Claude Code](https://claude.com/claude-code) 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
@audius/sdkto pick up the newuser_idquery parameter and renamed path placeholder onGET /notifications/{id}andGET /notifications/{id}/playlist_updates(api PRs AudiusProject/api#837 + AudiusProject/api#838, both merged).related.usersin the response (does_current_user_follow, etc.).Why
The notifications endpoint embeds user objects in
related.users. The backend personalizes those viaMyID: app.getMyId(c)inapi/v1_notifications.go:336, andgetMyIdreads from the query string?user_id=, not the URL path. The SDK only put the id in the path, so on the backendMyID = 0and the SQL short-circuiteddoes_current_user_followtofalsefor every embedded user.On a cold app boot,
useNotificationsfires early andprimeRelatedDatawrites those un-personalized users into the tan-query cache.primeUserDataonly writes when the slot is empty (noforceReplace), so the bad entries stick until the user navigates to that profile and a personalizedgetUserfetch overwrites the cache. That's why visiting@audiusdirectly and refreshing makes the Following state appear correct.A backend-only fix would collapse two semantically distinct ids (notifications owner vs. requester) — incorrect when a manager reads a managed user's notifications. The fix is client-side: send the requester id as
?user_id=, keep the path id for whose notifications.Changes
packages/sdk/src/sdk/api/generated/default/apis/NotificationsApi.tsuserId→id; newuserId?: stringquery field carries the requester id.packages/sdk/src/sdk/api/generated/default/apis/UsersApi.tsgetUserForYouFeedwas removed upstream and the generated copy was stale.packages/common/src/api/tan-query/notifications/useNotifications.ts{ id, userId }to the SDK.packages/common/src/api/tan-query/notifications/useNotificationUnreadCount.tsidas the path field. (No requester id passed here — the unread count doesn't hydraterelated.users.)packages/common/src/api/tan-query/playlist-updates/usePlaylistUpdates.ts{ id, userId }to the SDK; widen the local cast type.Breaking SDK change (external consumers)
The path field on
GetNotificationsRequestandGetPlaylistUpdatesRequestrenames fromuserIdtoid. External@audius/sdkconsumers of these two methods will need to rename the field. Wire format and server behaviour are unchanged.Test plan
@audius: confirm Following state renders correctly on lists hydrated from notification responses (e.g. notification panel actor avatars, recommended profiles surfaced from related users) without first having to navigate to@audius's profile.?user_id=<hashed-current-user-id>anddoes_current_user_follow: truefor users actually followed inrelated.users.?user_id=carries whatever the app treats as "current user" (the managed user in normal manager-switched state); personalization in the response reflects that perspective.🤖 Generated with Claude Code