Skip to content

docs(swagger): add user_id query param to notifications endpoints#837

Merged
raymondjacobson merged 1 commit into
mainfrom
api/notifications-current-user-query
May 20, 2026
Merged

docs(swagger): add user_id query param to notifications endpoints#837
raymondjacobson merged 1 commit into
mainfrom
api/notifications-current-user-query

Conversation

@raymondjacobson
Copy link
Copy Markdown
Member

Summary

  • Declare an optional user_id query parameter on GET /notifications/{user_id} and GET /notifications/{user_id}/playlist_updates so SDK consumers can send the requester id alongside the notifications-owner path id.
  • Spec-only change; the global resolveMyIdMiddleware already reads c.Query("user_id") and feeds it to getMyId(c), so no backend code needs to change.

Why

The notifications handler personalizes the embedded related.users field via MyID: app.getMyId(c) in api/v1_notifications.go:336. getMyId reads from the query string ?user_id=, not from the path. Today the SDK only puts the user id in the path (/notifications/{user_id}), so MyID resolves to 0. The does_current_user_follow SQL short-circuits to false (WHERE $1 > 0 AND follower_user_id = $1 in api/dbv1/get_users.sql.go), and every embedded user returns with does_current_user_follow: false.

Clients that prime these into a shared user cache (the web/mobile tan-query layer does this in primeRelatedData) end up showing the current user as not-following accounts they actually follow — until they navigate to that user's profile directly and a personalized fetch overwrites the cache entry.

A backend-only fix (MyID: app.getUserId(c)) would collapse two semantically different ids: the notifications owner (path) and the requester (current user). They are intentionally separable, since a manager can read a managed user's notifications and should still see relationships from their own perspective. Carrying the requester id explicitly via the query keeps the two distinct.

Notes for SDK regen

  • The path param and the new query param share the name user_id. OpenAPI 3 permits same-name params in different in locations, but openapi-generator-cli (typescript-fetch, v6.0.0) may emit a duplicate userId field in GetNotificationsRequest. If regen fails or produces an ambiguous type, options: rename the path placeholder to id (concrete URL unchanged; breaking change for @audius/sdk consumers) or use x-codegen-param-name to disambiguate without altering the wire format.
  • Existing clients that don't pass the query param continue to work (the new param is optional). They keep the same buggy behavior they have today until the SDK is updated and call sites pass the requester id.

Test plan

  • Spec validates (swagger-cli validate / equivalent in CI).
  • After SDK regen + client update: confirm does_current_user_follow returns true for users the current user actually follows in related.users on a fresh app boot (no profile navigation required).
  • Verify manager case: when a manager fetches a managed user's notifications, the manager's user_id in the query produces personalization from the manager's perspective.

🤖 Generated with Claude Code

The /notifications/{user_id} and /notifications/{user_id}/playlist_updates
endpoints currently take the notifications owner via the URL path only. The
backend computes personalization (e.g. does_current_user_follow on embedded
related.users) from resolveMyIdMiddleware, which reads c.Query("user_id") —
not the path. With no query value, MyID is 0 and every embedded user comes
back with does_current_user_follow: false, polluting clients that prime
related users into a shared cache.

Declare an optional user_id query parameter on both endpoints so SDK
consumers can send the requester id explicitly. This is kept separate from
the path user_id because a manager may read a managed user's notifications,
in which case the requester and the owner are different users.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@raymondjacobson raymondjacobson merged commit db7afc5 into main May 20, 2026
3 checks passed
@raymondjacobson raymondjacobson deleted the api/notifications-current-user-query branch May 20, 2026 19:00
raymondjacobson added a commit 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>
raymondjacobson added a commit to AudiusProject/apps that referenced this pull request May 20, 2026
…14366)

## Summary

