Skip to content

Commit 5de4bc7

Browse files
fix(notifications): personalize embedded users in related response
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>
1 parent a80d544 commit 5de4bc7

4 files changed

Lines changed: 19 additions & 65 deletions

File tree

packages/common/src/api/tan-query/notifications/useNotifications.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,10 @@ export const useNotifications = (options?: QueryOptions) => {
7777
const sdk = await audiusSdk()
7878
const response = await sdk.notifications.getNotifications({
7979
userId: Id.parse(currentUserId),
80+
// Requester id sent as `?user_id=`. Needed so the backend personalizes
81+
// embedded related.users (e.g. does_current_user_follow) — the path
82+
// userId alone identifies the notifications owner, not the requester.
83+
userId2: Id.parse(currentUserId),
8084
limit: DEFAULT_LIMIT,
8185
timestamp: pageParam?.timestamp,
8286
groupId: pageParam?.groupId

packages/common/src/api/tan-query/playlist-updates/usePlaylistUpdates.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,18 @@ export const usePlaylistUpdates = <TResult = PlaylistUpdate[]>(
3838
const sdk = await audiusSdk()
3939
// sdk.notifications.getPlaylistUpdates is not currently typed in the
4040
// public SDK surface; cast to the expected shape used in the legacy saga.
41+
// userId2 carries the requester id as `?user_id=` so the backend can
42+
// personalize related.users in the response.
4143
const response = (await (
4244
sdk.notifications as {
4345
getPlaylistUpdates: (params: {
4446
userId: string
47+
userId2?: string
4548
}) => Promise<PlaylistUpdatesResponse>
4649
}
4750
).getPlaylistUpdates({
48-
userId: Id.parse(currentUserId)
51+
userId: Id.parse(currentUserId),
52+
userId2: Id.parse(currentUserId)
4953
})) as PlaylistUpdatesResponse | undefined
5054

5155
return transformAndCleanList(

packages/sdk/src/sdk/api/generated/default/apis/NotificationsApi.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import {
2727

2828
export interface GetNotificationsRequest {
2929
userId: string;
30+
userId2?: string;
3031
timestamp?: number;
3132
groupId?: string;
3233
limit?: number;
@@ -35,6 +36,7 @@ export interface GetNotificationsRequest {
3536

3637
export interface GetPlaylistUpdatesRequest {
3738
userId: string;
39+
userId2?: string;
3840
}
3941

4042
/**
@@ -53,6 +55,10 @@ export class NotificationsApi extends runtime.BaseAPI {
5355

5456
const queryParameters: any = {};
5557

58+
if (params.userId2 !== undefined) {
59+
queryParameters['user_id'] = params.userId2;
60+
}
61+
5662
if (params.timestamp !== undefined) {
5763
queryParameters['timestamp'] = params.timestamp;
5864
}
@@ -100,6 +106,10 @@ export class NotificationsApi extends runtime.BaseAPI {
100106

101107
const queryParameters: any = {};
102108

109+
if (params.userId2 !== undefined) {
110+
queryParameters['user_id'] = params.userId2;
111+
}
112+
103113
const headerParameters: runtime.HTTPHeaders = {};
104114

105115
const response = await this.request({

packages/sdk/src/sdk/api/generated/default/apis/UsersApi.ts

Lines changed: 0 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -686,14 +686,6 @@ export interface GetUserFeedRequest {
686686
encodedDataSignature?: string;
687687
}
688688

689-
export interface GetUserForYouFeedRequest {
690-
id: string;
691-
limit?: number;
692-
offset?: number;
693-
maxPerArtist?: number;
694-
userId?: string;
695-
}
696-
697689
export interface GetUserIDsByAddressesRequest {
698690
address: Array<string>;
699691
}
@@ -4175,62 +4167,6 @@ export class UsersApi extends runtime.BaseAPI {
41754167
return await response.value();
41764168
}
41774169

4178-
/**
4179-
* @hidden
4180-
* Returns a personalized For You feed for the user identified in the path. Twitter-style multi-source pipeline — candidate retrieval (in-network, trending, underground, similar-artist) → linear ranking (recency decay × engagement × social affinity, weighted by source) → diversity (per-artist cap + consecutive-same-artist lookahead).
4181-
* Get For You feed for user
4182-
*/
4183-
async getUserForYouFeedRaw(params: GetUserForYouFeedRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<Tracks>> {
4184-
if (params.id === null || params.id === undefined) {
4185-
throw new runtime.RequiredError('id','Required parameter params.id was null or undefined when calling getUserForYouFeed.');
4186-
}
4187-
4188-
const queryParameters: any = {};
4189-
4190-
if (params.limit !== undefined) {
4191-
queryParameters['limit'] = params.limit;
4192-
}
4193-
4194-
if (params.offset !== undefined) {
4195-
queryParameters['offset'] = params.offset;
4196-
}
4197-
4198-
if (params.maxPerArtist !== undefined) {
4199-
queryParameters['max_per_artist'] = params.maxPerArtist;
4200-
}
4201-
4202-
if (params.userId !== undefined) {
4203-
queryParameters['user_id'] = params.userId;
4204-
}
4205-
4206-
const headerParameters: runtime.HTTPHeaders = {};
4207-
4208-
if (!headerParameters["Authorization"] && this.configuration && this.configuration.accessToken) {
4209-
const token = await this.configuration.accessToken("OAuth2", ["read"]);
4210-
if (token) {
4211-
headerParameters["Authorization"] = token;
4212-
}
4213-
}
4214-
4215-
const response = await this.request({
4216-
path: `/users/{id}/feed/for-you`.replace(`{${"id"}}`, encodeURIComponent(String(params.id))),
4217-
method: 'GET',
4218-
headers: headerParameters,
4219-
query: queryParameters,
4220-
}, initOverrides);
4221-
4222-
return new runtime.JSONApiResponse(response, (jsonValue) => TracksFromJSON(jsonValue));
4223-
}
4224-
4225-
/**
4226-
* Returns a personalized For You feed for the user identified in the path. Twitter-style multi-source pipeline — candidate retrieval (in-network, trending, underground, similar-artist) → linear ranking (recency decay × engagement × social affinity, weighted by source) → diversity (per-artist cap + consecutive-same-artist lookahead).
4227-
* Get For You feed for user
4228-
*/
4229-
async getUserForYouFeed(params: GetUserForYouFeedRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<Tracks> {
4230-
const response = await this.getUserForYouFeedRaw(params, initOverrides);
4231-
return await response.value();
4232-
}
4233-
42344170
/**
42354171
* @hidden
42364172
* Gets User IDs from any Ethereum wallet address or Solana account address associated with their Audius account.

0 commit comments

Comments
 (0)