diff --git a/packages/common/src/api/signUp.ts b/packages/common/src/api/signUp.ts index 1f62b6e09c6..9047a05838d 100644 --- a/packages/common/src/api/signUp.ts +++ b/packages/common/src/api/signUp.ts @@ -24,8 +24,7 @@ const signUpApi = createApi({ const user = await userApiFetch.getUserByHandle( { handle, - currentUserId: null, - retry: false + currentUserId: null }, context ) diff --git a/packages/common/src/api/user.ts b/packages/common/src/api/user.ts index 90f42e1d8b3..6fbcae1bb0d 100644 --- a/packages/common/src/api/user.ts +++ b/packages/common/src/api/user.ts @@ -1,7 +1,13 @@ import { full } from '@audius/sdk' import { createApi } from '~/audius-query' -import { ID, Kind, StringUSDC } from '~/models' +import { + ID, + Kind, + OptionalId, + StringUSDC, + userMetadataListFromSDK +} from '~/models' import { USDCTransactionDetails, USDCTransactionMethod, @@ -46,10 +52,14 @@ const userApi = createApi({ getUserById: { fetch: async ( { id, currentUserId }: { id: ID; currentUserId: Nullable }, - { apiClient } + { audiusSdk } ) => { - const apiUser = await apiClient.getUser({ userId: id, currentUserId }) - return apiUser?.[0] + const sdk = await audiusSdk() + const { data: users = [] } = await sdk.full.users.getUser({ + id: Id.parse(id), + userId: OptionalId.parse(currentUserId) + }) + return userMetadataListFromSDK(users)[0] }, options: { idArgKey: 'id', @@ -61,17 +71,16 @@ const userApi = createApi({ fetch: async ( { handle, - currentUserId, - retry = true - }: { handle: string; currentUserId: Nullable; retry?: boolean }, - { apiClient } + currentUserId + }: { handle: string; currentUserId: Nullable }, + { audiusSdk } ) => { - const apiUser = await apiClient.getUserByHandle({ + const sdk = await audiusSdk() + const { data: users = [] } = await sdk.full.users.getUserByHandle({ handle, - currentUserId, - retry + userId: OptionalId.parse(currentUserId) }) - return apiUser?.[0] + return userMetadataListFromSDK(users)[0] }, options: { kind: Kind.USERS, diff --git a/packages/common/src/api/utils.ts b/packages/common/src/api/utils.ts index 035b0081c04..812558ddf27 100644 --- a/packages/common/src/api/utils.ts +++ b/packages/common/src/api/utils.ts @@ -2,6 +2,9 @@ import { z } from 'zod' import { decodeHashId, encodeHashId } from '~/utils/hashIds' +// TODO: Move usage of these utils +// to the version in packages/common/src/models/Identifiers.ts +// https://linear.app/audius/issue/PAY-2997/migrate-idhashid-usage-to-models-version export const HashId = z.string().transform((data: string, context) => { const id = decodeHashId(data) if (id === null) { diff --git a/packages/common/src/models/Identifiers.ts b/packages/common/src/models/Identifiers.ts index db3e3a0d317..7fe6cdf6c1e 100644 --- a/packages/common/src/models/Identifiers.ts +++ b/packages/common/src/models/Identifiers.ts @@ -1,3 +1,7 @@ +import { z } from 'zod' + +import { decodeHashId, encodeHashId } from '~/utils/hashIds' + export type ID = number export type UID = string export type CID = string @@ -7,3 +11,39 @@ export enum PlayableType { PLAYLIST = 'playlist', ALBUM = 'album' } + +export const HashId = z.string().transform((data: string, context) => { + const id = decodeHashId(data) + if (id === null) { + context.addIssue({ + code: z.ZodIssueCode.custom, + message: 'Hash id is invalid' + }) + + return z.NEVER + } + return id +}) + +/** Parses a `Nullable` safely for use with SDK functions which acccept an optional + id parameter. +*/ +export const OptionalId = z + .number() + .nullable() + .transform( + (data: number | null) => encodeHashId(data) ?? undefined + ) + +export const Id = z.number().transform((data: number, context) => { + const id = encodeHashId(data) + if (id === null) { + context.addIssue({ + code: z.ZodIssueCode.custom, + message: 'Hash id is invalid' + }) + + return z.NEVER + } + return id +}) diff --git a/packages/common/src/models/Tipping.ts b/packages/common/src/models/Tipping.ts index e0038d93f6f..46ab41489a2 100644 --- a/packages/common/src/models/Tipping.ts +++ b/packages/common/src/models/Tipping.ts @@ -1,5 +1,9 @@ +import { full } from '@audius/sdk' + import { ID } from '~/models/Identifiers' +import { removeNullable } from '~/utils/typeUtils' +import { UserMetadata, userMetadataFromSDK } from './User' import { StringWei } from './Wallet' export type Supporter = { @@ -27,3 +31,45 @@ export type UserTip = { export type LastDismissedTip = { receiver_id: ID } + +export type SupporterMetadata = { + sender: UserMetadata + amount: StringWei + rank: number +} + +export type SupportedUserMetadata = { + receiver: UserMetadata + amount: StringWei + rank: number +} + +export const supporterMetadataFromSDK = ( + input: full.FullSupporter +): SupporterMetadata | undefined => { + const user = userMetadataFromSDK(input.sender) + return user + ? { sender: user, amount: input.amount as StringWei, rank: input.rank } + : undefined +} + +export const supporterMetadataListFromSDK = (input?: full.FullSupporter[]) => + input + ? input.map((d) => supporterMetadataFromSDK(d)).filter(removeNullable) + : [] + +export const supportedUserMetadataFromSDK = ( + input: full.FullSupporting +): SupportedUserMetadata | undefined => { + const user = userMetadataFromSDK(input.receiver) + return user + ? { receiver: user, amount: input.amount as StringWei, rank: input.rank } + : undefined +} + +export const supportedUserMetadataListFromSDK = ( + input?: full.FullSupporting[] +) => + input + ? input.map((d) => supportedUserMetadataFromSDK(d)).filter(removeNullable) + : [] diff --git a/packages/common/src/services/audius-api-client/AudiusAPIClient.ts b/packages/common/src/services/audius-api-client/AudiusAPIClient.ts index e50323196ae..29994e87812 100644 --- a/packages/common/src/services/audius-api-client/AudiusAPIClient.ts +++ b/packages/common/src/services/audius-api-client/AudiusAPIClient.ts @@ -34,9 +34,7 @@ import { APIUser, GetNFTGatedTrackSignaturesResponse, GetTipsResponse, - OpaqueID, - SupporterResponse, - SupportingResponse + OpaqueID } from './types' // TODO: declare this at the root and use actual audiusLibs type @@ -72,18 +70,8 @@ const FULL_ENDPOINT_MAP = { experiment ? `/playlists/trending/${experiment}` : '/playlists/trending', recommended: '/tracks/recommended', remixables: '/tracks/remixables', - following: (userId: OpaqueID) => `/users/${userId}/following`, - followers: (userId: OpaqueID) => `/users/${userId}/followers`, - trackRepostUsers: (trackId: OpaqueID) => `/tracks/${trackId}/reposts`, - trackFavoriteUsers: (trackId: OpaqueID) => `/tracks/${trackId}/favorites`, - playlistRepostUsers: (playlistId: OpaqueID) => - `/playlists/${playlistId}/reposts`, - playlistFavoriteUsers: (playlistId: OpaqueID) => - `/playlists/${playlistId}/favorites`, playlistUpdates: (userId: OpaqueID) => `/notifications/${userId}/playlist_updates`, - getUser: (userId: OpaqueID) => `/users/${userId}`, - userByHandle: (handle: OpaqueID) => `/users/handle/${handle}`, userTracksByHandle: (handle: OpaqueID) => `/users/handle/${handle}/tracks`, userAiTracksByHandle: (handle: OpaqueID) => `/users/handle/${handle}/tracks/ai_attributed`, @@ -102,13 +90,7 @@ const FULL_ENDPOINT_MAP = { getRemixing: (trackId: OpaqueID) => `/tracks/${trackId}/remixing`, searchFull: `/search/full`, searchAutocomplete: `/search/autocomplete`, - getUserSupporter: (userId: OpaqueID, supporterUserId: OpaqueID) => - `/users/${userId}/supporters/${supporterUserId}`, - getUserSupporting: (userId: OpaqueID, supporterUserId: OpaqueID) => - `/users/${userId}/supporting/${supporterUserId}`, getReaction: '/reactions', - getSupporting: (userId: OpaqueID) => `/users/${userId}/supporting`, - getSupporters: (userId: OpaqueID) => `/users/${userId}/supporters`, getTips: '/tips', getNFTGatedTrackSignatures: (userId: OpaqueID) => `/tracks/${userId}/nft-gated-signatures`, @@ -190,60 +172,6 @@ type GetRemixablesArgs = { currentUserId: Nullable } -type GetFollowingArgs = { - profileUserId: ID - currentUserId: Nullable - offset?: number - limit?: number -} - -type GetFollowersArgs = { - profileUserId: ID - currentUserId: Nullable - offset?: number - limit?: number -} - -type GetTrackRepostUsersArgs = { - trackId: ID - currentUserId: Nullable - limit?: number - offset?: number -} - -type GetTrackFavoriteUsersArgs = { - trackId: ID - currentUserId: Nullable - limit?: number - offset?: number -} - -type GetPlaylistRepostUsersArgs = { - playlistId: ID - currentUserId: Nullable - limit?: number - offset?: number -} - -type GetPlaylistFavoriteUsersArgs = { - playlistId: ID - currentUserId: Nullable - limit?: number - offset?: number -} - -type GetUserArgs = { - userId: ID - currentUserId: Nullable - abortOnUnreachable?: boolean -} - -type GetUserByHandleArgs = { - handle: string - currentUserId: Nullable - retry?: boolean -} - type GetUserTracksByHandleArgs = { handle: string currentUserId: Nullable @@ -435,18 +363,6 @@ type GetReactionResponse = [ } ] -export type GetSupportingArgs = { - userId: ID - limit?: number - offset?: number -} - -export type GetSupportersArgs = { - userId: ID - limit?: number - offset?: number -} - export type GetTipsArgs = { userId: ID limit?: number @@ -495,12 +411,6 @@ const emptySearchResponse: APIResponse = { } } -type GetUserSupporterArgs = { - userId: ID - supporterUserId: ID - currentUserId: Nullable -} - type AudiusAPIClientConfig = { audiusBackendInstance: AudiusBackend getAudiusLibs: () => Nullable @@ -703,174 +613,6 @@ export class AudiusAPIClient { return adapted } - async getFollowing({ - currentUserId, - profileUserId, - limit, - offset - }: GetFollowingArgs) { - this._assertInitialized() - const encodedCurrentUserId = encodeHashId(currentUserId) - const encodedProfileUserId = this._encodeOrThrow(profileUserId) - const params = { - user_id: encodedCurrentUserId || undefined, - limit, - offset - } - - const followingResponse = await this._getResponse>( - FULL_ENDPOINT_MAP.following(encodedProfileUserId), - params - ) - if (!followingResponse) return [] - const adapted = followingResponse.data - .map(adapter.makeUser) - .filter(removeNullable) - return adapted - } - - async getFollowers({ - currentUserId, - profileUserId, - limit, - offset - }: GetFollowersArgs) { - this._assertInitialized() - const encodedCurrentUserId = encodeHashId(currentUserId) - const encodedProfileUserId = this._encodeOrThrow(profileUserId) - const params = { - user_id: encodedCurrentUserId || undefined, - limit, - offset - } - - const followersResponse = await this._getResponse>( - FULL_ENDPOINT_MAP.followers(encodedProfileUserId), - params - ) - - if (!followersResponse) return [] - - const adapted = followersResponse.data - .map(adapter.makeUser) - .filter(removeNullable) - return adapted - } - - async getTrackRepostUsers({ - currentUserId, - trackId, - limit, - offset - }: GetTrackRepostUsersArgs) { - this._assertInitialized() - const encodedCurrentUserId = encodeHashId(currentUserId) - const encodedTrackId = this._encodeOrThrow(trackId) - const params = { - user_id: encodedCurrentUserId || undefined, - limit, - offset - } - - const repostUsers: Nullable> = - await this._getResponse( - FULL_ENDPOINT_MAP.trackRepostUsers(encodedTrackId), - params - ) - - if (!repostUsers) return [] - - const adapted = repostUsers.data - .map(adapter.makeUser) - .filter(removeNullable) - return adapted - } - - async getTrackFavoriteUsers({ - currentUserId, - trackId, - limit, - offset - }: GetTrackFavoriteUsersArgs) { - this._assertInitialized() - const encodedCurrentUserId = encodeHashId(currentUserId) - const encodedTrackId = this._encodeOrThrow(trackId) - const params = { - user_id: encodedCurrentUserId || undefined, - limit, - offset - } - - const followingResponse = await this._getResponse>( - FULL_ENDPOINT_MAP.trackFavoriteUsers(encodedTrackId), - params - ) - - if (!followingResponse) return [] - - const adapted = followingResponse.data - .map(adapter.makeUser) - .filter(removeNullable) - return adapted - } - - async getPlaylistRepostUsers({ - currentUserId, - playlistId, - limit, - offset - }: GetPlaylistRepostUsersArgs) { - this._assertInitialized() - const encodedCurrentUserId = encodeHashId(currentUserId) - const encodedPlaylistId = this._encodeOrThrow(playlistId) - const params = { - user_id: encodedCurrentUserId || undefined, - limit, - offset - } - - const repostUsers: Nullable> = - await this._getResponse( - FULL_ENDPOINT_MAP.playlistRepostUsers(encodedPlaylistId), - params - ) - - if (!repostUsers) return [] - - const adapted = repostUsers.data - .map(adapter.makeUser) - .filter(removeNullable) - return adapted - } - - async getPlaylistFavoriteUsers({ - currentUserId, - playlistId, - limit, - offset - }: GetPlaylistFavoriteUsersArgs) { - this._assertInitialized() - const encodedCurrentUserId = encodeHashId(currentUserId) - const encodedPlaylistId = this._encodeOrThrow(playlistId) - const params = { - user_id: encodedCurrentUserId || undefined, - limit, - offset - } - - const followingResponse = await this._getResponse>( - FULL_ENDPOINT_MAP.playlistFavoriteUsers(encodedPlaylistId), - params - ) - - if (!followingResponse) return [] - - const adapted = followingResponse.data - .map(adapter.makeUser) - .filter(removeNullable) - return adapted - } - async getTrack( { id, currentUserId, unlistedArgs, abortOnUnreachable }: GetTrackArgs, retry = true @@ -1012,52 +754,6 @@ export class AudiusAPIClient { return tracks } - async getUser({ userId, currentUserId, abortOnUnreachable }: GetUserArgs) { - const encodedUserId = this._encodeOrThrow(userId) - const encodedCurrentUserId = encodeHashId(currentUserId) - this._assertInitialized() - const params = { - user_id: encodedCurrentUserId || undefined - } - - const response = await this._getResponse>( - FULL_ENDPOINT_MAP.getUser(encodedUserId), - params, - undefined, - undefined, - undefined, - abortOnUnreachable - ) - - if (!response) return [] - - const adapted = response.data.map(adapter.makeUser).filter(removeNullable) - return adapted - } - - async getUserByHandle({ - handle, - currentUserId, - retry = true - }: GetUserByHandleArgs) { - const encodedCurrentUserId = encodeHashId(currentUserId) - this._assertInitialized() - const params = { - user_id: encodedCurrentUserId || undefined - } - - const response = await this._getResponse>( - FULL_ENDPOINT_MAP.userByHandle(handle), - params, - retry - ) - - if (!response) return [] - - const adapted = response.data.map(adapter.makeUser).filter(removeNullable) - return adapted - } - async getUserTracksByHandle({ handle, currentUserId, @@ -1581,26 +1277,6 @@ export class AudiusAPIClient { return response.data } - async getUserSupporter({ - currentUserId, - userId, - supporterUserId - }: GetUserSupporterArgs) { - const encodedUserId = this._encodeOrThrow(userId) - const encodedSupporterUserId = this._encodeOrThrow(supporterUserId) - const encodedCurrentUserId = encodeHashId(currentUserId) - this._assertInitialized() - const params = { - user_id: encodedCurrentUserId || undefined - } - - const response = await this._getResponse>( - FULL_ENDPOINT_MAP.getUserSupporter(encodedUserId, encodedSupporterUserId), - params - ) - return response ? response.data : null - } - async getReaction({ reactedToIds }: GetReactionArgs) { const params = { reacted_to_ids: reactedToIds @@ -1626,36 +1302,6 @@ export class AudiusAPIClient { return adapted } - async getSupporting({ userId, limit = 25, offset = 0 }: GetSupportingArgs) { - const encodedUserId = this._encodeOrThrow(userId) - this._assertInitialized() - const params = { - limit, - offset - } - - const response = await this._getResponse>( - FULL_ENDPOINT_MAP.getSupporting(encodedUserId), - params - ) - return response ? response.data : null - } - - async getSupporters({ userId, limit = 25, offset = 0 }: GetSupportersArgs) { - const encodedUserId = this._encodeOrThrow(userId) - this._assertInitialized() - const params = { - limit, - offset - } - - const response = await this._getResponse>( - FULL_ENDPOINT_MAP.getSupporters(encodedUserId), - params - ) - return response ? response.data : null - } - async getTips({ userId, limit, diff --git a/packages/common/src/store/effects.ts b/packages/common/src/store/effects.ts index b4ee94dfd7e..c0b9d7d2fbd 100644 --- a/packages/common/src/store/effects.ts +++ b/packages/common/src/store/effects.ts @@ -1,17 +1,5 @@ -import { AllEffect, CallEffect, GetContextEffect } from 'redux-saga/effects' -import { - all, - call, - getContext as getContextBase, - SagaGenerator -} from 'typed-redux-saga' - -import { ErrorLevel } from '~/models/ErrorReporting' -import { FeatureFlags } from '~/services/remote-config/feature-flags' -import { - compareSDKResponse, - SDKMigrationFailedError -} from '~/utils/sdkMigrationUtils' +import { GetContextEffect } from 'redux-saga/effects' +import { getContext as getContextBase, SagaGenerator } from 'typed-redux-saga' import { CommonStoreContext } from './storeContext' @@ -19,69 +7,3 @@ export const getContext = ( prop: Prop ): SagaGenerator => getContextBase(prop) - -/** Helper generator that returns a fully-awaited AudiusSDK instance */ -export function* getSDK() { - const audiusSdk = yield* getContext('audiusSdk') - return yield* call(audiusSdk) -} - -/** This effect is used to shadow a migration without affecting the return value. - * It will run two effects in parallel to fetch the legacy and migrated responses, - * compare the results, log the diff, and then return the legacy value. Errors thrown - * by the effect for the migrated response will be caught to avoid bugs in the migrated - * code from causing errors. - */ -export function* checkSDKMigration({ - legacy: legacyCall, - migrated: migratedCall, - endpointName -}: { - legacy: SagaGenerator> - migrated: SagaGenerator> - endpointName: string -}) { - const getFeatureEnabled = yield* getContext('getFeatureEnabled') - const reportToSentry = yield* getContext('reportToSentry') - - if (!getFeatureEnabled(FeatureFlags.SDK_MIGRATION_SHADOWING)) { - return yield* legacyCall - } - - const [legacy, migrated] = yield* all([ - legacyCall, - call(function* settle() { - try { - return yield* migratedCall - } catch (e) { - return e instanceof Error ? e : new Error(`${e}`) - } - }) - ]) as SagaGenerator>> - - try { - compareSDKResponse({ legacy, migrated }, endpointName) - } catch (e) { - const error = - e instanceof SDKMigrationFailedError - ? e - : new SDKMigrationFailedError({ - endpointName, - innerMessage: `Unknown error: ${e}`, - legacyValue: legacy, - migratedValue: migrated - }) - console.warn('SDK Migration failed', error) - yield* call(reportToSentry, { - error, - level: ErrorLevel.Warning, - additionalInfo: { - diff: JSON.stringify(error.diff, null, 2), - legacyValue: JSON.stringify(error.legacyValue, null, 2), - migratedValue: JSON.stringify(error.migratedValue, null, 2) - }, - tags: { endpointName: error.endpointName } - }) - } - return legacy -} diff --git a/packages/common/src/store/index.ts b/packages/common/src/store/index.ts index d3a03d13e15..4278afd223a 100644 --- a/packages/common/src/store/index.ts +++ b/packages/common/src/store/index.ts @@ -37,3 +37,4 @@ export * from './playlist-updates' export * from './saved-collections' export * from './confirmer' export * from './downloads' +export * from './sdkUtils' diff --git a/packages/common/src/store/sdkUtils.ts b/packages/common/src/store/sdkUtils.ts new file mode 100644 index 00000000000..d093471e38c --- /dev/null +++ b/packages/common/src/store/sdkUtils.ts @@ -0,0 +1,97 @@ +import { AudiusSdk } from '@audius/sdk' +import { iterator as isIterator } from '@redux-saga/is' +import { AllEffect, CallEffect } from 'redux-saga/effects' +import { SagaGenerator, all, call, getContext } from 'typed-redux-saga' + +import { ErrorLevel, ReportToSentryArgs } from '~/models/ErrorReporting' +import { FeatureFlags } from '~/services/remote-config/feature-flags' +import { + SDKMigrationFailedError, + compareSDKResponse +} from '~/utils/sdkMigrationUtils' + +// These are defined explicitly here to avoid including the entire `storeContext` module +// as that creates some nasty circular dependencies. +type AudiusSDKContext = () => Promise +type GetFeatureEnabledContext = ( + flag: FeatureFlags, + fallbackFlag?: FeatureFlags +) => Promise +type ReportToSentryContext = (args: ReportToSentryArgs) => void + +/** Helper generator that returns a fully-awaited AudiusSDK instance */ +export function* getSDK() { + const audiusSdk = yield* getContext('audiusSdk') + return yield* call(audiusSdk) +} + +/** This effect is used to shadow a migration without affecting the return value. + * It will run two effects in parallel to fetch the legacy and migrated responses, + * compare the results, log the diff, and then return the legacy value. Errors thrown + * by the effect for the migrated response will be caught to avoid bugs in the migrated + * code from causing errors. + * NOTE: Should always be called from within a saga. + */ +export function* checkSDKMigration({ + legacy: legacyCall, + migrated: migratedCall, + endpointName +}: { + legacy: SagaGenerator> | CallEffect + migrated: SagaGenerator> | CallEffect + endpointName: string +}): SagaGenerator { + const getFeatureEnabled = yield* getContext( + 'getFeatureEnabled' + ) + const reportToSentry = yield* getContext( + 'reportToSentry' + ) + + if (!getFeatureEnabled(FeatureFlags.SDK_MIGRATION_SHADOWING)) { + // Yield control if it's a generator, otherwise let middleware handle it + // @ts-ignore + return isIterator(legacyCall) ? yield* legacyCall : yield legacyCall + } + + const [legacy, migrated] = yield* all([ + legacyCall, + call(function* settle() { + try { + // Yield control if it's a generator, otherwise let middleware handle it + return isIterator(migratedCall) + ? yield* migratedCall + : // @ts-ignore + yield migratedCall + } catch (e) { + return e instanceof Error ? e : new Error(`${e}`) + } + }) + ]) as SagaGenerator>> + + try { + compareSDKResponse({ legacy, migrated }, endpointName) + } catch (e) { + const error = + e instanceof SDKMigrationFailedError + ? e + : new SDKMigrationFailedError({ + endpointName, + innerMessage: `Unknown error: ${e}`, + legacyValue: legacy, + migratedValue: migrated + }) + console.warn('SDK Migration failed', error) + yield* call(reportToSentry, { + error, + level: ErrorLevel.Warning, + additionalInfo: { + diff: JSON.stringify(error.diff, null, 2), + legacyValue: JSON.stringify(error.legacyValue, null, 2), + migratedValue: JSON.stringify(error.migratedValue, null, 2) + }, + tags: { endpointName: error.endpointName } + }) + } + return legacy +} diff --git a/packages/common/src/store/tipping/index.ts b/packages/common/src/store/tipping/index.ts index 03513d950a5..c8d8d8c7907 100644 --- a/packages/common/src/store/tipping/index.ts +++ b/packages/common/src/store/tipping/index.ts @@ -1,3 +1,4 @@ export * as tippingSelectors from './selectors' export { default as tippingReducer, actions as tippingActions } from './slice' +export * as tippingUtils from './utils' export * from './types' diff --git a/packages/common/src/store/tipping/utils.ts b/packages/common/src/store/tipping/utils.ts new file mode 100644 index 00000000000..3a10435390d --- /dev/null +++ b/packages/common/src/store/tipping/utils.ts @@ -0,0 +1,29 @@ +import { SupportedUserMetadata, SupporterMetadata } from '~/models' + +import { SupportersMapForUser, SupportingMapForUser } from './types' + +export const makeSupportingMapForUser = ( + supportedUsers: SupportedUserMetadata[] +): SupportingMapForUser => + supportedUsers.reduce((out, { receiver, amount, rank }) => { + const receiver_id = receiver.user_id + out[receiver_id] = { + receiver_id, + rank, + amount + } + return out + }, {}) + +export const makeSupportersMapForUser = ( + supporters: SupporterMetadata[] +): SupportersMapForUser => + supporters.reduce((out, { sender, amount, rank }) => { + const sender_id = sender.user_id + out[sender_id] = { + sender_id, + rank, + amount + } + return out + }, {}) diff --git a/packages/common/src/store/ui/related-artists/sagas.ts b/packages/common/src/store/ui/related-artists/sagas.ts index d52aaa6e1f0..f517f2cd121 100644 --- a/packages/common/src/store/ui/related-artists/sagas.ts +++ b/packages/common/src/store/ui/related-artists/sagas.ts @@ -7,7 +7,8 @@ import { ID, UserMetadata, userMetadataListFromSDK } from '~/models' import { DoubleKeys } from '~/services/remote-config' import { accountSelectors } from '~/store/account' import { processAndCacheUsers } from '~/store/cache' -import { checkSDKMigration, getContext, getSDK } from '~/store/effects' +import { getContext } from '~/store/effects' +import { checkSDKMigration, getSDK } from '~/store/sdkUtils' import { waitForRead } from '~/utils/sagaHelpers' import { removeNullable } from '~/utils/typeUtils' diff --git a/packages/libs/src/sdk/api/generated/default/apis/TracksApi.ts b/packages/libs/src/sdk/api/generated/default/apis/TracksApi.ts index 5a51701b681..054897a2082 100644 --- a/packages/libs/src/sdk/api/generated/default/apis/TracksApi.ts +++ b/packages/libs/src/sdk/api/generated/default/apis/TracksApi.ts @@ -87,6 +87,7 @@ export interface StreamTrackRequest { userData?: string; nftAccessSignature?: string; skipPlayCount?: boolean; + apiKey?: string; } /** @@ -433,6 +434,10 @@ export class TracksApi extends runtime.BaseAPI { queryParameters['skip_play_count'] = params.skipPlayCount; } + if (params.apiKey !== undefined) { + queryParameters['api_key'] = params.apiKey; + } + const headerParameters: runtime.HTTPHeaders = {}; const response = await this.request({ diff --git a/packages/web/src/common/store/cache/users/sagas.ts b/packages/web/src/common/store/cache/users/sagas.ts index e2bf93fdea8..528682a2d7e 100644 --- a/packages/web/src/common/store/cache/users/sagas.ts +++ b/packages/web/src/common/store/cache/users/sagas.ts @@ -1,4 +1,11 @@ -import { DefaultSizes, Kind, User, UserMetadata } from '@audius/common/models' +import { + DefaultSizes, + Id, + Kind, + User, + UserMetadata, + userMetadataListFromSDK +} from '@audius/common/models' import { Metadata, accountSelectors, @@ -7,7 +14,8 @@ import { cacheReducer, cacheUsersSelectors, getContext, - reformatUser + reformatUser, + getSDK } from '@audius/common/store' import { waitForAccount, waitForValue } from '@audius/common/utils' import { mergeWith } from 'lodash' @@ -53,17 +61,20 @@ export function* fetchUsers( function* retrieveUserByHandle(handle: string, retry: boolean) { yield* waitForRead() - const apiClient = yield* getContext('apiClient') + const sdk = yield* getSDK() const userId = yield* select(getUserId) if (Array.isArray(handle)) { handle = handle[0] } - const user = yield* call([apiClient, apiClient.getUserByHandle], { - handle, - currentUserId: userId, - retry - }) - return user + + const { data: users = [] } = yield* call( + [sdk.full.users, sdk.full.users.getUserByHandle], + { + handle, + userId: Id.parse(userId) + } + ) + return userMetadataListFromSDK(users) } export function* fetchUserByHandle( diff --git a/packages/web/src/common/store/pages/token-dashboard/getAccountMetadataCID.ts b/packages/web/src/common/store/pages/token-dashboard/getAccountMetadataCID.ts index 10d1f89a5b1..8421f94a0ad 100644 --- a/packages/web/src/common/store/pages/token-dashboard/getAccountMetadataCID.ts +++ b/packages/web/src/common/store/pages/token-dashboard/getAccountMetadataCID.ts @@ -1,4 +1,5 @@ -import { accountSelectors, getContext } from '@audius/common/store' +import { Id, userMetadataListFromSDK } from '@audius/common/models' +import { accountSelectors, getSDK } from '@audius/common/store' import { select, call } from 'typed-redux-saga' import { waitForRead } from 'utils/sagaHelpers' @@ -7,14 +8,16 @@ const { getUserId } = accountSelectors export function* getAccountMetadataCID() { yield* waitForRead() - const apiClient = yield* getContext('apiClient') + const sdk = yield* getSDK() const accountUserId = yield* select(getUserId) if (!accountUserId) return null - const users = yield* call([apiClient, apiClient.getUser], { - userId: accountUserId, - currentUserId: accountUserId + const { data = [] } = yield* call([sdk.full.users, sdk.full.users.getUser], { + id: Id.parse(accountUserId), + userId: Id.parse(accountUserId) }) + const users = userMetadataListFromSDK(data) + if (users.length !== 1) return null return users[0].metadata_multihash } diff --git a/packages/web/src/common/store/profile/sagas.js b/packages/web/src/common/store/profile/sagas.js index 6911e735087..ec8979e9d70 100644 --- a/packages/web/src/common/store/profile/sagas.js +++ b/packages/web/src/common/store/profile/sagas.js @@ -1,4 +1,9 @@ -import { DefaultSizes, Kind } from '@audius/common/models' +import { + DefaultSizes, + Kind, + Id, + userMetadataListFromSDK +} from '@audius/common/models' import { DoubleKeys, FeatureFlags } from '@audius/common/services' import { accountSelectors, @@ -52,6 +57,7 @@ import { waitForRead, waitForWrite } from 'utils/sagaHelpers' import { watchFetchProfileCollections } from './fetchProfileCollectionsSaga' import { watchFetchTopTags } from './fetchTopTagsSaga' + const { refreshSupport } = tippingActions const { getIsReachable } = reachabilitySelectors const { getProfileUserId, getProfileFollowers, getProfileUser } = @@ -549,7 +555,8 @@ export function* updateProfileAsync(action) { function* confirmUpdateProfile(userId, metadata) { yield waitForWrite() - const apiClient = yield getContext('apiClient') + const getSDK = yield getContext('audiusSdk') + const sdk = yield getSDK() const audiusBackendInstance = yield getContext('audiusBackendInstance') yield put( confirmerActions.requestConfirmation( @@ -570,11 +577,14 @@ function* confirmUpdateProfile(userId, metadata) { } yield waitForAccount() const currentUserId = yield select(getUserId) - const users = yield apiClient.getUser({ - userId, - currentUserId - }) - return users[0] + const { data = [] } = yield call( + [sdk.full.users, sdk.full.users.getUser], + { + id: Id.parse(userId), + userId: Id.parse(currentUserId) + } + ) + return userMetadataListFromSDK(data)[0] }, function* (confirmedUser) { // Update the cached user so it no longer contains image upload artifacts diff --git a/packages/web/src/common/store/tipping/sagas.ts b/packages/web/src/common/store/tipping/sagas.ts index 7d55d8009a9..897cc6bae8e 100644 --- a/packages/web/src/common/store/tipping/sagas.ts +++ b/packages/web/src/common/store/tipping/sagas.ts @@ -2,19 +2,20 @@ import { Name, Kind, ID, - Supporter, - Supporting, LastDismissedTip, User, StringWei, BNWei, - SolanaWalletAddress + SolanaWalletAddress, + Id, + OptionalId, + supportedUserMetadataListFromSDK, + supporterMetadataListFromSDK, + supporterMetadataFromSDK } from '@audius/common/models' import { createUserBankIfNeeded, LocalStorage, - GetSupportingArgs, - GetSupportersArgs, GetTipsArgs, FeatureFlags } from '@audius/common/services' @@ -29,10 +30,11 @@ import { walletSelectors, walletActions, getContext, - RefreshSupportPayloadAction + RefreshSupportPayloadAction, + getSDK, + tippingUtils } from '@audius/common/store' import { - decodeHashId, isNullOrUndefined, weiToAudioString, stringWeiToBN, @@ -91,8 +93,9 @@ const { getSupporters, getSupporting } = tippingSelectors + const { update } = cacheActions -const { getAccountUser } = accountSelectors +const { getAccountUser, getUserId } = accountSelectors const { fetchPermissions } = chatActions export const FEED_TIP_DISMISSAL_TIME_LIMIT_SEC = 30 * 24 * 60 * 60 // 30 days @@ -228,7 +231,7 @@ function* overrideSupportersForUser({ } /** - * Polls the getUserSupporter endpoint to check if the sender is listed as a supporter of the recipient + * Polls the /supporter endpoint to check if the sender is listed as a supporter of the recipient */ function* confirmTipIndexed({ sender, @@ -248,13 +251,20 @@ function* confirmTipIndexed({ }/${maxAttempts}] (delay: ${delayMs}ms)` ) try { - const apiClient = yield* getContext('apiClient') - const response = yield* call([apiClient, apiClient.getUserSupporter], { - currentUserId: sender.user_id, - userId: recipient.user_id, - supporterUserId: sender.user_id - }) - if (response) { + const sdk = yield* getSDK() + + const senderUserId = Id.parse(sender.user_id) + + const { data } = yield* call( + [sdk.full.users, sdk.full.users.getSupporter], + { + id: Id.parse(recipient.user_id), + supporterUserId: senderUserId, + userId: senderUserId + } + ) + + if (data) { console.debug('Tip indexed') return true } @@ -633,76 +643,64 @@ function* sendTipAsyncOld() { } function* refreshSupportAsync({ - payload: { senderUserId, receiverUserId, supportingLimit, supportersLimit } + payload: { + senderUserId, + receiverUserId, + supportingLimit: supportingLimitArg, + supportersLimit + } }: { payload: RefreshSupportPayloadAction type: string }) { yield* waitForRead() - const apiClient = yield* getContext('apiClient') + const sdk = yield* getSDK() + const currentUserId = yield* select(getUserId) - const supportingParams: GetSupportingArgs = { - userId: senderUserId - } - if (supportingLimit) { - supportingParams.limit = supportingLimit - } else { + let supportingLimit = supportingLimitArg + + if (!supportingLimit) { const account = yield* select(getAccountUser) - supportingParams.limit = + supportingLimit = account?.user_id === senderUserId ? account?.supporting_count : SUPPORTING_PAGINATION_SIZE } - const supportersParams: GetSupportersArgs = { - userId: receiverUserId - } - if (supportersLimit) { - supportersParams.limit = supportersLimit - } - - const supportingForSenderList = yield* call( - [apiClient, apiClient.getSupporting], - supportingParams + const { data: supportingData = [] } = yield* call( + [sdk.full.users, sdk.full.users.getSupportedUsers], + { + id: Id.parse(senderUserId), + userId: OptionalId.parse(currentUserId), + limit: supportingLimit + } ) - const supportersForReceiverList = yield* call( - [apiClient, apiClient.getSupporters], - supportersParams + const supportingForSenderList = + supportedUserMetadataListFromSDK(supportingData) + + const { data: supporterData = [] } = yield* call( + [sdk.full.users, sdk.full.users.getSupporters], + { + id: Id.parse(receiverUserId), + limit: supportersLimit, + userId: OptionalId.parse(currentUserId) + } ) + const supportersForReceiverList = supporterMetadataListFromSDK(supporterData) const userIds = [ - ...(supportingForSenderList || []).map((supporting) => - decodeHashId(supporting.receiver.id) - ), - ...(supportersForReceiverList || []).map((supporter) => - decodeHashId(supporter.sender.id) - ) + ...supportingForSenderList.map((supporting) => supporting.receiver.user_id), + ...supportersForReceiverList.map((supporter) => supporter.sender.user_id) ].filter(removeNullable) yield call(fetchUsers, userIds) - const supportingForSenderMap: Record = {} - ;(supportingForSenderList || []).forEach((supporting) => { - const supportingUserId = decodeHashId(supporting.receiver.id) - if (supportingUserId) { - supportingForSenderMap[supportingUserId] = { - receiver_id: supportingUserId, - rank: supporting.rank, - amount: supporting.amount - } - } - }) - const supportersForReceiverMap: Record = {} - ;(supportersForReceiverList || []).forEach((supporter) => { - const supporterUserId = decodeHashId(supporter.sender.id) - if (supporterUserId) { - supportersForReceiverMap[supporterUserId] = { - sender_id: supporterUserId, - rank: supporter.rank, - amount: supporter.amount - } - } - }) + const supportingForSenderMap = tippingUtils.makeSupportingMapForUser( + supportingForSenderList + ) + const supportersForReceiverMap = tippingUtils.makeSupportersMapForUser( + supportersForReceiverList + ) yield put( setSupportingForUser({ @@ -725,36 +723,28 @@ function* fetchSupportersForUserAsync(action: FetchSupportingAction) { payload: { userId } } = action yield* waitForRead() - const apiClient = yield* getContext('apiClient') - - const supportersParams: GetSupportersArgs = { - userId, - limit: MAX_PROFILE_TOP_SUPPORTERS + 1 - } - - const supportersForReceiverList = yield* call( - [apiClient, apiClient.getSupporters], - supportersParams + const sdk = yield* getSDK() + const currentUserId = yield* select(getUserId) + + const { data = [] } = yield* call( + [sdk.full.users, sdk.full.users.getSupporters], + { + id: Id.parse(userId), + limit: MAX_PROFILE_TOP_SUPPORTERS + 1, + userId: OptionalId.parse(currentUserId) + } ) + const supportersForReceiverList = supporterMetadataListFromSDK(data) - const userIds = supportersForReceiverList - ?.map((supporter) => decodeHashId(supporter.sender.id)) - .filter(removeNullable) + const userIds = supportersForReceiverList.map( + (supporter) => supporter.sender.user_id + ) if (!userIds) return yield call(fetchUsers, userIds) - const supportersForReceiverMap: Record = {} - - supportersForReceiverList?.forEach((supporter) => { - const supporterUserId = decodeHashId(supporter.sender.id) - if (supporterUserId) { - supportersForReceiverMap[supporterUserId] = { - sender_id: supporterUserId, - rank: supporter.rank, - amount: supporter.amount - } - } - }) + const supportersForReceiverMap = tippingUtils.makeSupportersMapForUser( + supportersForReceiverList + ) yield put( setSupportersForUser({ @@ -771,7 +761,8 @@ function* fetchSupportingForUserAsync({ type: string }) { yield* waitForRead() - const apiClient = yield* getContext('apiClient') + const sdk = yield* getSDK() + const currentUserId = yield* select(getUserId) /** * If the user id is that of the logged in user, then @@ -786,28 +777,24 @@ function* fetchSupportingForUserAsync({ account?.user_id === userId ? account.supporting_count : SUPPORTING_PAGINATION_SIZE - const supportingList = yield* call([apiClient, apiClient.getSupporting], { - userId, - limit - }) - const userIds = - supportingList - ?.map((supporting) => decodeHashId(supporting.receiver.id)) - .filter(removeNullable) ?? [] + + const { data = [] } = yield* call( + [sdk.full.users, sdk.full.users.getSupportedUsers], + { + id: Id.parse(userId), + limit, + userId: OptionalId.parse(currentUserId) + } + ) + const supportingList = supportedUserMetadataListFromSDK(data) + + const userIds = supportingList.map( + (supporting) => supporting.receiver.user_id + ) yield call(fetchUsers, userIds) - const map: Record = {} - supportingList?.forEach((supporting) => { - const supportingUserId = decodeHashId(supporting.receiver.id) - if (supportingUserId) { - map[supportingUserId] = { - receiver_id: supportingUserId, - rank: supporting.rank, - amount: supporting.amount - } - } - }) + const map = tippingUtils.makeSupportingMapForUser(supportingList) yield put( setSupportingForUser({ @@ -902,14 +889,24 @@ function* fetchUserSupporterAsync( action: ReturnType ) { const { currentUserId, userId, supporterUserId } = action.payload - const apiClient = yield* getContext('apiClient') + const sdk = yield* getSDK() try { - const response = yield* call([apiClient, apiClient.getUserSupporter], { - currentUserId, - userId, - supporterUserId - }) - if (response) { + const { data } = yield* call( + [sdk.full.users, sdk.full.users.getSupporter], + { + id: Id.parse(userId), + supporterUserId: Id.parse(supporterUserId), + userId: OptionalId.parse(currentUserId) + } + ) + + if (!data) { + return + } + + const supporter = supporterMetadataFromSDK(data) + + if (supporter) { const supportingMap = yield* select(getSupporting) yield put( setSupportingForUser({ @@ -918,8 +915,8 @@ function* fetchUserSupporterAsync( ...supportingMap[supporterUserId], [userId]: { receiver_id: userId, - amount: response.amount, - rank: response.rank + amount: supporter.amount, + rank: supporter.rank } } }) @@ -933,8 +930,8 @@ function* fetchUserSupporterAsync( ...supportersMap[userId], [supporterUserId]: { sender_id: supporterUserId, - amount: response.amount, - rank: response.rank + amount: supporter.amount, + rank: supporter.rank } } }) diff --git a/packages/web/src/common/store/user-list/favorites/sagas.ts b/packages/web/src/common/store/user-list/favorites/sagas.ts index 9edeecc5a3c..2462b14f2bc 100644 --- a/packages/web/src/common/store/user-list/favorites/sagas.ts +++ b/packages/web/src/common/store/user-list/favorites/sagas.ts @@ -1,13 +1,22 @@ -import { FavoriteType, Collection, ID, Track } from '@audius/common/models' +import { + FavoriteType, + Collection, + ID, + Track, + Id, + userMetadataListFromSDK, + OptionalId +} from '@audius/common/models' import { cacheCollectionsSelectors, cacheTracksSelectors, UserListSagaFactory, favoritesUserListActions, favoritesUserListSelectors, - FAVORITES_USER_LIST_TAG + FAVORITES_USER_LIST_TAG, + getSDK } from '@audius/common/store' -import { select, put } from 'typed-redux-saga' +import { select, put, call } from 'typed-redux-saga' import { watchFavoriteError } from 'common/store/user-list/favorites/errorSagas' import { createUserListProvider } from 'common/store/user-list/utils' @@ -22,19 +31,24 @@ const getPlaylistFavorites = createUserListProvider({ getExistingEntity: getCollection, extractUserIDSubsetFromEntity: (collection: Collection) => collection.followee_saves.map((r) => r.user_id), - fetchAllUsersForEntity: async ({ + fetchAllUsersForEntity: function* ({ limit, offset, entityId, - currentUserId, - apiClient - }) => { - const users = await apiClient.getPlaylistFavoriteUsers({ - limit, - offset, - playlistId: entityId, - currentUserId - }) + currentUserId + }) { + const sdk = yield* getSDK() + + const { data } = yield* call( + [sdk.full.playlists, sdk.full.playlists.getUsersFromPlaylistFavorites], + { + limit, + offset, + playlistId: Id.parse(entityId), + userId: OptionalId.parse(currentUserId) + } + ) + const users = userMetadataListFromSDK(data) return { users } }, selectCurrentUserIDsInList: getUserIds, @@ -47,19 +61,25 @@ const getTrackFavorites = createUserListProvider({ getExistingEntity: getTrack, extractUserIDSubsetFromEntity: (track: Track) => track.followee_saves.map((r) => r.user_id), - fetchAllUsersForEntity: async ({ + fetchAllUsersForEntity: function* ({ limit, offset, entityId, - currentUserId, - apiClient - }) => { - const users = await apiClient.getTrackFavoriteUsers({ - limit, - offset, - trackId: entityId, - currentUserId - }) + currentUserId + }) { + const sdk = yield* getSDK() + + const { data } = yield* call( + [sdk.full.tracks, sdk.full.tracks.getUsersFromFavorites], + { + limit, + offset, + trackId: Id.parse(entityId), + userId: OptionalId.parse(currentUserId) + } + ) + const users = userMetadataListFromSDK(data) + return { users } }, selectCurrentUserIDsInList: getUserIds, diff --git a/packages/web/src/common/store/user-list/followers/sagas.ts b/packages/web/src/common/store/user-list/followers/sagas.ts index bcf2099381a..202991a229b 100644 --- a/packages/web/src/common/store/user-list/followers/sagas.ts +++ b/packages/web/src/common/store/user-list/followers/sagas.ts @@ -1,12 +1,19 @@ -import { ID, User } from '@audius/common/models' +import { + ID, + Id, + OptionalId, + User, + userMetadataListFromSDK +} from '@audius/common/models' import { cacheUsersSelectors, UserListSagaFactory, followersUserListActions, followersUserListSelectors, - FOLLOWERS_USER_LIST_TAG as USER_LIST_TAG + FOLLOWERS_USER_LIST_TAG as USER_LIST_TAG, + getSDK } from '@audius/common/store' -import { put, select } from 'typed-redux-saga' +import { call, put, select } from 'typed-redux-saga' import { watchFollowersError } from 'common/store/user-list/followers/errorSagas' import { createUserListProvider } from 'common/store/user-list/utils' @@ -17,19 +24,24 @@ const { getUser } = cacheUsersSelectors const provider = createUserListProvider({ getExistingEntity: getUser, extractUserIDSubsetFromEntity: () => [], - fetchAllUsersForEntity: async ({ + fetchAllUsersForEntity: function* ({ limit, offset, entityId, - currentUserId, - apiClient - }) => { - const users = await apiClient.getFollowers({ - currentUserId, - profileUserId: entityId, - limit, - offset - }) + currentUserId + }) { + const sdk = yield* getSDK() + + const { data } = yield* call( + [sdk.full.users, sdk.full.users.getFollowers], + { + id: Id.parse(entityId), + limit, + offset, + userId: OptionalId.parse(currentUserId) + } + ) + const users = userMetadataListFromSDK(data) return { users } }, selectCurrentUserIDsInList: getUserIds, diff --git a/packages/web/src/common/store/user-list/following/sagas.ts b/packages/web/src/common/store/user-list/following/sagas.ts index e20e871dba1..615c9bb4ead 100644 --- a/packages/web/src/common/store/user-list/following/sagas.ts +++ b/packages/web/src/common/store/user-list/following/sagas.ts @@ -1,12 +1,19 @@ -import { ID, User } from '@audius/common/models' +import { + ID, + Id, + OptionalId, + User, + userMetadataListFromSDK +} from '@audius/common/models' import { cacheUsersSelectors, UserListSagaFactory, followingUserListActions, followingUserListSelectors, - FOLLOWING_USER_LIST_TAG + FOLLOWING_USER_LIST_TAG, + getSDK } from '@audius/common/store' -import { put, select } from 'typed-redux-saga' +import { call, put, select } from 'typed-redux-saga' import { watchFollowingError } from 'common/store/user-list/following/errorSagas' import { createUserListProvider } from 'common/store/user-list/utils' @@ -17,19 +24,24 @@ const { getUser } = cacheUsersSelectors const provider = createUserListProvider({ getExistingEntity: getUser, extractUserIDSubsetFromEntity: () => [], - fetchAllUsersForEntity: async ({ + fetchAllUsersForEntity: function* ({ limit, offset, entityId, - currentUserId, - apiClient - }) => { - const users = await apiClient.getFollowing({ - currentUserId, - profileUserId: entityId, - limit, - offset - }) + currentUserId + }) { + const sdk = yield* getSDK() + const { data } = yield* call( + [sdk.full.users, sdk.full.users.getFollowing], + { + id: Id.parse(entityId), + limit, + offset, + userId: OptionalId.parse(currentUserId) + } + ) + const users = userMetadataListFromSDK(data) + return { users } }, selectCurrentUserIDsInList: getUserIds, diff --git a/packages/web/src/common/store/user-list/mutuals/sagas.ts b/packages/web/src/common/store/user-list/mutuals/sagas.ts index 782fdc7bf0f..4083aea8dbc 100644 --- a/packages/web/src/common/store/user-list/mutuals/sagas.ts +++ b/packages/web/src/common/store/user-list/mutuals/sagas.ts @@ -1,13 +1,13 @@ import { ID, User } from '@audius/common/models' -import { AudiusBackend } from '@audius/common/services' import { cacheUsersSelectors, UserListSagaFactory, mutualsUserListActions, mutualsUserListSelectors, - MUTUALS_USER_LIST_TAG + MUTUALS_USER_LIST_TAG, + getContext } from '@audius/common/store' -import { put, select } from 'typed-redux-saga' +import { call, put, select } from 'typed-redux-saga' import { watchMutualsError } from 'common/store/user-list/mutuals/errorSagas' import { createUserListProvider } from 'common/store/user-list/utils' @@ -20,16 +20,17 @@ type FetchMutualsConfig = { offset: number entityId: ID currentUserId: ID | null - audiusBackendInstance: AudiusBackend } -const fetchAllUsersForEntity = async ({ +const fetchMutualFollowers = function* ({ limit, offset, - entityId: userId, - audiusBackendInstance -}: FetchMutualsConfig) => { - const mutuals = await audiusBackendInstance.getFolloweeFollows( + entityId: userId +}: FetchMutualsConfig) { + const audiusBackendInstance = yield* getContext('audiusBackendInstance') + + const mutuals = yield* call( + [audiusBackendInstance, audiusBackendInstance.getFolloweeFollows], userId, limit, offset @@ -40,7 +41,7 @@ const fetchAllUsersForEntity = async ({ const provider = createUserListProvider({ getExistingEntity: getUser, extractUserIDSubsetFromEntity: () => [], - fetchAllUsersForEntity, + fetchAllUsersForEntity: fetchMutualFollowers, selectCurrentUserIDsInList: getUserIds, canFetchMoreUsers: (user: User, combinedUserIDs: ID[]) => combinedUserIDs.length < user.current_user_followee_follow_count, diff --git a/packages/web/src/common/store/user-list/reposts/sagas.ts b/packages/web/src/common/store/user-list/reposts/sagas.ts index 0df2232aeb2..d3b5c56dbcd 100644 --- a/packages/web/src/common/store/user-list/reposts/sagas.ts +++ b/packages/web/src/common/store/user-list/reposts/sagas.ts @@ -1,4 +1,11 @@ -import { Collection, ID, Track } from '@audius/common/models' +import { + Collection, + ID, + Id, + OptionalId, + Track, + userMetadataListFromSDK +} from '@audius/common/models' import { cacheCollectionsSelectors, cacheTracksSelectors, @@ -6,9 +13,10 @@ import { repostsUserListActions, repostsUserListSelectors, RepostType, - REPOSTS_USER_LIST_TAG + REPOSTS_USER_LIST_TAG, + getSDK } from '@audius/common/store' -import { put, select } from 'typed-redux-saga' +import { call, put, select } from 'typed-redux-saga' import { watchRepostsError } from 'common/store/user-list/reposts/errorSagas' import { createUserListProvider } from 'common/store/user-list/utils' @@ -22,19 +30,25 @@ const getPlaylistReposts = createUserListProvider({ getExistingEntity: getCollection, extractUserIDSubsetFromEntity: (collection: Collection) => collection.followee_reposts.map((r) => r.user_id), - fetchAllUsersForEntity: async ({ + fetchAllUsersForEntity: function* ({ limit, offset, entityId, - currentUserId, - apiClient - }) => { - const users = await apiClient.getPlaylistRepostUsers({ - limit, - offset, - playlistId: entityId, - currentUserId - }) + currentUserId + }) { + const sdk = yield* getSDK() + + const { data } = yield* call( + [sdk.full.playlists, sdk.full.playlists.getUsersFromPlaylistReposts], + { + limit, + offset, + playlistId: Id.parse(entityId), + userId: OptionalId.parse(currentUserId) + } + ) + const users = userMetadataListFromSDK(data) + return { users } }, selectCurrentUserIDsInList: getUserIds, @@ -47,19 +61,24 @@ const getTrackReposts = createUserListProvider({ getExistingEntity: getTrack, extractUserIDSubsetFromEntity: (track: Track) => track.followee_reposts.map((r) => r.user_id), - fetchAllUsersForEntity: async ({ + fetchAllUsersForEntity: function* ({ limit, offset, entityId, - currentUserId, - apiClient - }) => { - const users = await apiClient.getTrackRepostUsers({ - limit, - offset, - trackId: entityId, - currentUserId - }) + currentUserId + }) { + const sdk = yield* getSDK() + const { data } = yield* call( + [sdk.full.tracks, sdk.full.tracks.getUsersFromReposts], + { + limit, + offset, + trackId: Id.parse(entityId), + userId: OptionalId.parse(currentUserId) + } + ) + const users = userMetadataListFromSDK(data) + return { users } }, selectCurrentUserIDsInList: getUserIds, diff --git a/packages/web/src/common/store/user-list/supporting/sagas.ts b/packages/web/src/common/store/user-list/supporting/sagas.ts index 48b04005d1d..5e52ab3e5bc 100644 --- a/packages/web/src/common/store/user-list/supporting/sagas.ts +++ b/packages/web/src/common/store/user-list/supporting/sagas.ts @@ -1,8 +1,11 @@ -import { ID, UserMetadata, User } from '@audius/common/models' import { - responseAdapter as adapter, - SupportingResponse -} from '@audius/common/services' + ID, + User, + Id, + supportedUserMetadataListFromSDK, + OptionalId, + SupportedUserMetadata +} from '@audius/common/models' import { cacheUsersSelectors, tippingActions, @@ -10,10 +13,11 @@ import { supportingUserListActions, supportingUserListSelectors, SUPPORTING_USER_LIST_TAG, - SupportingMapForUser + getSDK, + tippingUtils } from '@audius/common/store' -import { decodeHashId, stringWeiToBN } from '@audius/common/utils' -import { put, select } from 'typed-redux-saga' +import { stringWeiToBN } from '@audius/common/utils' +import { call, put, select } from 'typed-redux-saga' import { watchSupportingError } from 'common/store/user-list/supporting/errorSagas' import { createUserListProvider } from 'common/store/user-list/utils' @@ -24,27 +28,37 @@ const { getUser } = cacheUsersSelectors type SupportingProcessExtraType = { userId: ID - supportingList: SupportingResponse[] + supportingList: SupportedUserMetadata[] } const provider = createUserListProvider({ getExistingEntity: getUser, extractUserIDSubsetFromEntity: () => [], - fetchAllUsersForEntity: async ({ limit, offset, entityId, apiClient }) => { - const supporting = - (await apiClient.getSupporting({ - userId: entityId, + fetchAllUsersForEntity: function* ({ + limit, + offset, + entityId, + currentUserId + }) { + const sdk = yield* getSDK() + const { data = [] } = yield* call( + [sdk.full.users, sdk.full.users.getSupportedUsers], + { + id: Id.parse(entityId), limit, - offset - })) || [] + offset, + userId: OptionalId.parse(currentUserId) + } + ) + const supporting = supportedUserMetadataListFromSDK(data) + const users = supporting .sort((s1, s2) => { const amount1BN = stringWeiToBN(s1.amount) const amount2BN = stringWeiToBN(s2.amount) return amount1BN.gte(amount2BN) ? -1 : 1 }) - .map((s) => adapter.makeUser(s.receiver)) - .filter((user): user is UserMetadata => !!user) + .map((s) => s.receiver) return { users, extra: { userId: entityId, supportingList: supporting } } }, selectCurrentUserIDsInList: getUserIds, @@ -59,17 +73,7 @@ const provider = createUserListProvider({ * in the interface, to update the store. */ processExtra: function* ({ userId, supportingList }) { - const supportingMap: SupportingMapForUser = {} - supportingList.forEach((supporting: SupportingResponse) => { - const supportingUserId = decodeHashId(supporting.receiver.id) - if (supportingUserId) { - supportingMap[supportingUserId] = { - receiver_id: supportingUserId, - rank: supporting.rank, - amount: supporting.amount - } - } - }) + const supportingMap = tippingUtils.makeSupportingMapForUser(supportingList) yield put( setSupportingForUser({ id: userId, diff --git a/packages/web/src/common/store/user-list/top-supporters/sagas.ts b/packages/web/src/common/store/user-list/top-supporters/sagas.ts index ac61d936a84..1c9340a62e3 100644 --- a/packages/web/src/common/store/user-list/top-supporters/sagas.ts +++ b/packages/web/src/common/store/user-list/top-supporters/sagas.ts @@ -1,8 +1,11 @@ -import { ID, User } from '@audius/common/models' import { - responseAdapter as adapter, - SupporterResponse -} from '@audius/common/services' + ID, + Id, + OptionalId, + SupporterMetadata, + User, + supporterMetadataListFromSDK +} from '@audius/common/models' import { cacheUsersSelectors, tippingActions, @@ -10,10 +13,10 @@ import { topSupportersUserListActions, topSupportersUserListSelectors, TOP_SUPPORTERS_USER_LIST_TAG, - SupportersMapForUser + getSDK, + tippingUtils } from '@audius/common/store' -import { decodeHashId, removeNullable } from '@audius/common/utils' -import { put, select } from 'typed-redux-saga' +import { call, put, select } from 'typed-redux-saga' import { watchTopSupportersError } from 'common/store/user-list/top-supporters/errorSagas' import { createUserListProvider } from 'common/store/user-list/utils' @@ -24,23 +27,34 @@ const { getUser } = cacheUsersSelectors type SupportersProcessExtraType = { userId: ID - supporters: SupporterResponse[] + supporters: SupporterMetadata[] } const provider = createUserListProvider({ getExistingEntity: getUser, extractUserIDSubsetFromEntity: () => [], - fetchAllUsersForEntity: async ({ limit, offset, entityId, apiClient }) => { - const supporters = - (await apiClient.getSupporters({ - userId: entityId, + fetchAllUsersForEntity: function* ({ + limit, + offset, + entityId, + currentUserId + }) { + const sdk = yield* getSDK() + + const { data = [] } = yield* call( + [sdk.full.users, sdk.full.users.getSupporters], + { + id: Id.parse(entityId), limit, - offset - })) || [] + offset, + userId: OptionalId.parse(currentUserId) + } + ) + const supporters = supporterMetadataListFromSDK(data) + const users = supporters .sort((s1, s2) => s1.rank - s2.rank) - .map((s) => adapter.makeUser(s.sender)) - .filter(removeNullable) + .map((s) => s.sender) return { users, extra: { userId: entityId, supporters } } }, selectCurrentUserIDsInList: getUserIds, @@ -55,17 +69,8 @@ const provider = createUserListProvider({ * in the interface, to update the store. */ processExtra: function* ({ userId, supporters }) { - const supportersMap: SupportersMapForUser = {} - supporters.forEach((supporter: SupporterResponse) => { - const supporterUserId = decodeHashId(supporter.sender.id) - if (supporterUserId) { - supportersMap[supporterUserId] = { - sender_id: supporterUserId, - rank: supporter.rank, - amount: supporter.amount - } - } - }) + const supportersMap = tippingUtils.makeSupportersMapForUser(supporters) + yield* put( setSupportersForUser({ id: userId, diff --git a/packages/web/src/common/store/user-list/utils.ts b/packages/web/src/common/store/user-list/utils.ts index 02877b71f36..c49b80dd6b8 100644 --- a/packages/web/src/common/store/user-list/utils.ts +++ b/packages/web/src/common/store/user-list/utils.ts @@ -1,11 +1,6 @@ import { ID, UserMetadata, User } from '@audius/common/models' -import { AudiusAPIClient, AudiusBackend } from '@audius/common/services' -import { - accountSelectors, - processAndCacheUsers, - getContext -} from '@audius/common/store' -import { call, select } from 'typed-redux-saga' +import { accountSelectors, processAndCacheUsers } from '@audius/common/store' +import { SagaGenerator, call, select } from 'typed-redux-saga' import { AppState } from 'store/types' import { waitForRead } from 'utils/sagaHelpers' @@ -28,9 +23,7 @@ export type UserListProviderArgs = { offset: number entityId: ID currentUserId: ID | null - audiusBackendInstance: AudiusBackend - apiClient: AudiusAPIClient - }) => Promise<{ users: UserMetadata[]; extra?: U }> + }) => SagaGenerator<{ users: UserMetadata[]; extra?: U }> includeCurrentUser: (entity: T) => boolean @@ -71,8 +64,6 @@ export function createUserListProvider({ pageSize: number }) { yield* waitForRead() - const audiusBackendInstance = yield* getContext('audiusBackendInstance') - const apiClient = yield* getContext('apiClient') const existingEntity: T | null = yield* select(getExistingEntity, { id }) if (!existingEntity) return { userIds: [], hasMore: false } @@ -82,14 +73,13 @@ export function createUserListProvider({ const userId = yield* select(getUserId) // Get the next page of users const offset = currentPage * pageSize - const { users: allUsers, extra } = yield* call(fetchAllUsersForEntity, { + const { users: allUsers, extra } = yield* fetchAllUsersForEntity({ limit: pageSize, offset, entityId: id, - currentUserId: userId, - audiusBackendInstance, - apiClient + currentUserId: userId }) + if (includeCurrentUser(existingEntity)) { const currentUser = yield* select(getAccountUser) if (currentUser) { diff --git a/packages/web/src/components/tipping/support/SupportingList.tsx b/packages/web/src/components/tipping/support/SupportingList.tsx index 20862c5ac30..0810252b786 100644 --- a/packages/web/src/components/tipping/support/SupportingList.tsx +++ b/packages/web/src/components/tipping/support/SupportingList.tsx @@ -56,7 +56,10 @@ const SupportingListForProfile = ({ profile }: { profile: User }) => { {rankedSupportingList .slice(0, MAX_PROFILE_SUPPORTING_TILES) .map((supporting) => ( - + ))} {profile.supporting_count > MAX_PROFILE_SUPPORTING_TILES ? (