- Regenerate `@audius/sdk` to pick up the new `user_id` query parameter
and renamed path placeholder on `GET /notifications/{id}` and `GET
/notifications/{id}/playlist_updates` (api PRs
[AudiusProject/api#837](AudiusProject/api#837) +
[AudiusProject/api#838](AudiusProject/api#838),
both merged).
- Pass the current user id through every call site so the backend
personalizes embedded `related.users` in the response
(`does_current_user_follow`, etc.).

## Why

The notifications endpoint embeds user objects in `related.users`. The
backend personalizes those via `MyID: app.getMyId(c)` in
[`api/v1_notifications.go:336`](https://github.com/AudiusProject/api/blob/main/api/v1_notifications.go#L336),
and
[`getMyId`](https://github.com/AudiusProject/api/blob/main/api/resolve_middleware.go#L30)
reads from the **query string** `?user_id=`, not the URL path. The SDK
only put the id in the path, so on the backend `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.

On a cold app boot, `useNotifications` fires early and
`primeRelatedData` writes those un-personalized users into the tan-query
cache. `primeUserData` only writes when the slot is empty (no
`forceReplace`), so the bad entries stick until the user navigates to
that profile and a personalized `getUser` fetch overwrites the cache.
That's why visiting `@audius` directly 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

| File | Change |
| --- | --- |
| `packages/sdk/src/sdk/api/generated/default/apis/NotificationsApi.ts`
| Regenerated. Path field renames `userId` → `id`; new `userId?: string`
query field carries the requester id. |
| `packages/sdk/src/sdk/api/generated/default/apis/UsersApi.ts` |
Incidental drift: `getUserForYouFeed` was removed upstream and the
generated copy was stale. |
| `packages/common/src/api/tan-query/notifications/useNotifications.ts`
| Pass `{ id, userId }` to the SDK. |
|
`packages/common/src/api/tan-query/notifications/useNotificationUnreadCount.ts`
| Update local cast type and call site to use `id` as the path field.
(No requester id passed here — the unread count doesn't hydrate
`related.users`.) |
|
`packages/common/src/api/tan-query/playlist-updates/usePlaylistUpdates.ts`
| Pass `{ id, userId }` to the SDK; widen the local cast type. |

## Breaking SDK change (external consumers)

The path field on `GetNotificationsRequest` and
`GetPlaylistUpdatesRequest` renames from `userId` to `id`. External
`@audius/sdk` consumers of these two methods will need to rename the
field. Wire format and server behaviour are unchanged.

## Test plan

- [ ] CI green (typecheck/lint/tests).
- [ ] On a fresh app load with a signed-in account that follows
`@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.
- [ ] Network inspector on the notifications request shows
`?user_id=<hashed-current-user-id>` and `does_current_user_follow: true`
for users actually followed in `related.users`.
- [ ] Manager flow: read notifications while acting as a managed user.
`?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](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
raymondjacobson added a commit that referenced this pull request May 20, 2026
## Summary

Adds an optional `user_id` query parameter to three routes that embed
user objects in their response and rely on `app.getMyId(c)` to
personalize them. Today the SDK request types don't expose this field,
so clients never send `?user_id=` and the backend computes
personalization against `MyID = 0` — i.e. `does_current_user_follow` and
friends return false for every embedded user.

- `GET /events/remix-contests`
([v1_events_remix_contests.go:187,195,215](https://github.com/AudiusProject/api/blob/main/api/v1_events_remix_contests.go#L187))
- `GET /users/{id}/contests`
([v1_users_contests.go:114,122,142](https://github.com/AudiusProject/api/blob/main/api/v1_users_contests.go#L114))
- `GET /playlists/new-releases`
([v1_playlists_new_releases.go:74](https://github.com/AudiusProject/api/blob/main/api/v1_playlists_new_releases.go#L74))

## Why

Audit follow-up to [#837](#837)
/ [#838](#838) (notifications).
Same bug class:

1. Endpoint embeds users in `related.users` (or a collection's `user`
field).
2. Backend handler passes `MyID: app.getMyId(c)` into
`dbv1.ParallelParams` to personalize those users.
3. `getMyId(c)` reads from the query string `?user_id=`. With no query
value, `MyID = 0` and the SQL
[short-circuits](https://github.com/AudiusProject/api/blob/main/api/dbv1/get_users.sql.go#L129-L136)
`does_current_user_follow` to false.
4. Web/mobile prime those un-personalized users into a shared tan-query
cache (via `primeRelatedData` for remix-contests, `primeCollectionData`
→ `primeUserData` for new-releases). The cache write only happens when
the slot is empty, so once poisoned the entry sticks until the user
navigates to that profile and a personalized fetch overwrites it.

Affected client hooks:

- `useAllRemixContests`
([apps/packages/common/src/api/tan-query/events/useAllRemixContests.ts](https://github.com/AudiusProject/apps/blob/main/packages/common/src/api/tan-query/events/useAllRemixContests.ts))
- `useUserRemixContests`
([apps/packages/common/src/api/tan-query/events/useUserRemixContests.ts](https://github.com/AudiusProject/apps/blob/main/packages/common/src/api/tan-query/events/useUserRemixContests.ts))
- `useNewAlbumReleases`
([apps/packages/common/src/api/tan-query/collection/useNewAlbumReleases.ts](https://github.com/AudiusProject/apps/blob/main/packages/common/src/api/tan-query/collection/useNewAlbumReleases.ts))

## Spec-only change

`resolveMyIdMiddleware` already reads `c.Query(\"user_id\")` globally
([resolve_middleware.go:24-28](https://github.com/AudiusProject/api/blob/main/api/resolve_middleware.go#L24)),
so the Go handlers personalize correctly as soon as the SDK starts
sending the param. No backend code change required, no Go test updates
required.

No path-placeholder collision this time — all three routes are either
query-only (`new-releases`, `remix-contests`) or already use `id` as the
path placeholder (`users/{id}/contests`). The generator will emit a
clean `userId?: string` field.

## Test plan

- [ ] Spec validates in CI.
- [ ] After SDK regen + apps update (follow-up PR): on a fresh app load
with a signed-in account that follows artists surfaced on the Browse →
New Albums and Explore → Contests pages, confirm Following state renders
correctly without first having to visit each artist's profile.
- [ ] Network inspector on the three endpoints shows
`?user_id=<hashed-current-user-id>` and `does_current_user_follow: true`
for users actually followed in `related.users`.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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