From 8470ce0ba0c8bf878558bbded89cf165e56baca4 Mon Sep 17 00:00:00 2001 From: Michael Piazza Date: Fri, 18 Sep 2020 15:36:47 -0700 Subject: [PATCH 01/14] Successfully hitting API --- src/app.tsx | 11 +- src/components/artist/ArtistChip.js | 3 - src/components/artist/UserListModal.js | 1 - .../artist/mobile/UserListModal.tsx | 1 - src/components/card/UserCard.js | 3 - src/components/card/desktop/Card.tsx | 1 - .../discover-page/components/EmptyFeed.js | 1 - .../discover-page/components/FollowUsers.js | 1 - .../store/lineups/trending/sagas.js | 5 + .../profile-page/ProfilePageProvider.tsx | 5 - .../components/desktop/ProfileWrapping.js | 1 - src/containers/profile-page/store/sagas.js | 5 - .../components/mobile/SavedPage.tsx | 1 - src/containers/search-bar/SearchBar.js | 1 - .../components/desktop/SearchPageContent.js | 1 - .../sign-on/components/desktop/FollowPage.tsx | 1 - .../sign-on/components/mobile/FollowPage.tsx | 1 - .../track-page/TrackPageProvider.tsx | 5 +- .../user-list/components/UserList.tsx | 1 - src/models/Favorite.ts | 7 +- src/models/Repost.ts | 6 +- src/models/Track.ts | 76 +++-- src/models/User.ts | 34 +- .../audius-api-client/AudiusAPIClient.ts | 294 ++++++++++++++++++ src/store/cache/tracks/utils/reformat.ts | 3 +- src/store/player/sagas.ts | 3 +- src/utils/typeUtils.ts | 3 + 27 files changed, 382 insertions(+), 93 deletions(-) create mode 100644 src/services/audius-api-client/AudiusAPIClient.ts diff --git a/src/app.tsx b/src/app.tsx index 456160b28f..f8348e8fab 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import React, { useEffect } from 'react' import { Provider } from 'react-redux' import { ConnectedRouter } from 'connected-react-router' @@ -10,6 +10,7 @@ import AppContext from 'AppContext' import App from './containers/App' import './services/webVitals' import './index.css' +import AudiusAPIClient from 'services/audius-api-client/AudiusAPIClient' declare global { interface Window { @@ -34,6 +35,14 @@ const AudiusApp = ({ setConnectivityFailure, shouldShowPopover }: AudiusAppProps) => { + useEffect(() => { + const fn = async () => { + const client = new AudiusAPIClient({ environment: 'development' }) + await client.init() + await client.getTrending('week') + } + fn() + }, []) return ( diff --git a/src/components/artist/ArtistChip.js b/src/components/artist/ArtistChip.js index 32b089645a..d2979516d5 100644 --- a/src/components/artist/ArtistChip.js +++ b/src/components/artist/ArtistChip.js @@ -8,7 +8,6 @@ import DynamicImage from 'components/dynamic-image/DynamicImage' import { SquareSizes } from 'models/common/ImageSizes' import { useUserProfilePicture } from 'hooks/useImageSize' -import placeholderProfilePicture from 'assets/img/imageProfilePicEmpty2X.png' import { ReactComponent as IconVerified } from 'assets/img/iconVerified.svg' import styles from './ArtistChip.module.css' @@ -55,7 +54,6 @@ ArtistChip.propTypes = { className: PropTypes.string, userId: PropTypes.number, profilePictureSizes: PropTypes.object, - profileImgUrl: PropTypes.string, name: PropTypes.string, handle: PropTypes.string, followers: PropTypes.number, @@ -65,7 +63,6 @@ ArtistChip.propTypes = { } ArtistChip.defaultProps = { - profileImgUrl: placeholderProfilePicture, name: '', followers: 0, verified: false, diff --git a/src/components/artist/UserListModal.js b/src/components/artist/UserListModal.js index 85cd2f888b..7648d26cc8 100644 --- a/src/components/artist/UserListModal.js +++ b/src/components/artist/UserListModal.js @@ -56,7 +56,6 @@ export const UserListModal = props => { userId={user.user_id} followers={user.follower_count} verified={user.is_verified} - profileImgUrl={user.profile_picture_url} onClickArtistName={() => { props.onClose() props.onClickArtistName(user.handle) diff --git a/src/components/artist/mobile/UserListModal.tsx b/src/components/artist/mobile/UserListModal.tsx index 2805c8309a..a5b6f69874 100644 --- a/src/components/artist/mobile/UserListModal.tsx +++ b/src/components/artist/mobile/UserListModal.tsx @@ -96,7 +96,6 @@ export const UserListModal = (props: UserListModalProps) => { className={styles.artistChipContainer} followers={user.follower_count} verified={user.is_verified} - profileImgUrl={user.profile_picture_url} onClickArtistName={() => { props.onClose() props.onClickArtistName(user.handle) diff --git a/src/components/card/UserCard.js b/src/components/card/UserCard.js index 2c3055f2a7..d3dfbb6213 100644 --- a/src/components/card/UserCard.js +++ b/src/components/card/UserCard.js @@ -4,7 +4,6 @@ import cn from 'classnames' import { formatCount } from 'utils/formatUtil' import styles from './UserCard.module.css' -import placeholderArt from 'assets/img/imageProfilePicEmpty2X.png' import { ReactComponent as IconVerified } from 'assets/img/iconVerified.svg' import DynamicImage from 'components/dynamic-image/DynamicImage' import { SquareSizes } from 'models/common/ImageSizes' @@ -57,7 +56,6 @@ const UserCard = ({ UserCard.propTypes = { className: PropTypes.string, name: PropTypes.string, - profileImageUrl: PropTypes.string, isVerified: PropTypes.bool, isMobile: PropTypes.bool, followers: PropTypes.number, @@ -67,7 +65,6 @@ UserCard.propTypes = { UserCard.defaultProps = { isVerified: true, - profileImageUrl: placeholderArt, onFollow: () => {}, name: '', followers: 0, diff --git a/src/components/card/desktop/Card.tsx b/src/components/card/desktop/Card.tsx index e28423edde..8d865174af 100644 --- a/src/components/card/desktop/Card.tsx +++ b/src/components/card/desktop/Card.tsx @@ -50,7 +50,6 @@ type CardProps = { imageSize: ProfilePictureSizes | CoverArtSizes | null primaryText: string | React.ReactNode secondaryText: string | React.ReactNode - cardCoverImageUrl?: string cardCoverImageSizes?: CoverArtSizes playlistName?: string isUser: boolean diff --git a/src/containers/discover-page/components/EmptyFeed.js b/src/containers/discover-page/components/EmptyFeed.js index 8ee0f5b950..f343e8e577 100644 --- a/src/containers/discover-page/components/EmptyFeed.js +++ b/src/containers/discover-page/components/EmptyFeed.js @@ -76,7 +76,6 @@ EmptyFeed.propTypes = { PropTypes.shape({ user_id: PropTypes.number, name: PropTypes.string, - profile_picture_url: PropTypes.string, handle: PropTypes.string, is_verified: PropTypes.bool, follower_count: PropTypes.number diff --git a/src/containers/discover-page/components/FollowUsers.js b/src/containers/discover-page/components/FollowUsers.js index 979aed6ff6..06fcaa673f 100644 --- a/src/containers/discover-page/components/FollowUsers.js +++ b/src/containers/discover-page/components/FollowUsers.js @@ -50,7 +50,6 @@ const FollowUsers = props => { name={user.name} selected={followUsers.includes(user.user_id)} onClick={onUserClick(user.user_id)} - profileImageUrl={user.profile_picture_url} isVerified={user.is_verified} followers={user.follower_count} /> diff --git a/src/containers/discover-page/store/lineups/trending/sagas.js b/src/containers/discover-page/store/lineups/trending/sagas.js index 1d2ca34806..5a7769d318 100644 --- a/src/containers/discover-page/store/lineups/trending/sagas.js +++ b/src/containers/discover-page/store/lineups/trending/sagas.js @@ -41,9 +41,13 @@ function* getTrendingOrderAndStats(timeRange, genre) { timeRange, genre: genre }) + + // A sorted array of Ids const trendingOrder = trendingResponse.listen_counts .sort(sortByTrendingScore(timeRange)) .map(t => t.track_id) + + // Makes a map of id => score const trendingStats = trendingResponse.listen_counts.reduce((stats, t) => { stats[t.track_id] = { ...t, @@ -79,6 +83,7 @@ function makeGetTracks(timeRangeProvider) { } function* getTracks({ timeRangeProvider, offset, limit }) { + // Get the time range const timeRange = yield timeRangeProvider() const [getTrendingOrder, getTrendingStats] = [ makeGetTrendingOrder(timeRange), diff --git a/src/containers/profile-page/ProfilePageProvider.tsx b/src/containers/profile-page/ProfilePageProvider.tsx index f388b6009b..c34e5da6f0 100644 --- a/src/containers/profile-page/ProfilePageProvider.tsx +++ b/src/containers/profile-page/ProfilePageProvider.tsx @@ -286,16 +286,11 @@ class ProfilePage extends PureComponent { updatedProfilePicture: { file, url, source } }) } catch (err) { - const { - profile: { profile } - } = this.props const { updatedProfilePicture } = this.state this.setState({ updatedProfilePicture: { ...(updatedProfilePicture && updatedProfilePicture.url ? this.state.updatedProfilePicture - : profile - ? { url: profile.cover_photo_url } : {}), error: err.message } diff --git a/src/containers/profile-page/components/desktop/ProfileWrapping.js b/src/containers/profile-page/components/desktop/ProfileWrapping.js index e29699dd04..033f3e364d 100644 --- a/src/containers/profile-page/components/desktop/ProfileWrapping.js +++ b/src/containers/profile-page/components/desktop/ProfileWrapping.js @@ -74,7 +74,6 @@ const Followers = props => { followers={follower.follower_count} onClickArtistName={() => props.onClickArtistName(follower.handle)} verified={follower.is_verified} - profileImgUrl={follower.profile_picture_url} /> ))} diff --git a/src/containers/profile-page/store/sagas.js b/src/containers/profile-page/store/sagas.js index 49f43aa52f..c4f89c8d96 100644 --- a/src/containers/profile-page/store/sagas.js +++ b/src/containers/profile-page/store/sagas.js @@ -251,15 +251,10 @@ function* updateProfileAsync(action) { if (action.metadata.updatedCoverPhoto) { action.metadata._cover_photo_sizes[DefaultSizes.OVERRIDE] = action.metadata.updatedCoverPhoto.url - // TODO: clean up cover_photo_url, which is to support legacy images - action.metadata.cover_photo_url = action.metadata.updatedCoverPhoto.url } if (creator.updatedProfilePicture) { action.metadata._profile_picture_sizes[DefaultSizes.OVERRIDE] = action.metadata.updatedProfilePicture.url - // TODO: clean up profile_picture_url, which is to support legacy images - action.metadata.profile_picture_url = - action.metadata.updatedProfilePicture.url } yield put( diff --git a/src/containers/saved-page/components/mobile/SavedPage.tsx b/src/containers/saved-page/components/mobile/SavedPage.tsx index 55690cecd8..a7256c4a49 100644 --- a/src/containers/saved-page/components/mobile/SavedPage.tsx +++ b/src/containers/saved-page/components/mobile/SavedPage.tsx @@ -113,7 +113,6 @@ const TracksLineup = ({ isPlaying: queuedAndPlaying && playingUid === entry.uid, artistName: entry.user.name, artistHandle: entry.user.handle, - coverArtUrl: entry.cover_art_url, trackTitle: entry.title, trackId: entry.track_id, uid: entry.uid, diff --git a/src/containers/search-bar/SearchBar.js b/src/containers/search-bar/SearchBar.js index 9b28520279..ddefab1dba 100644 --- a/src/containers/search-bar/SearchBar.js +++ b/src/containers/search-bar/SearchBar.js @@ -152,7 +152,6 @@ class SearchBar extends Component { ? track.user.creator_node_endpoint : '', defaultImage: placeholderArt, - imageUrl: track.cover_art_url, isVerifiedUser: track.user ? track.user.is_verified : false } }) diff --git a/src/containers/search-page/components/desktop/SearchPageContent.js b/src/containers/search-page/components/desktop/SearchPageContent.js index 10945ddb48..5f5b2bc628 100644 --- a/src/containers/search-page/components/desktop/SearchPageContent.js +++ b/src/containers/search-page/components/desktop/SearchPageContent.js @@ -151,7 +151,6 @@ class SearchPageContent extends Component { size={'small'} primaryText={artist.name} secondaryText={`${formatCount(artist.follower_count)} Followers`} - cardCoverImageUrl={artist.profile_picture_url} onClick={onClick} menu={{ type: 'user', diff --git a/src/containers/sign-on/components/desktop/FollowPage.tsx b/src/containers/sign-on/components/desktop/FollowPage.tsx index 42ad494103..1f0ddeaad2 100644 --- a/src/containers/sign-on/components/desktop/FollowPage.tsx +++ b/src/containers/sign-on/components/desktop/FollowPage.tsx @@ -124,7 +124,6 @@ export const FollowPage = (props: FollowPageProps) => { imageSizes={user._profile_picture_sizes} selected={followedArtists.includes(user.user_id)} className={cn(styles.userCard)} - profileImageUrl={user.profile_picture_url} isVerified={user.is_verified} followers={user.follower_count} onClick={onToggleSelect(user.user_id)} diff --git a/src/containers/sign-on/components/mobile/FollowPage.tsx b/src/containers/sign-on/components/mobile/FollowPage.tsx index 73c568b297..110061f93d 100644 --- a/src/containers/sign-on/components/mobile/FollowPage.tsx +++ b/src/containers/sign-on/components/mobile/FollowPage.tsx @@ -121,7 +121,6 @@ const FollowPage = ({ imageSizes={user._profile_picture_sizes} selected={followedArtists.includes(user.user_id)} className={styles.userCard} - profileImageUrl={user.profile_picture_url} isVerified={user.is_verified} followers={user.follower_count} onClick={onToggleSelect(user.user_id)} diff --git a/src/containers/track-page/TrackPageProvider.tsx b/src/containers/track-page/TrackPageProvider.tsx index ce7d799467..513db44b0d 100644 --- a/src/containers/track-page/TrackPageProvider.tsx +++ b/src/containers/track-page/TrackPageProvider.tsx @@ -395,11 +395,10 @@ class TrackPageProvider extends Component< }) const releaseDate = track ? track.release_date || track.created_at : '' - const mood = track ? track.mood : '' const description = getTrackPageDescription({ releaseDate: releaseDate ? formatDate(releaseDate) : '', - description: track ? track.description : '', - mood, + description: track?.description ?? '', + mood: track?.description ?? '', genre: track ? getCannonicalName(track.genre) : '', duration: track ? formatSeconds(track.duration) : '', tags: track ? (track.tags || '').split(',').filter(Boolean) : [] diff --git a/src/containers/user-list/components/UserList.tsx b/src/containers/user-list/components/UserList.tsx index dd8f842efd..806e556ab6 100644 --- a/src/containers/user-list/components/UserList.tsx +++ b/src/containers/user-list/components/UserList.tsx @@ -47,7 +47,6 @@ const UserList = (props: UserListProps) => { className={styles.artistChipContainer} followers={user.follower_count} verified={user.is_verified} - profileImgUrl={user.profile_picture_url} onClickArtistName={() => { props.onClickArtistName(user.handle) }} diff --git a/src/models/Favorite.ts b/src/models/Favorite.ts index 62d05c8bef..f7261fc32e 100644 --- a/src/models/Favorite.ts +++ b/src/models/Favorite.ts @@ -1,4 +1,3 @@ -import OnChain from 'models/common/OnChain' import { ID } from './common/Identifiers' export enum FavoriteType { @@ -6,10 +5,8 @@ export enum FavoriteType { PLAYLIST = 'playlist' } -type Favorite = OnChain & { - created_at: string - is_current: boolean - is_delete: boolean +type Favorite = { + // is_delete: boolean save_item_id: ID save_type: FavoriteType user_id: number diff --git a/src/models/Repost.ts b/src/models/Repost.ts index 58a24e3d74..b5dcd48a37 100644 --- a/src/models/Repost.ts +++ b/src/models/Repost.ts @@ -1,10 +1,6 @@ -import OnChain from 'models/common/OnChain' import { ID } from './common/Identifiers' -type Repost = OnChain & { - created_at: string - is_current: boolean - is_delete: boolean +type Repost = { repost_item_id: number repost_type: string user_id: ID diff --git a/src/models/Track.ts b/src/models/Track.ts index 7827881028..9db950ad88 100644 --- a/src/models/Track.ts +++ b/src/models/Track.ts @@ -1,12 +1,12 @@ import Color from 'models/common/Color' import { CID, ID, UID } from 'models/common/Identifiers' import { CoverArtSizes } from 'models/common/ImageSizes' -import OnChain from 'models/common/OnChain' import Repost from 'models/Repost' import User from 'models/User' import Favorite from 'models/Favorite' import Timestamped from './common/Timestamped' import { StemCategory } from './Stems' +import { Nullable } from 'utils/typeUtils' export interface TrackSegment { duration: string @@ -20,9 +20,13 @@ interface Followee extends User { } export interface Download { - is_downloadable: boolean - requires_follow: boolean - cid: string | null + // TODO: figure out why + // is_downloadable and requires_follow + // are randomly null on some tracks + // returned from the API + is_downloadable: Nullable + requires_follow: Nullable + cid: Nullable } export type FieldVisibility = { @@ -46,43 +50,51 @@ export type RemixOf = { } export type TrackMetadata = { - create_date: string | null + // create_date: string | null + // file_type: string | null + // is_current: boolean + // length: number | null + // cover_art_url: string + activity_timestamp?: string + is_delete: boolean + track_id: number created_at: string - isrc: string | null - iswc: string | null - credits_splits: string | null - description: string - file_type: string | null + isrc: Nullable + iswc: Nullable + credits_splits: Nullable + description: Nullable followee_reposts: Repost[] followee_saves: Favorite[] genre: string has_current_user_reposted: boolean - is_current: boolean - download: Download | null - length: number | null - license: string - mood: string + has_current_user_saved: boolean + download: Nullable + license: Nullable + mood: Nullable play_count: number owner_id: ID - release_date: string + release_date: Nullable repost_count: number save_count: number - tags: string + tags: Nullable title: string track_segments: TrackSegment[] - cover_art: CID | null - cover_art_sizes: CID + cover_art: Nullable is_unlisted: boolean field_visibility?: FieldVisibility listenCount?: number - dateListened?: string - duration: number + + // Optional Fields is_invalid?: boolean stem_of?: { parent_track_id: ID category: StemCategory } - remix_of?: RemixOf + remix_of: Nullable + + // Added fields + dateListened?: string + duration: number } & Timestamped export type Stem = { @@ -90,31 +102,29 @@ export type Stem = { category: StemCategory } -export type Track = { - activity_timestamp?: string - has_current_user_saved: boolean - is_delete: boolean - metadata_multihash: CID - track_id: number - cover_art_url: string +export type LibsTrack = TrackMetadata + +export type ComputedTrackProperties = { + // All below, added clientside _cover_art_sizes: CoverArtSizes _first_segment?: string _followees?: Followee[] _cover_art_color?: Color _marked_deleted?: boolean _is_publishing?: boolean + // Computed! _stems?: Stem[] // Present iff remixes have been fetched for a track _remixes?: Array<{ track_id: ID }> _remixes_count?: number - // Present iff remix parents have been fetched for a track _remix_parents?: Array<{ track_id: ID }> // Present iff the track has been cosigned - _co_sign?: Remix | null -} & OnChain & - TrackMetadata + _co_sign?: Nullable +} + +export type Track = LibsTrack & ComputedTrackProperties export type UserTrack = Track & { user: User diff --git a/src/models/User.ts b/src/models/User.ts index 3a116eb68e..bda9a7bf90 100644 --- a/src/models/User.ts +++ b/src/models/User.ts @@ -1,14 +1,17 @@ import Color from 'models/common/Color' import { CID, ID } from 'models/common/Identifiers' import { CoverPhotoSizes, ProfilePictureSizes } from 'models/common/ImageSizes' -import OnChain from 'models/common/OnChain' -import Timestamped from 'models/common/Timestamped' +import { Nullable } from 'utils/typeUtils' +import Timestamped from './common/Timestamped' + +export type UserMetadata = { + // profile_picture_url: string + // cover_photo_url: string -export default interface User extends OnChain, Timestamped { album_count: number bio: string | null - cover_photo: CID | null - creator_node_endpoint: string | null + cover_photo: Nullable + creator_node_endpoint: Nullable current_user_followee_follow_count: number does_current_user_follow: boolean followee_count: number @@ -16,30 +19,33 @@ export default interface User extends OnChain, Timestamped { handle: string handle_lc: string is_creator: boolean - is_current: boolean - is_ready: boolean is_verified: boolean - location: string | null - metadata_multihash: CID | null + location: Nullable name: string playlist_count: number - profile_picture: CID - profile_picture_sizes?: CID + profile_picture: Nullable repost_count: number track_count: number + cover_photo_sizes: Nullable + profile_picture_sizes: Nullable + // Only present on the "current" account track_save_count?: number user_id: number - wallet: string - profile_picture_url: string - cover_photo_url: string twitter_handle?: string instagram_handle?: string website?: string donation?: string +} & Timestamped + +export type ComputedUserProperties = { _profile_picture_sizes: ProfilePictureSizes _cover_photo_sizes: CoverPhotoSizes _collectionIds?: string[] _profile_picture_color?: Color _artist_pick: ID } + +type User = UserMetadata & ComputedUserProperties + +export default User diff --git a/src/services/audius-api-client/AudiusAPIClient.ts b/src/services/audius-api-client/AudiusAPIClient.ts new file mode 100644 index 0000000000..5a3c881346 --- /dev/null +++ b/src/services/audius-api-client/AudiusAPIClient.ts @@ -0,0 +1,294 @@ +import { CID } from 'models/common/Identifiers' +import { + CoverArtSizes, + CoverPhotoSizes, + ProfilePictureSizes +} from 'models/common/ImageSizes' +import Favorite, { FavoriteType } from 'models/Favorite' +import Repost from 'models/Repost' +import { + Download, + FieldVisibility, + Remix, + TrackMetadata, + TrackSegment +} from 'models/Track' +import { UserMetadata } from 'models/User' +import { decodeHashId } from 'utils/route/hashIds' +import { Nullable, removeNullable } from 'utils/typeUtils' + +type Environment = 'production' | 'staging' | 'development' +type TrendingTimeRange = 'week' | 'month' | 'year' | 'allTime' + +const ENDPOINT_PROVIDER_MAP: { [env in Environment]: string } = { + development: 'http://docker.for.mac.localhost:5000', + staging: '', + production: 'https://api.audius.co' +} + +type OpaqueID = string + +// TODO: Autogen these from swagger, eventually + +type APIUser = { + album_count: number + bio: Nullable + cover_photo: CoverPhotoSizes + followee_count: number + follower_count: number + handle: string + id: OpaqueID + is_verified: boolean + location: Nullable + name: string + playlist_count: number + profile_picture: ProfilePictureSizes + repost_count: number + track_count: number + created_at: string + creator_node_endpoint: Nullable + current_user_followee_follow_count: number + does_current_user_follow: boolean + handle_lc: string + is_creator: boolean + updated_at: string + cover_photo_sizes: Nullable + profile_picture_sizes: Nullable +} + +export type APIRepost = { + repost_item_id: string + repost_type: string + user_id: string +} + +export type APIFavorite = { + favorite_item_id: string + favorite_type: FavoriteType + user_id: string +} + +export type APIRemix = { + parent_track_id: OpaqueID + user: APIUser + has_remix_author_reposted: boolean + has_remix_author_saved: boolean +} + +export type APITrack = { + artwork: CoverArtSizes + description: Nullable + genre: string + id: OpaqueID + mood: Nullable + release_date: Nullable + remix_of: { + tracks: null | APIRemix[] + } + repost_count: number + favorite_count: number + tags: Nullable + title: string + // TODO + user: APIUser + duration: number + downloadable: boolean + create_date: Nullable + created_at: string + credits_splits: Nullable + cover_art_sizes: string + download: Download + isrc: Nullable + license: Nullable + iswc: Nullable + field_visibility: FieldVisibility + followee_reposts: APIRepost[] + has_current_user_reposted: boolean + is_unlisted: boolean + has_current_user_saved: boolean + followee_favorites: APIFavorite[] + route_id: string + stem_of: any + track_segments: TrackSegment[] + updated_at: string + user_id: OpaqueID + is_delete: boolean + cover_art: Nullable +} + +type APIResponse = { + data: T +} + +type UserTrackMetadata = TrackMetadata & { user: UserMetadata } + +class APIClientMarshaller { + marshalUser(user: APIUser): UserMetadata | undefined { + const decodedUserId = decodeHashId(user.id) + if (!decodedUserId) { + return undefined + } + + const newUser = { + ...user, + user_id: decodedUserId, + cover_photo: user.cover_photo_sizes, + profile_picture: user.profile_picture_sizes, + id: undefined + } + + delete newUser.id + + return newUser + } + + marshalFavorite(favorite: APIFavorite): Favorite | undefined { + const decodedSaveItemId = decodeHashId(favorite.favorite_item_id) + const decodedUserId = decodeHashId(favorite.user_id) + if (!decodedSaveItemId || !decodedUserId) { + // TODO: handle this better + return undefined + } + return { + save_item_id: decodedSaveItemId, + user_id: decodedUserId, + save_type: favorite.favorite_type + } + } + + marshalRepost(repost: APIRepost): Repost | undefined { + const decodedRepostItemId = decodeHashId(repost.repost_item_id) + const decodedUserId = decodeHashId(repost.user_id) + if (!decodedRepostItemId || !decodedUserId) { + // TODO + return undefined + } + + return { + repost_item_id: decodedRepostItemId, + user_id: decodedUserId, + repost_type: repost.repost_type + } + } + + marshalRemix(remix: APIRemix): Remix | undefined { + const decodedTrackId = decodeHashId(remix.parent_track_id) + const user = this.marshalUser(remix.user) + if (!decodedTrackId || !user) { + return undefined + } + + return { + ...remix, + parent_track_id: decodedTrackId, + user + } + } + + marshalTrack(track: APITrack): UserTrackMetadata | undefined { + // TODO: if I get this working, let's look into io.TS... + const decodedTrackId = decodeHashId(track.id) + const decodedOwnerId = decodeHashId(track.user_id) + const user = this.marshalUser(track.user) + if (!decodedTrackId || !decodedOwnerId || !user) { + // TODO: handle errors better + return undefined + } + + const saves = track.followee_favorites + .map(this.marshalFavorite) + .filter(removeNullable) + + const reposts = track.followee_reposts + .map(this.marshalRepost) + .filter(removeNullable) + + const remixes = + track.remix_of.tracks?.map(this.marshalRemix).filter(removeNullable) ?? [] + + const marshalled = { + ...track, + user, + track_id: decodedTrackId, + owner_id: decodedOwnerId, + followee_saves: saves, + followee_reposts: reposts, + save_count: track.favorite_count, + remix_of: { + tracks: remixes + }, + + // Fields to prune + id: undefined, + user_id: undefined, + followee_favorites: undefined, + artwork: undefined, + downloadable: undefined + } + + delete marshalled.id + delete marshalled.user_id + delete marshalled.followee_favorites + + return marshalled + } +} + +class AudiusAPIClient { + isInitialized = false + endpoint: string | null = null + environment: Environment + marshaller: APIClientMarshaller + + constructor({ environment }: { environment: Environment }) { + this.environment = environment + this.marshaller = new APIClientMarshaller() + } + + async getTrending(timeRange: TrendingTimeRange) { + this.assertDidIntialize() + const endpoint = `${this.endpoint}/tracks/trending?timeRange=${timeRange}` + const trendingResponse: APIResponse = await this.getResponse( + endpoint + ) + console.log({ response: trendingResponse.data }) + const marshalled = trendingResponse.data + .map(t => this.marshaller.marshalTrack(t)) + .filter(removeNullable) + console.log(marshalled) + return marshalled + } + + async init() { + try { + let endpoint + if (this.environment === 'development') { + // Hardcode local DP as endpoint if in development + // env + endpoint = ENDPOINT_PROVIDER_MAP[this.environment] + } else { + const endpointProvider = ENDPOINT_PROVIDER_MAP[this.environment] + const endpointsResponse = await fetch(endpointProvider) + const endpointsJson = await endpointsResponse.json() + const { data: endpoints }: { data: string[] } = endpointsJson + endpoint = endpoints[Math.floor(Math.random() * endpoints.length)] + } + this.endpoint = `${endpoint}/v1/full` + this.isInitialized = true + } catch { + // TODO: handle this + } + } + + assertDidIntialize() { + // TODO + } + + // Helpers + async getResponse(resource: string): Promise { + const response = await fetch(resource) + return response.json() + } +} + +export default AudiusAPIClient diff --git a/src/store/cache/tracks/utils/reformat.ts b/src/store/cache/tracks/utils/reformat.ts index de0096132c..edb8b7f003 100644 --- a/src/store/cache/tracks/utils/reformat.ts +++ b/src/store/cache/tracks/utils/reformat.ts @@ -4,10 +4,9 @@ import AudiusBackend from 'services/AudiusBackend' import Track from 'models/Track' /** - * Adds cover_art_url to a track object if it does not have one set + * Adds _cover_art_sizes to a track object if it does not have one set */ const addTrackImages = (track: Track) => { - if (track.cover_art_url) return track return AudiusBackend.getTrackImages(track) } diff --git a/src/store/player/sagas.ts b/src/store/player/sagas.ts index f4f7e55b16..a189a08cf3 100644 --- a/src/store/player/sagas.ts +++ b/src/store/player/sagas.ts @@ -74,8 +74,7 @@ export function* watchPlay() { gateways, { title: track.title, - artist: owner.name, - artwork: track.cover_art_url + artist: owner.name } ) return () => {} diff --git a/src/utils/typeUtils.ts b/src/utils/typeUtils.ts index 61c2ce47dc..0c9173dce9 100644 --- a/src/utils/typeUtils.ts +++ b/src/utils/typeUtils.ts @@ -8,3 +8,6 @@ export function removeNullable( export type NestedNonNullable = { [P in keyof T]: NestedNonNullable> } + +export type Nullable = T | null +export type Overwrite = Omit & V From f99005198be32d9871358ac48e4c36304eabc919 Mon Sep 17 00:00:00 2001 From: Michael Piazza Date: Wed, 23 Sep 2020 17:10:27 -0700 Subject: [PATCH 02/14] First pass at consuming API successful --- src/app.tsx | 11 +- .../store/lineups/trending/sagas.js | 150 ++++-------------- .../store/lineups/trending/selectors.js | 11 -- .../store/lineups/trending/selectors.ts | 15 ++ .../track-page/TrackPageProvider.tsx | 2 + .../track-page/store/retrieveTrending.ts | 63 ++++++++ src/containers/track-page/store/sagas.js | 34 ++-- src/models/Track.ts | 5 +- src/models/User.ts | 2 +- src/services/AudiusBackend.js | 19 --- .../audius-api-client/AudiusAPIClient.ts | 51 ++++-- src/store/cache/tracks/utils/helpers.ts | 5 +- .../tracks/utils/processAndCacheTracks.ts | 9 +- src/store/cache/tracks/utils/reformat.ts | 27 ++-- src/store/cache/users/utils/reformat.ts | 21 ++- src/utils/route/hashIds.ts | 13 +- src/utils/trendingScorer.js | 5 - 17 files changed, 219 insertions(+), 224 deletions(-) delete mode 100644 src/containers/discover-page/store/lineups/trending/selectors.js create mode 100644 src/containers/discover-page/store/lineups/trending/selectors.ts create mode 100644 src/containers/track-page/store/retrieveTrending.ts delete mode 100644 src/utils/trendingScorer.js diff --git a/src/app.tsx b/src/app.tsx index f8348e8fab..456160b28f 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -1,4 +1,4 @@ -import React, { useEffect } from 'react' +import React from 'react' import { Provider } from 'react-redux' import { ConnectedRouter } from 'connected-react-router' @@ -10,7 +10,6 @@ import AppContext from 'AppContext' import App from './containers/App' import './services/webVitals' import './index.css' -import AudiusAPIClient from 'services/audius-api-client/AudiusAPIClient' declare global { interface Window { @@ -35,14 +34,6 @@ const AudiusApp = ({ setConnectivityFailure, shouldShowPopover }: AudiusAppProps) => { - useEffect(() => { - const fn = async () => { - const client = new AudiusAPIClient({ environment: 'development' }) - await client.init() - await client.getTrending('week') - } - fn() - }, []) return ( diff --git a/src/containers/discover-page/store/lineups/trending/sagas.js b/src/containers/discover-page/store/lineups/trending/sagas.js index 5a7769d318..0ec25df486 100644 --- a/src/containers/discover-page/store/lineups/trending/sagas.js +++ b/src/containers/discover-page/store/lineups/trending/sagas.js @@ -1,7 +1,5 @@ -import { call, put, select } from 'redux-saga/effects' +import { put, select } from 'redux-saga/effects' -import AudiusBackend from 'services/AudiusBackend' -import { makeGetTrendingOrder, makeGetTrendingStats } from './selectors' import { setLastFetchedTrendingGenre, setLastFetchedTimeRange @@ -9,12 +7,7 @@ import { import { LineupSagas } from 'store/lineup/sagas' import TimeRange from 'models/TimeRange' -import { getTrendingScore, sortByTrendingScore } from 'utils/trendingScorer' - -import { - getTrendingGenre, - getLastFetchedTrendingGenre -} from 'containers/discover-page/store/selectors' +import { getTrendingGenre } from 'containers/discover-page/store/selectors' import { TRENDING_WEEK_PREFIX, @@ -24,113 +17,38 @@ import { trendingMonthActions, trendingYearActions } from './actions' -import { retrieveTracks } from 'store/cache/tracks/utils' - -const getActionSet = timeRange => { - return { - [TimeRange.WEEK]: trendingWeekActions, - [TimeRange.MONTH]: trendingMonthActions, - [TimeRange.YEAR]: trendingYearActions - }[timeRange] -} - -function* getTrendingOrderAndStats(timeRange, genre) { - const trendingResponse = yield call(AudiusBackend.getTrendingTracks, { - offset: 0, - limit: 200, - timeRange, - genre: genre - }) - - // A sorted array of Ids - const trendingOrder = trendingResponse.listen_counts - .sort(sortByTrendingScore(timeRange)) - .map(t => t.track_id) - - // Makes a map of id => score - const trendingStats = trendingResponse.listen_counts.reduce((stats, t) => { - stats[t.track_id] = { - ...t, - score: getTrendingScore(t, timeRange) - } - return stats - }, {}) - return { trendingOrder, trendingStats } -} - -// This will return true if it stored the response, false if the genre changed and it didn't store. -function* fetchTrendingScores(timeRange, genreAtStart) { - const { trendingOrder, trendingStats } = yield call( - getTrendingOrderAndStats, - timeRange, - genreAtStart - ) - - const actionSet = getActionSet(timeRange) - const genreAtEnd = yield select(getTrendingGenre) - if (genreAtStart !== genreAtEnd) return false - - yield put(actionSet.setTrendingScores(trendingOrder, trendingStats)) - return true -} +import { getUserId } from 'store/account/selectors' +import { retrieveTrending } from 'containers/track-page/store/retrieveTrending' -// Injects a timeRange provider function getTracks. Allows us to -// specialize a getTracks function for different lineup sagas. -function makeGetTracks(timeRangeProvider) { +function getTracks(timeRange) { return function* ({ offset, limit }) { - return yield getTracks({ timeRangeProvider, offset, limit }) - } -} - -function* getTracks({ timeRangeProvider, offset, limit }) { - // Get the time range - const timeRange = yield timeRangeProvider() - const [getTrendingOrder, getTrendingStats] = [ - makeGetTrendingOrder(timeRange), - makeGetTrendingStats(timeRange) - ] - - // Refetch when the last fetched genre isn't the one being requested - const genreAtStart = yield select(getTrendingGenre) - const lastGenre = yield select(getLastFetchedTrendingGenre) - - let trendingStats = yield select(getTrendingStats) - let trendingOrder = yield select(getTrendingOrder) - - const needsNewScores = - !Object.keys(trendingStats).length || - !trendingOrder.length || - genreAtStart !== lastGenre - - if (needsNewScores) { - const didFetch = yield call(fetchTrendingScores, timeRange, genreAtStart) - if (!didFetch) return null - - trendingStats = yield select(getTrendingStats) - trendingOrder = yield select(getTrendingOrder) - } - - let ret - if (trendingOrder.length > 0) { - const trackIds = yield call(retrieveTracks, { - trackIds: trendingOrder.slice(offset, offset + limit) + // Possibly abort early + const genreAtStart = yield select(getTrendingGenre) + // const lastGenre = yield select(getLastFetchedTrendingGenre) + // const trendingEntries = yield select(getTrendingEntries(timeRange)) + // TODO: figure out how to handle this with pagination now... + // const needsRefetch = + // !Object.keys(trendingEntries).length || genreAtStart !== lastGenre + // if (!needsRefetch) return [] + + const userId = yield select(getUserId) + const tracks = yield retrieveTrending({ + timeRange, + limit, + offset, + genre: genreAtStart, + currentUserId: userId }) - // If we've changed genres since we kicked off this call, we shouldn't - // save the trackIds. - const genreAtEnd = yield select(getTrendingGenre) - if (genreAtStart !== genreAtEnd) { - return null - } + // const genreAtEnd = yield select(getTrendingGenre) + // if (genreAtStart !== genreAtEnd) { + // return null + // } - ret = trackIds - } else { - ret = [] + yield put(setLastFetchedTrendingGenre(genreAtStart)) + yield put(setLastFetchedTimeRange(timeRange)) + return tracks } - - yield put(setLastFetchedTrendingGenre(genreAtStart)) - yield put(setLastFetchedTimeRange(timeRange)) - return ret } class TrendingWeekSagas extends LineupSagas { @@ -139,9 +57,7 @@ class TrendingWeekSagas extends LineupSagas { TRENDING_WEEK_PREFIX, trendingWeekActions, store => store.discover.trendingWeek, - makeGetTracks(function* () { - return yield TimeRange.WEEK - }) + getTracks(TimeRange.WEEK) ) } } @@ -152,9 +68,7 @@ class TrendingMonthSagas extends LineupSagas { TRENDING_MONTH_PREFIX, trendingMonthActions, store => store.discover.trendingMonth, - makeGetTracks(function* () { - return yield TimeRange.MONTH - }) + getTracks(TimeRange.MONTH) ) } } @@ -165,9 +79,7 @@ class TrendingYearSagas extends LineupSagas { TRENDING_YEAR_PREFIX, trendingYearActions, store => store.discover.trendingYear, - makeGetTracks(function* () { - return yield TimeRange.YEAR - }) + getTracks(TimeRange.YEAR) ) } } diff --git a/src/containers/discover-page/store/lineups/trending/selectors.js b/src/containers/discover-page/store/lineups/trending/selectors.js deleted file mode 100644 index f77e97a5aa..0000000000 --- a/src/containers/discover-page/store/lineups/trending/selectors.js +++ /dev/null @@ -1,11 +0,0 @@ -import { makeGetTrendingLineup } from 'containers/discover-page/store/selectors' - -export const makeGetTrendingOrder = timeRange => state => { - const lineup = makeGetTrendingLineup(timeRange)(state) - return lineup.trendingOrder -} - -export const makeGetTrendingStats = timeRange => state => { - const lineup = makeGetTrendingLineup(timeRange)(state) - return lineup.trendingStats -} diff --git a/src/containers/discover-page/store/lineups/trending/selectors.ts b/src/containers/discover-page/store/lineups/trending/selectors.ts new file mode 100644 index 0000000000..ad51917a9b --- /dev/null +++ b/src/containers/discover-page/store/lineups/trending/selectors.ts @@ -0,0 +1,15 @@ +import { LineupStateTrack } from 'models/common/Lineup' +import TimeRange from 'models/TimeRange' +import { AppState } from 'store/types' + +export const getTrendingEntries = (timeRange: TimeRange) => ( + state: AppState +): LineupStateTrack<{ id: number }>[] => { + if (timeRange === TimeRange.WEEK) { + return state.discover.trendingWeek.entries + } else if (timeRange === TimeRange.MONTH) { + return state.discover.trendingMonth.entries + } else { + return state.discover.trendingYear.entries + } +} diff --git a/src/containers/track-page/TrackPageProvider.tsx b/src/containers/track-page/TrackPageProvider.tsx index 513db44b0d..e217b3f9eb 100644 --- a/src/containers/track-page/TrackPageProvider.tsx +++ b/src/containers/track-page/TrackPageProvider.tsx @@ -197,6 +197,7 @@ class TrackPageProvider extends Component< } fetchTracks = (params: NonNullable) => { + console.log('fetch track') const { track } = this.props const { trackTitle, trackId, handle } = params @@ -208,6 +209,7 @@ class TrackPageProvider extends Component< } } this.props.reset() + console.log('setting track: ' + trackId) this.props.setTrackId(trackId) this.props.fetchTrack(trackId, trackTitle, handle, !!(trackTitle && handle)) if (handle) { diff --git a/src/containers/track-page/store/retrieveTrending.ts b/src/containers/track-page/store/retrieveTrending.ts new file mode 100644 index 0000000000..b3940005a3 --- /dev/null +++ b/src/containers/track-page/store/retrieveTrending.ts @@ -0,0 +1,63 @@ +import { getTrendingEntries } from 'containers/discover-page/store/lineups/trending/selectors' +import { ID } from 'models/common/Identifiers' +import TimeRange from 'models/TimeRange' +import Track from 'models/Track' +import { select } from 'redux-saga/effects' +import AudiusAPIClient, { + UserTrackMetadata +} from 'services/audius-api-client/AudiusAPIClient' +import { getTracks } from 'store/cache/tracks/selectors' +import { processAndCacheTracks } from 'store/cache/tracks/utils' +import { AppState } from 'store/types' +import { encodeHashId } from 'utils/route/hashIds' + +type RetrieveTrendingArgs = { + timeRange: TimeRange + genre?: string + offset: number + limit: number + currentUserId?: ID +} + +const apiClient = new AudiusAPIClient({ environment: 'development' }) +export function* retrieveTrending({ + timeRange, + genre, + offset, + limit, + currentUserId +}: RetrieveTrendingArgs): Generator { + const cachedTracks: ReturnType> = yield select(getTrendingEntries(timeRange)) + + // TODO: remove this out + yield apiClient.init() + + const useCached = + cachedTracks?.length > 0 && cachedTracks.length > offset + limit + + if (useCached) { + const trackIds = cachedTracks.slice(offset, limit + offset).map(t => t.id) + const tracksMap: ReturnType = yield select( + (state: AppState) => getTracks(state, { ids: trackIds }) + ) + const tracks = trackIds.map(id => tracksMap[id]) + return tracks + } + + const encodedUserId = currentUserId + ? encodeHashId(currentUserId) ?? undefined + : undefined + + const apiTracks: UserTrackMetadata[] = yield apiClient.getTrending({ + genre, + offset, + limit, + currentUserId: encodedUserId, + timeRange + }) + + const processed: Track[] = yield processAndCacheTracks(apiTracks) + return processed +} diff --git a/src/containers/track-page/store/sagas.js b/src/containers/track-page/store/sagas.js index 5322546b71..3111f7d905 100644 --- a/src/containers/track-page/store/sagas.js +++ b/src/containers/track-page/store/sagas.js @@ -5,51 +5,43 @@ import tracksSagas from 'containers/track-page/store/lineups/tracks/sagas' import * as trackPageActions from './actions' import * as trackCacheActions from 'store/cache/tracks/actions' import { tracksActions } from './lineups/tracks/actions' -import AudiusBackend from 'services/AudiusBackend' import { waitForBackendSetup } from 'store/backend/sagas' import { getIsReachable } from 'store/reachability/selectors' import { getTrack as getCachedTrack } from 'store/cache/tracks/selectors' import { getTrack, getUser } from './selectors' -import { sortByTrendingScore } from 'utils/trendingScorer' import TimeRange from 'models/TimeRange' import { push as pushRoute } from 'connected-react-router' import { retrieveTracks } from 'store/cache/tracks/utils' import { NOT_FOUND_PAGE, trackRemixesPage } from 'utils/route' import { getUsers, getUserFromTrack } from 'store/cache/users/selectors' +import { retrieveTrending } from 'containers/track-page/store/retrieveTrending' + +const TRENDING_LIMIT = 100 function* watchTrackBadge() { yield takeEvery(trackPageActions.GET_TRACK_RANKS, function* (action) { yield call(waitForBackendSetup) - let [ + const [ weeklyTrendingTracks, monthlyTrendingTracks, yearlyTrendingTracks ] = yield all([ - call(AudiusBackend.getTrendingTracks, { + call(retrieveTrending, { + timeRange: TimeRange.WEEK, offset: 0, - limit: 200, - timeRange: 'week' + limit: TRENDING_LIMIT }), - call(AudiusBackend.getTrendingTracks, { + call(retrieveTrending, { + timeRange: TimeRange.MONTH, offset: 0, - limit: 200, - timeRange: 'month' + limit: TRENDING_LIMIT }), - call(AudiusBackend.getTrendingTracks, { + call(retrieveTrending, { + timeRange: TimeRange.YEAR, offset: 0, - limit: 200, - timeRange: 'year' + limit: TRENDING_LIMIT }) ]) - yearlyTrendingTracks = yearlyTrendingTracks.listen_counts - .sort(sortByTrendingScore(TimeRange.YEAR)) - .slice(0, 5) - weeklyTrendingTracks = weeklyTrendingTracks.listen_counts - .sort(sortByTrendingScore(TimeRange.WEEK)) - .slice(0, 5) - monthlyTrendingTracks = monthlyTrendingTracks.listen_counts - .sort(sortByTrendingScore(TimeRange.MONTH)) - .slice(0, 5) const weeklyTrackIndex = weeklyTrendingTracks.findIndex( ({ track_id: trackId }) => trackId === action.trackId diff --git a/src/models/Track.ts b/src/models/Track.ts index 9db950ad88..6e768c8f5c 100644 --- a/src/models/Track.ts +++ b/src/models/Track.ts @@ -80,6 +80,7 @@ export type TrackMetadata = { title: string track_segments: TrackSegment[] cover_art: Nullable + cover_art_sizes: Nullable is_unlisted: boolean field_visibility?: FieldVisibility listenCount?: number @@ -102,8 +103,6 @@ export type Stem = { category: StemCategory } -export type LibsTrack = TrackMetadata - export type ComputedTrackProperties = { // All below, added clientside _cover_art_sizes: CoverArtSizes @@ -124,7 +123,7 @@ export type ComputedTrackProperties = { _co_sign?: Nullable } -export type Track = LibsTrack & ComputedTrackProperties +export type Track = TrackMetadata & ComputedTrackProperties export type UserTrack = Track & { user: User diff --git a/src/models/User.ts b/src/models/User.ts index bda9a7bf90..cd9dc4526d 100644 --- a/src/models/User.ts +++ b/src/models/User.ts @@ -43,7 +43,7 @@ export type ComputedUserProperties = { _cover_photo_sizes: CoverPhotoSizes _collectionIds?: string[] _profile_picture_color?: Color - _artist_pick: ID + _artist_pick?: ID } type User = UserMetadata & ComputedUserProperties diff --git a/src/services/AudiusBackend.js b/src/services/AudiusBackend.js index e6dd2cc835..8dd46faa65 100644 --- a/src/services/AudiusBackend.js +++ b/src/services/AudiusBackend.js @@ -1041,25 +1041,6 @@ class AudiusBackend { } } - static async getTrendingTracks({ genre, offset, limit, timeRange }) { - try { - const computedGenre = genre ? encodeURIComponent(genre) : null - return withEagerOption( - { - normal: libs => libs.Track.getTrendingTracks, - eager: DiscoveryAPI.getTrendingTracks - }, - computedGenre, - timeRange, - null /* all tracks */, - limit, - offset - ) - } catch (err) { - console.error(err.message) - } - } - static async repostTrack(trackId) { try { return audiusLibs.Track.addTrackRepost(trackId) diff --git a/src/services/audius-api-client/AudiusAPIClient.ts b/src/services/audius-api-client/AudiusAPIClient.ts index 5a3c881346..81518ee76d 100644 --- a/src/services/audius-api-client/AudiusAPIClient.ts +++ b/src/services/audius-api-client/AudiusAPIClient.ts @@ -6,6 +6,7 @@ import { } from 'models/common/ImageSizes' import Favorite, { FavoriteType } from 'models/Favorite' import Repost from 'models/Repost' +import TimeRange from 'models/TimeRange' import { Download, FieldVisibility, @@ -18,7 +19,6 @@ import { decodeHashId } from 'utils/route/hashIds' import { Nullable, removeNullable } from 'utils/typeUtils' type Environment = 'production' | 'staging' | 'development' -type TrendingTimeRange = 'week' | 'month' | 'year' | 'allTime' const ENDPOINT_PROVIDER_MAP: { [env in Environment]: string } = { development: 'http://docker.for.mac.localhost:5000', @@ -114,13 +114,14 @@ export type APITrack = { user_id: OpaqueID is_delete: boolean cover_art: Nullable + play_count: number } type APIResponse = { data: T } -type UserTrackMetadata = TrackMetadata & { user: UserMetadata } +export type UserTrackMetadata = TrackMetadata & { user: UserMetadata } class APIClientMarshaller { marshalUser(user: APIUser): UserMetadata | undefined { @@ -214,26 +215,43 @@ class APIClientMarshaller { followee_saves: saves, followee_reposts: reposts, save_count: track.favorite_count, - remix_of: { - tracks: remixes - }, + remix_of: + remixes.length > 0 + ? { + tracks: remixes + } + : null, + + stem_of: track.stem_of.parent_track_id === null ? null : track.stem_of, // Fields to prune id: undefined, user_id: undefined, followee_favorites: undefined, artwork: undefined, - downloadable: undefined + downloadable: undefined, + favorite_count: undefined } delete marshalled.id delete marshalled.user_id delete marshalled.followee_favorites + delete marshalled.artwork + delete marshalled.downloadable + delete marshalled.favorite_count return marshalled } } +type GetTrendingArgs = { + timeRange?: TimeRange + offset?: number + limit?: number + currentUserId?: string + genre?: string +} + class AudiusAPIClient { isInitialized = false endpoint: string | null = null @@ -245,13 +263,26 @@ class AudiusAPIClient { this.marshaller = new APIClientMarshaller() } - async getTrending(timeRange: TrendingTimeRange) { + async getTrending({ + timeRange = TimeRange.WEEK, + limit = 200, + offset = 0, + currentUserId, + genre + }: GetTrendingArgs) { this.assertDidIntialize() - const endpoint = `${this.endpoint}/tracks/trending?timeRange=${timeRange}` + // TODO: use a query builder + let endpoint = `${this.endpoint}/tracks/trending?time=${timeRange}&limit=${limit}&offset=${offset}` + if (currentUserId) { + endpoint = `${endpoint}&user_id=${currentUserId}` + } + if (genre) { + endpoint = `${endpoint}&genre=${genre}` + } + const trendingResponse: APIResponse = await this.getResponse( endpoint ) - console.log({ response: trendingResponse.data }) const marshalled = trendingResponse.data .map(t => this.marshaller.marshalTrack(t)) .filter(removeNullable) @@ -260,6 +291,8 @@ class AudiusAPIClient { } async init() { + if (this.isInitialized) return + try { let endpoint if (this.environment === 'development') { diff --git a/src/store/cache/tracks/utils/helpers.ts b/src/store/cache/tracks/utils/helpers.ts index 0c6aeeee4a..4c69593ec5 100644 --- a/src/store/cache/tracks/utils/helpers.ts +++ b/src/store/cache/tracks/utils/helpers.ts @@ -5,14 +5,13 @@ import { put } from 'redux-saga/effects' import * as cacheActions from 'store/cache/actions' import { reformat as reformatUser } from 'store/cache/users/utils' import { makeUid } from 'utils/uid' +import { UserTrackMetadata } from 'services/audius-api-client/AudiusAPIClient' /** * Adds users from track metadata to cache. * @param metadataArray */ -export function* addUsersFromTracks( - metadataArray: Array -) { +export function* addUsersFromTracks(metadataArray: UserTrackMetadata[]) { const users = metadataArray .filter(m => m.user) .map(m => { diff --git a/src/store/cache/tracks/utils/processAndCacheTracks.ts b/src/store/cache/tracks/utils/processAndCacheTracks.ts index 77a506a76c..39008c05b9 100644 --- a/src/store/cache/tracks/utils/processAndCacheTracks.ts +++ b/src/store/cache/tracks/utils/processAndCacheTracks.ts @@ -1,22 +1,25 @@ -import { UserTrack } from 'models/Track' import { put } from 'redux-saga/effects' import * as cacheActions from 'store/cache/actions' import { reformat } from './reformat' import { Kind } from 'store/types' import { makeUid } from 'utils/uid' import { addUsersFromTracks } from './helpers' +import { UserTrackMetadata } from 'services/audius-api-client/AudiusAPIClient' +import Track from 'models/Track' /** * Processes tracks, adding users and calling `reformat`, before * caching the tracks. * @param tracks */ -export function* processAndCacheTracks(tracks: UserTrack[]) { +export function* processAndCacheTracks( + tracks: UserTrackMetadata[] +): Generator { // Add users yield addUsersFromTracks(tracks) // Remove users, add images - const reformattedTracks = tracks.map(t => reformat(t)) + const reformattedTracks = tracks.map(reformat) // insert tracks into cache yield put( diff --git a/src/store/cache/tracks/utils/reformat.ts b/src/store/cache/tracks/utils/reformat.ts index edb8b7f003..324a878951 100644 --- a/src/store/cache/tracks/utils/reformat.ts +++ b/src/store/cache/tracks/utils/reformat.ts @@ -1,12 +1,15 @@ import { omit } from 'lodash' import AudiusBackend from 'services/AudiusBackend' -import Track from 'models/Track' +import Track, { TrackMetadata } from 'models/Track' +import { CoverArtSizes } from 'models/common/ImageSizes' /** * Adds _cover_art_sizes to a track object if it does not have one set */ -const addTrackImages = (track: Track) => { +const addTrackImages = ( + track: T +): T & { duration: number; _cover_art_sizes: CoverArtSizes } => { return AudiusBackend.getTrackImages(track) } @@ -14,7 +17,7 @@ const addTrackImages = (track: Track) => { * Potentially add * @param track */ -const setIsCoSigned = (track: Track) => { +const setIsCoSigned = (track: T) => { const { remix_of } = track const remixOfTrack = remix_of?.tracks[0] @@ -39,7 +42,7 @@ const setIsCoSigned = (track: Track) => { * The current erroneous disprov endpoint is `/feed/reposts/` * @param track */ -const setDefaultFolloweeSaves = (track: Track) => { +const setDefaultFolloweeSaves = (track: T) => { return { ...track, followee_saves: track?.followee_saves ?? [] @@ -50,11 +53,13 @@ const setDefaultFolloweeSaves = (track: Track) => { * Reformats a track to be used internally within the client * This method should *always* be called before a track is cached. */ -export const reformat = (track: Track) => { - let t = track - t = omit(t, 'user') - t = addTrackImages(t) - t = setIsCoSigned(t) - t = setDefaultFolloweeSaves(t) - return t +export const reformat = (track: T): Track => { + const t = track + const withoutUser = omit(t, 'user') + const withImages = addTrackImages(withoutUser) + const withCosign = setIsCoSigned(withImages) + + // TODO: we can possibly remove this now, look into it + const withDefaultSaves = setDefaultFolloweeSaves(withCosign) + return withDefaultSaves } diff --git a/src/store/cache/users/utils/reformat.ts b/src/store/cache/users/utils/reformat.ts index 349805e62e..cd567210d2 100644 --- a/src/store/cache/users/utils/reformat.ts +++ b/src/store/cache/users/utils/reformat.ts @@ -1,10 +1,16 @@ import AudiusBackend from 'services/AudiusBackend' -import User from 'models/User' +import User, { UserMetadata } from 'models/User' +import { CoverPhotoSizes, ProfilePictureSizes } from 'models/common/ImageSizes' /** * Adds profile picture and cover art to a user object if it does not have one set * */ -const addUserImages = (user: User) => { +const addUserImages = ( + user: T +): T & { + _profile_picture_sizes: ProfilePictureSizes + _cover_photo_sizes: CoverPhotoSizes +} => { return AudiusBackend.getUserImages(user) } @@ -13,7 +19,7 @@ const addUserImages = (user: User) => { * During sign-up, it's possible for an account to be created but the set display * name transaction to fail, which would leave us in a bad UI state. */ -const setDisplayNameToHandleIfUnset = (user: User) => { +const setDisplayNameToHandleIfUnset = (user: T) => { if (user.name) return user return { ...user, @@ -25,9 +31,8 @@ const setDisplayNameToHandleIfUnset = (user: User) => { * Reformats a user to be used internally within the client. * This method should *always* be called before a user is cached. */ -export const reformat = (user: User) => { - let u = user - u = addUserImages(u) - u = setDisplayNameToHandleIfUnset(u) - return u +export const reformat = (user: T): User => { + const withImages = addUserImages(user) + const withNames = setDisplayNameToHandleIfUnset(withImages) + return withNames } diff --git a/src/utils/route/hashIds.ts b/src/utils/route/hashIds.ts index b35bd0ec5f..f6b4215dd1 100644 --- a/src/utils/route/hashIds.ts +++ b/src/utils/route/hashIds.ts @@ -1,11 +1,12 @@ import Hashids from 'hashids' +import { Nullable } from 'utils/typeUtils' const HASH_SALT = 'azowernasdfoia' const MIN_LENGTH = 5 const hashids = new Hashids(HASH_SALT, MIN_LENGTH) /** Decodes a string id into an int. Returns null if an invalid ID. */ -export const decodeHashId = (id: string): number | null => { +export const decodeHashId = (id: string): Nullable => { try { const ids = hashids.decode(id) if (!ids.length) return null @@ -17,3 +18,13 @@ export const decodeHashId = (id: string): number | null => { return null } } + +export const encodeHashId = (id: number): Nullable => { + try { + const encodedId = hashids.encode(id) + return encodedId + } catch (e) { + console.error(`Failed to encode ${id}`, e) + return null + } +} diff --git a/src/utils/trendingScorer.js b/src/utils/trendingScorer.js deleted file mode 100644 index 32c7159191..0000000000 --- a/src/utils/trendingScorer.js +++ /dev/null @@ -1,5 +0,0 @@ -import moment from 'moment' -import TimeRange from 'models/TimeRange' - -// eslint-disable-next-line -var _0xdf4a=["\x57\x45\x45\x4B","\x4D\x4F\x4E\x54\x48","\x59\x45\x41\x52","\x74\x72\x61\x63\x6B\x5F\x6F\x77\x6E\x65\x72\x5F\x66\x6F\x6C\x6C\x6F\x77\x65\x72\x5F\x63\x6F\x75\x6E\x74","\x6C\x69\x73\x74\x65\x6E\x73","\x77\x69\x6E\x64\x6F\x77\x65\x64\x5F\x72\x65\x70\x6F\x73\x74\x5F\x63\x6F\x75\x6E\x74","\x77\x69\x6E\x64\x6F\x77\x65\x64\x5F\x73\x61\x76\x65\x5F\x63\x6F\x75\x6E\x74","\x72\x65\x70\x6F\x73\x74\x5F\x63\x6F\x75\x6E\x74","\x73\x61\x76\x65\x5F\x63\x6F\x75\x6E\x74","\x63\x72\x65\x61\x74\x65\x64\x5F\x61\x74","\x64\x61\x79\x73","\x64\x69\x66\x66","\x70\x6F\x77","\x6D\x61\x78","\x6B\x61\x72\x6D\x61","\x74\x72\x61\x63\x6B\x5F\x69\x64"];const LISTEN_MULTIPLER=1;const WINDOWED_REPOST_MULTIPLER=50;const WINDOWED_SAVE_MULTIPLIER=1;const ALL_TIME_REPOST_MULTIPLER=0.25;const ALL_TIME_SAVE_MULTIPLER=0.01;const CREATED_AT_MULTIPLIER=20.0;const TIME_RANGE_MAPPING={[TimeRange[_0xdf4a[0]]]:7,[TimeRange[_0xdf4a[1]]]:30,[TimeRange[_0xdf4a[2]]]:365};const now=moment();export const getTrendingScore=(_0xbf82xa,_0xbf82xb)=>{if(_0xbf82xa[_0xdf4a[3]]< 3){return 0};const _0xbf82xc=LISTEN_MULTIPLER* _0xbf82xa[_0xdf4a[4]]+ WINDOWED_REPOST_MULTIPLER* _0xbf82xa[_0xdf4a[5]]+ WINDOWED_SAVE_MULTIPLIER* _0xbf82xa[_0xdf4a[6]]+ ALL_TIME_REPOST_MULTIPLER* _0xbf82xa[_0xdf4a[7]]+ ALL_TIME_SAVE_MULTIPLER* _0xbf82xa[_0xdf4a[8]];const _0xbf82xd=moment(_0xbf82xa[_0xdf4a[9]]);const _0xbf82xe=now[_0xdf4a[11]](_0xbf82xd,_0xdf4a[10]);let _0xbf82xf;if(_0xbf82xe< TIME_RANGE_MAPPING[_0xbf82xb]){_0xbf82xf= 1}else {_0xbf82xf= Math[_0xdf4a[13]](1.0/ CREATED_AT_MULTIPLIER,Math[_0xdf4a[12]](CREATED_AT_MULTIPLIER,1- _0xbf82xe/ TIME_RANGE_MAPPING[_0xbf82xb]))};return _0xbf82xc* _0xbf82xf* _0xbf82xa[_0xdf4a[14]]};export const sortByTrendingScore=(_0xbf82xb)=>(_0xbf82x11,_0xbf82x12)=>{const _0xbf82x13=getTrendingScore(_0xbf82x11,_0xbf82xb);const _0xbf82x14=getTrendingScore(_0xbf82x12,_0xbf82xb);if(_0xbf82x13=== _0xbf82x14){return _0xbf82x12[_0xdf4a[15]]- _0xbf82x11[_0xdf4a[15]]};return _0xbf82x14- _0xbf82x13} \ No newline at end of file From 6f88329ed5202af6207a4c0f9e8193e417a3e4f3 Mon Sep 17 00:00:00 2001 From: Michael Piazza Date: Wed, 23 Sep 2020 18:46:46 -0700 Subject: [PATCH 03/14] Remove scoring and ordering from store --- .../desktop/TrendingPageContent.tsx | 2 +- .../store/lineups/trending/actions.js | 20 ++++------------ .../store/lineups/trending/reducer.js | 24 +------------------ .../store/lineups/trending/sagas.js | 1 - src/containers/discover-page/types.ts | 2 -- .../track-page/store/retrieveTrending.ts | 6 ++++- 6 files changed, 12 insertions(+), 43 deletions(-) diff --git a/src/containers/discover-page/components/desktop/TrendingPageContent.tsx b/src/containers/discover-page/components/desktop/TrendingPageContent.tsx index be0e82e66e..79b0ef4f30 100644 --- a/src/containers/discover-page/components/desktop/TrendingPageContent.tsx +++ b/src/containers/discover-page/components/desktop/TrendingPageContent.tsx @@ -180,7 +180,7 @@ const TrendingPageContent = (props: DiscoverPageContentProps) => { currentLineup.lineup.status !== Status.LOADING) || (!unfetchedLineup && currentLineup.lineup.status === Status.SUCCESS && - !(currentLineup.lineup as any).trendingOrder.length && + !currentLineup.lineup.entries.length && trendingGenre !== null) if (shouldMoveToNextTab) { diff --git a/src/containers/discover-page/store/lineups/trending/actions.js b/src/containers/discover-page/store/lineups/trending/actions.js index ad62e70742..eabb75161a 100644 --- a/src/containers/discover-page/store/lineups/trending/actions.js +++ b/src/containers/discover-page/store/lineups/trending/actions.js @@ -1,4 +1,4 @@ -import { LineupActions, addPrefix } from 'store/lineup/actions' +import { LineupActions } from 'store/lineup/actions' export const PREFIX = 'DISCOVER_TRENDING' export const TRENDING_WEEK_PREFIX = 'DISCOVER_TRENDING_WEEK' @@ -7,34 +7,24 @@ export const TRENDING_YEAR_PREFIX = 'DISCOVER_TRENDING_YEAR' export const SET_TRENDING_SCORES = 'SET_TRENDING_SCORES' -export class BaseTrendingActions extends LineupActions { - setTrendingScores(trendingOrder, trendingStats) { - return { - type: addPrefix(this.prefix, SET_TRENDING_SCORES), - trendingOrder, - trendingStats - } - } -} - -class TrendingActions extends BaseTrendingActions { +class TrendingActions extends LineupActions { constructor() { super(PREFIX) } } -class TrendingWeekActions extends BaseTrendingActions { +class TrendingWeekActions extends LineupActions { constructor() { super(TRENDING_WEEK_PREFIX) } } -class TrendingMonthActions extends BaseTrendingActions { +class TrendingMonthActions extends LineupActions { constructor() { super(TRENDING_MONTH_PREFIX) } } -class TrendingYearActions extends BaseTrendingActions { +class TrendingYearActions extends LineupActions { constructor() { super(TRENDING_YEAR_PREFIX) } diff --git a/src/containers/discover-page/store/lineups/trending/reducer.js b/src/containers/discover-page/store/lineups/trending/reducer.js index 1441837f93..d54c80c542 100644 --- a/src/containers/discover-page/store/lineups/trending/reducer.js +++ b/src/containers/discover-page/store/lineups/trending/reducer.js @@ -2,7 +2,6 @@ import { initialLineupState } from 'store/lineup/reducer' import { RESET_SUCCEEDED, stripPrefix } from 'store/lineup/actions' import { PREFIX, - SET_TRENDING_SCORES, TRENDING_WEEK_PREFIX, TRENDING_MONTH_PREFIX, TRENDING_YEAR_PREFIX @@ -12,21 +11,7 @@ const initialState = { ...initialLineupState, antiBot: true, dedupe: true, - prefix: PREFIX, - - // Array<{ - // track_id, - // }> - trendingOrder: [], - // { track_id: - // { - // listens, - // repost_count, - // save_count, - // track_owner_follower_count, - // } - // } - trendingStats: [] + prefix: PREFIX } const makeActionsMap = initialState => { @@ -34,13 +19,6 @@ const makeActionsMap = initialState => { [RESET_SUCCEEDED](state, action) { const newState = initialState return newState - }, - [SET_TRENDING_SCORES](state, action) { - return { - ...state, - trendingOrder: action.trendingOrder, - trendingStats: action.trendingStats - } } } } diff --git a/src/containers/discover-page/store/lineups/trending/sagas.js b/src/containers/discover-page/store/lineups/trending/sagas.js index 0ec25df486..5e1977561d 100644 --- a/src/containers/discover-page/store/lineups/trending/sagas.js +++ b/src/containers/discover-page/store/lineups/trending/sagas.js @@ -24,7 +24,6 @@ function getTracks(timeRange) { return function* ({ offset, limit }) { // Possibly abort early const genreAtStart = yield select(getTrendingGenre) - // const lastGenre = yield select(getLastFetchedTrendingGenre) // const trendingEntries = yield select(getTrendingEntries(timeRange)) // TODO: figure out how to handle this with pagination now... // const needsRefetch = diff --git a/src/containers/discover-page/types.ts b/src/containers/discover-page/types.ts index aaaa26677b..69d34acfa3 100644 --- a/src/containers/discover-page/types.ts +++ b/src/containers/discover-page/types.ts @@ -8,8 +8,6 @@ import Track from 'models/Track' type ExtraTrendingLineupProps = { antiBot: boolean - trendingOrder: ID[] - trendingStats: { [key: string]: any } } export interface DiscoverPageContentProps { diff --git a/src/containers/track-page/store/retrieveTrending.ts b/src/containers/track-page/store/retrieveTrending.ts index b3940005a3..4f42a4140e 100644 --- a/src/containers/track-page/store/retrieveTrending.ts +++ b/src/containers/track-page/store/retrieveTrending.ts @@ -1,4 +1,5 @@ import { getTrendingEntries } from 'containers/discover-page/store/lineups/trending/selectors' +import { getLastFetchedTrendingGenre } from 'containers/discover-page/store/selectors' import { ID } from 'models/common/Identifiers' import TimeRange from 'models/TimeRange' import Track from 'models/Track' @@ -31,11 +32,14 @@ export function* retrieveTrending({ typeof getTrendingEntries >> = yield select(getTrendingEntries(timeRange)) + const lastGenre = yield select(getLastFetchedTrendingGenre) // TODO: remove this out yield apiClient.init() const useCached = - cachedTracks?.length > 0 && cachedTracks.length > offset + limit + lastGenre === genre && + cachedTracks?.length > 0 && + cachedTracks.length > offset + limit if (useCached) { const trackIds = cachedTracks.slice(offset, limit + offset).map(t => t.id) From 439b9c98af7e670c41d80d4bd472a278d0749e6d Mon Sep 17 00:00:00 2001 From: Michael Piazza Date: Wed, 23 Sep 2020 23:28:14 -0700 Subject: [PATCH 04/14] Clean up APIClient --- .../track-page/store/retrieveTrending.ts | 9 +- src/models/Track.ts | 4 +- .../audius-api-client/AudiusAPIClient.ts | 269 ++---------------- .../audius-api-client/ResponseAdapter.ts | 124 ++++++++ src/services/audius-api-client/types.ts | 103 +++++++ src/store/cache/tracks/utils/helpers.ts | 3 +- .../tracks/utils/processAndCacheTracks.ts | 3 +- 7 files changed, 260 insertions(+), 255 deletions(-) create mode 100644 src/services/audius-api-client/ResponseAdapter.ts create mode 100644 src/services/audius-api-client/types.ts diff --git a/src/containers/track-page/store/retrieveTrending.ts b/src/containers/track-page/store/retrieveTrending.ts index 4f42a4140e..36cf261398 100644 --- a/src/containers/track-page/store/retrieveTrending.ts +++ b/src/containers/track-page/store/retrieveTrending.ts @@ -2,11 +2,9 @@ import { getTrendingEntries } from 'containers/discover-page/store/lineups/trend import { getLastFetchedTrendingGenre } from 'containers/discover-page/store/selectors' import { ID } from 'models/common/Identifiers' import TimeRange from 'models/TimeRange' -import Track from 'models/Track' +import Track, { UserTrackMetadata } from 'models/Track' import { select } from 'redux-saga/effects' -import AudiusAPIClient, { - UserTrackMetadata -} from 'services/audius-api-client/AudiusAPIClient' +import AudiusAPIClient from 'services/audius-api-client/AudiusAPIClient' import { getTracks } from 'store/cache/tracks/selectors' import { processAndCacheTracks } from 'store/cache/tracks/utils' import { AppState } from 'store/types' @@ -21,6 +19,7 @@ type RetrieveTrendingArgs = { } const apiClient = new AudiusAPIClient({ environment: 'development' }) +apiClient.init() export function* retrieveTrending({ timeRange, genre, @@ -34,7 +33,7 @@ export function* retrieveTrending({ const lastGenre = yield select(getLastFetchedTrendingGenre) // TODO: remove this out - yield apiClient.init() + // yield apiClient.init() const useCached = lastGenre === genre && diff --git a/src/models/Track.ts b/src/models/Track.ts index 6e768c8f5c..2f6a51e96c 100644 --- a/src/models/Track.ts +++ b/src/models/Track.ts @@ -2,7 +2,7 @@ import Color from 'models/common/Color' import { CID, ID, UID } from 'models/common/Identifiers' import { CoverArtSizes } from 'models/common/ImageSizes' import Repost from 'models/Repost' -import User from 'models/User' +import User, { UserMetadata } from 'models/User' import Favorite from 'models/Favorite' import Timestamped from './common/Timestamped' import { StemCategory } from './Stems' @@ -125,6 +125,8 @@ export type ComputedTrackProperties = { export type Track = TrackMetadata & ComputedTrackProperties +export type UserTrackMetadata = TrackMetadata & { user: UserMetadata } + export type UserTrack = Track & { user: User } diff --git a/src/services/audius-api-client/AudiusAPIClient.ts b/src/services/audius-api-client/AudiusAPIClient.ts index 81518ee76d..cb9bf6d8ff 100644 --- a/src/services/audius-api-client/AudiusAPIClient.ts +++ b/src/services/audius-api-client/AudiusAPIClient.ts @@ -1,22 +1,7 @@ -import { CID } from 'models/common/Identifiers' -import { - CoverArtSizes, - CoverPhotoSizes, - ProfilePictureSizes -} from 'models/common/ImageSizes' -import Favorite, { FavoriteType } from 'models/Favorite' -import Repost from 'models/Repost' import TimeRange from 'models/TimeRange' -import { - Download, - FieldVisibility, - Remix, - TrackMetadata, - TrackSegment -} from 'models/Track' -import { UserMetadata } from 'models/User' -import { decodeHashId } from 'utils/route/hashIds' -import { Nullable, removeNullable } from 'utils/typeUtils' +import { removeNullable } from 'utils/typeUtils' +import { APIResponse, APITrack } from './types' +import * as adapter from './ResponseAdapter' type Environment = 'production' | 'staging' | 'development' @@ -26,224 +11,6 @@ const ENDPOINT_PROVIDER_MAP: { [env in Environment]: string } = { production: 'https://api.audius.co' } -type OpaqueID = string - -// TODO: Autogen these from swagger, eventually - -type APIUser = { - album_count: number - bio: Nullable - cover_photo: CoverPhotoSizes - followee_count: number - follower_count: number - handle: string - id: OpaqueID - is_verified: boolean - location: Nullable - name: string - playlist_count: number - profile_picture: ProfilePictureSizes - repost_count: number - track_count: number - created_at: string - creator_node_endpoint: Nullable - current_user_followee_follow_count: number - does_current_user_follow: boolean - handle_lc: string - is_creator: boolean - updated_at: string - cover_photo_sizes: Nullable - profile_picture_sizes: Nullable -} - -export type APIRepost = { - repost_item_id: string - repost_type: string - user_id: string -} - -export type APIFavorite = { - favorite_item_id: string - favorite_type: FavoriteType - user_id: string -} - -export type APIRemix = { - parent_track_id: OpaqueID - user: APIUser - has_remix_author_reposted: boolean - has_remix_author_saved: boolean -} - -export type APITrack = { - artwork: CoverArtSizes - description: Nullable - genre: string - id: OpaqueID - mood: Nullable - release_date: Nullable - remix_of: { - tracks: null | APIRemix[] - } - repost_count: number - favorite_count: number - tags: Nullable - title: string - // TODO - user: APIUser - duration: number - downloadable: boolean - create_date: Nullable - created_at: string - credits_splits: Nullable - cover_art_sizes: string - download: Download - isrc: Nullable - license: Nullable - iswc: Nullable - field_visibility: FieldVisibility - followee_reposts: APIRepost[] - has_current_user_reposted: boolean - is_unlisted: boolean - has_current_user_saved: boolean - followee_favorites: APIFavorite[] - route_id: string - stem_of: any - track_segments: TrackSegment[] - updated_at: string - user_id: OpaqueID - is_delete: boolean - cover_art: Nullable - play_count: number -} - -type APIResponse = { - data: T -} - -export type UserTrackMetadata = TrackMetadata & { user: UserMetadata } - -class APIClientMarshaller { - marshalUser(user: APIUser): UserMetadata | undefined { - const decodedUserId = decodeHashId(user.id) - if (!decodedUserId) { - return undefined - } - - const newUser = { - ...user, - user_id: decodedUserId, - cover_photo: user.cover_photo_sizes, - profile_picture: user.profile_picture_sizes, - id: undefined - } - - delete newUser.id - - return newUser - } - - marshalFavorite(favorite: APIFavorite): Favorite | undefined { - const decodedSaveItemId = decodeHashId(favorite.favorite_item_id) - const decodedUserId = decodeHashId(favorite.user_id) - if (!decodedSaveItemId || !decodedUserId) { - // TODO: handle this better - return undefined - } - return { - save_item_id: decodedSaveItemId, - user_id: decodedUserId, - save_type: favorite.favorite_type - } - } - - marshalRepost(repost: APIRepost): Repost | undefined { - const decodedRepostItemId = decodeHashId(repost.repost_item_id) - const decodedUserId = decodeHashId(repost.user_id) - if (!decodedRepostItemId || !decodedUserId) { - // TODO - return undefined - } - - return { - repost_item_id: decodedRepostItemId, - user_id: decodedUserId, - repost_type: repost.repost_type - } - } - - marshalRemix(remix: APIRemix): Remix | undefined { - const decodedTrackId = decodeHashId(remix.parent_track_id) - const user = this.marshalUser(remix.user) - if (!decodedTrackId || !user) { - return undefined - } - - return { - ...remix, - parent_track_id: decodedTrackId, - user - } - } - - marshalTrack(track: APITrack): UserTrackMetadata | undefined { - // TODO: if I get this working, let's look into io.TS... - const decodedTrackId = decodeHashId(track.id) - const decodedOwnerId = decodeHashId(track.user_id) - const user = this.marshalUser(track.user) - if (!decodedTrackId || !decodedOwnerId || !user) { - // TODO: handle errors better - return undefined - } - - const saves = track.followee_favorites - .map(this.marshalFavorite) - .filter(removeNullable) - - const reposts = track.followee_reposts - .map(this.marshalRepost) - .filter(removeNullable) - - const remixes = - track.remix_of.tracks?.map(this.marshalRemix).filter(removeNullable) ?? [] - - const marshalled = { - ...track, - user, - track_id: decodedTrackId, - owner_id: decodedOwnerId, - followee_saves: saves, - followee_reposts: reposts, - save_count: track.favorite_count, - remix_of: - remixes.length > 0 - ? { - tracks: remixes - } - : null, - - stem_of: track.stem_of.parent_track_id === null ? null : track.stem_of, - - // Fields to prune - id: undefined, - user_id: undefined, - followee_favorites: undefined, - artwork: undefined, - downloadable: undefined, - favorite_count: undefined - } - - delete marshalled.id - delete marshalled.user_id - delete marshalled.followee_favorites - delete marshalled.artwork - delete marshalled.downloadable - delete marshalled.favorite_count - - return marshalled - } -} - type GetTrendingArgs = { timeRange?: TimeRange offset?: number @@ -256,11 +23,11 @@ class AudiusAPIClient { isInitialized = false endpoint: string | null = null environment: Environment - marshaller: APIClientMarshaller + awaitFunc: (() => void) | null constructor({ environment }: { environment: Environment }) { this.environment = environment - this.marshaller = new APIClientMarshaller() + this.awaitFunc = null } async getTrending({ @@ -270,7 +37,9 @@ class AudiusAPIClient { currentUserId, genre }: GetTrendingArgs) { - this.assertDidIntialize() + console.log('Getting trending, awaiting init') + await this._awaitInitialization() + console.log('Got trending! running') // TODO: use a query builder let endpoint = `${this.endpoint}/tracks/trending?time=${timeRange}&limit=${limit}&offset=${offset}` if (currentUserId) { @@ -283,11 +52,10 @@ class AudiusAPIClient { const trendingResponse: APIResponse = await this.getResponse( endpoint ) - const marshalled = trendingResponse.data - .map(t => this.marshaller.marshalTrack(t)) + const adapted = trendingResponse.data + .map(adapter.makeTrack) .filter(removeNullable) - console.log(marshalled) - return marshalled + return adapted } async init() { @@ -299,6 +67,13 @@ class AudiusAPIClient { // Hardcode local DP as endpoint if in development // env endpoint = ENDPOINT_PROVIDER_MAP[this.environment] + await new Promise(resolve => { + console.log('inside init promise') + setTimeout(() => { + console.log('RESOLVING') + resolve() + }, 5000) + }) } else { const endpointProvider = ENDPOINT_PROVIDER_MAP[this.environment] const endpointsResponse = await fetch(endpointProvider) @@ -308,13 +83,17 @@ class AudiusAPIClient { } this.endpoint = `${endpoint}/v1/full` this.isInitialized = true + if (this.awaitFunc) this.awaitFunc() } catch { // TODO: handle this } } - assertDidIntialize() { - // TODO + _awaitInitialization() { + if (this.isInitialized) return + return new Promise(resolve => { + this.awaitFunc = resolve + }) } // Helpers diff --git a/src/services/audius-api-client/ResponseAdapter.ts b/src/services/audius-api-client/ResponseAdapter.ts new file mode 100644 index 0000000000..d0f200ab5e --- /dev/null +++ b/src/services/audius-api-client/ResponseAdapter.ts @@ -0,0 +1,124 @@ +import Favorite from 'models/Favorite' +import Repost from 'models/Repost' +import { Remix, UserTrackMetadata } from 'models/Track' +import { UserMetadata } from 'models/User' +import { decodeHashId } from 'utils/route/hashIds' +import { removeNullable } from 'utils/typeUtils' +import { APIFavorite, APIRemix, APIRepost, APITrack, APIUser } from './types' + +const makeUser = (user: APIUser): UserMetadata | undefined => { + const decodedUserId = decodeHashId(user.id) + if (!decodedUserId) { + return undefined + } + + const newUser = { + ...user, + user_id: decodedUserId, + cover_photo: user.cover_photo_sizes, + profile_picture: user.profile_picture_sizes, + id: undefined + } + + delete newUser.id + + return newUser +} + +const makeFavorite = (favorite: APIFavorite): Favorite | undefined => { + const decodedSaveItemId = decodeHashId(favorite.favorite_item_id) + const decodedUserId = decodeHashId(favorite.user_id) + if (!decodedSaveItemId || !decodedUserId) { + // TODO: handle this better + return undefined + } + return { + save_item_id: decodedSaveItemId, + user_id: decodedUserId, + save_type: favorite.favorite_type + } +} + +const makeRepost = (repost: APIRepost): Repost | undefined => { + const decodedRepostItemId = decodeHashId(repost.repost_item_id) + const decodedUserId = decodeHashId(repost.user_id) + if (!decodedRepostItemId || !decodedUserId) { + // TODO + return undefined + } + + return { + repost_item_id: decodedRepostItemId, + user_id: decodedUserId, + repost_type: repost.repost_type + } +} + +const makeRemix = (remix: APIRemix): Remix | undefined => { + const decodedTrackId = decodeHashId(remix.parent_track_id) + const user = makeUser(remix.user) + if (!decodedTrackId || !user) { + return undefined + } + + return { + ...remix, + parent_track_id: decodedTrackId, + user + } +} + +export const makeTrack = (track: APITrack): UserTrackMetadata | undefined => { + // TODO: if I get this working, let's look into io.TS... + const decodedTrackId = decodeHashId(track.id) + const decodedOwnerId = decodeHashId(track.user_id) + const user = makeUser(track.user) + if (!decodedTrackId || !decodedOwnerId || !user) { + // TODO: handle errors better + return undefined + } + + const saves = track.followee_favorites + .map(makeFavorite) + .filter(removeNullable) + + const reposts = track.followee_reposts.map(makeRepost).filter(removeNullable) + + const remixes = + track.remix_of.tracks?.map(makeRemix).filter(removeNullable) ?? [] + + const marshalled = { + ...track, + user, + track_id: decodedTrackId, + owner_id: decodedOwnerId, + followee_saves: saves, + followee_reposts: reposts, + save_count: track.favorite_count, + remix_of: + remixes.length > 0 + ? { + tracks: remixes + } + : null, + + stem_of: track.stem_of.parent_track_id === null ? null : track.stem_of, + + // Fields to prune + id: undefined, + user_id: undefined, + followee_favorites: undefined, + artwork: undefined, + downloadable: undefined, + favorite_count: undefined + } + + delete marshalled.id + delete marshalled.user_id + delete marshalled.followee_favorites + delete marshalled.artwork + delete marshalled.downloadable + delete marshalled.favorite_count + + return marshalled +} diff --git a/src/services/audius-api-client/types.ts b/src/services/audius-api-client/types.ts new file mode 100644 index 0000000000..79cda23e25 --- /dev/null +++ b/src/services/audius-api-client/types.ts @@ -0,0 +1,103 @@ +import { Nullable } from 'utils/typeUtils' + +import { CID } from 'models/common/Identifiers' +import { + CoverArtSizes, + CoverPhotoSizes, + ProfilePictureSizes +} from 'models/common/ImageSizes' +import { FavoriteType } from 'models/Favorite' +import { Download, FieldVisibility, TrackSegment } from 'models/Track' + +type OpaqueID = string + +export type APIUser = { + album_count: number + bio: Nullable + cover_photo: CoverPhotoSizes + followee_count: number + follower_count: number + handle: string + id: OpaqueID + is_verified: boolean + location: Nullable + name: string + playlist_count: number + profile_picture: ProfilePictureSizes + repost_count: number + track_count: number + created_at: string + creator_node_endpoint: Nullable + current_user_followee_follow_count: number + does_current_user_follow: boolean + handle_lc: string + is_creator: boolean + updated_at: string + cover_photo_sizes: Nullable + profile_picture_sizes: Nullable +} + +export type APIRepost = { + repost_item_id: string + repost_type: string + user_id: string +} + +export type APIFavorite = { + favorite_item_id: string + favorite_type: FavoriteType + user_id: string +} + +export type APIRemix = { + parent_track_id: OpaqueID + user: APIUser + has_remix_author_reposted: boolean + has_remix_author_saved: boolean +} + +export type APITrack = { + artwork: CoverArtSizes + description: Nullable + genre: string + id: OpaqueID + mood: Nullable + release_date: Nullable + remix_of: { + tracks: null | APIRemix[] + } + repost_count: number + favorite_count: number + tags: Nullable + title: string + // TODO + user: APIUser + duration: number + downloadable: boolean + create_date: Nullable + created_at: string + credits_splits: Nullable + cover_art_sizes: string + download: Download + isrc: Nullable + license: Nullable + iswc: Nullable + field_visibility: FieldVisibility + followee_reposts: APIRepost[] + has_current_user_reposted: boolean + is_unlisted: boolean + has_current_user_saved: boolean + followee_favorites: APIFavorite[] + route_id: string + stem_of: any + track_segments: TrackSegment[] + updated_at: string + user_id: OpaqueID + is_delete: boolean + cover_art: Nullable + play_count: number +} + +export type APIResponse = { + data: T +} diff --git a/src/store/cache/tracks/utils/helpers.ts b/src/store/cache/tracks/utils/helpers.ts index 4c69593ec5..662c1b8f0f 100644 --- a/src/store/cache/tracks/utils/helpers.ts +++ b/src/store/cache/tracks/utils/helpers.ts @@ -1,11 +1,10 @@ -import Track from 'models/Track' +import Track, { UserTrackMetadata } from 'models/Track' import User from 'models/User' import { Kind } from 'store/types' import { put } from 'redux-saga/effects' import * as cacheActions from 'store/cache/actions' import { reformat as reformatUser } from 'store/cache/users/utils' import { makeUid } from 'utils/uid' -import { UserTrackMetadata } from 'services/audius-api-client/AudiusAPIClient' /** * Adds users from track metadata to cache. diff --git a/src/store/cache/tracks/utils/processAndCacheTracks.ts b/src/store/cache/tracks/utils/processAndCacheTracks.ts index 39008c05b9..5bf9aa87dd 100644 --- a/src/store/cache/tracks/utils/processAndCacheTracks.ts +++ b/src/store/cache/tracks/utils/processAndCacheTracks.ts @@ -4,8 +4,7 @@ import { reformat } from './reformat' import { Kind } from 'store/types' import { makeUid } from 'utils/uid' import { addUsersFromTracks } from './helpers' -import { UserTrackMetadata } from 'services/audius-api-client/AudiusAPIClient' -import Track from 'models/Track' +import Track, { UserTrackMetadata } from 'models/Track' /** * Processes tracks, adding users and calling `reformat`, before From 577c7d48da222427000508c828c955378f02301d Mon Sep 17 00:00:00 2001 From: Michael Piazza Date: Wed, 23 Sep 2020 23:39:52 -0700 Subject: [PATCH 05/14] Move APIClient --- .../track-page/store/retrieveTrending.ts | 4 +--- src/services/audius-api-client/AudiusAPIClient.ts | 15 +++++---------- src/store/backend/sagas.js | 4 ++++ 3 files changed, 10 insertions(+), 13 deletions(-) diff --git a/src/containers/track-page/store/retrieveTrending.ts b/src/containers/track-page/store/retrieveTrending.ts index 36cf261398..2b51bd5114 100644 --- a/src/containers/track-page/store/retrieveTrending.ts +++ b/src/containers/track-page/store/retrieveTrending.ts @@ -4,7 +4,7 @@ import { ID } from 'models/common/Identifiers' import TimeRange from 'models/TimeRange' import Track, { UserTrackMetadata } from 'models/Track' import { select } from 'redux-saga/effects' -import AudiusAPIClient from 'services/audius-api-client/AudiusAPIClient' +import apiClient from 'services/audius-api-client/AudiusAPIClient' import { getTracks } from 'store/cache/tracks/selectors' import { processAndCacheTracks } from 'store/cache/tracks/utils' import { AppState } from 'store/types' @@ -18,8 +18,6 @@ type RetrieveTrendingArgs = { currentUserId?: ID } -const apiClient = new AudiusAPIClient({ environment: 'development' }) -apiClient.init() export function* retrieveTrending({ timeRange, genre, diff --git a/src/services/audius-api-client/AudiusAPIClient.ts b/src/services/audius-api-client/AudiusAPIClient.ts index cb9bf6d8ff..7080e148bb 100644 --- a/src/services/audius-api-client/AudiusAPIClient.ts +++ b/src/services/audius-api-client/AudiusAPIClient.ts @@ -37,9 +37,7 @@ class AudiusAPIClient { currentUserId, genre }: GetTrendingArgs) { - console.log('Getting trending, awaiting init') await this._awaitInitialization() - console.log('Got trending! running') // TODO: use a query builder let endpoint = `${this.endpoint}/tracks/trending?time=${timeRange}&limit=${limit}&offset=${offset}` if (currentUserId) { @@ -62,18 +60,12 @@ class AudiusAPIClient { if (this.isInitialized) return try { + console.debug('Initializing AudiusAPIClient') let endpoint if (this.environment === 'development') { // Hardcode local DP as endpoint if in development // env endpoint = ENDPOINT_PROVIDER_MAP[this.environment] - await new Promise(resolve => { - console.log('inside init promise') - setTimeout(() => { - console.log('RESOLVING') - resolve() - }, 5000) - }) } else { const endpointProvider = ENDPOINT_PROVIDER_MAP[this.environment] const endpointsResponse = await fetch(endpointProvider) @@ -84,6 +76,7 @@ class AudiusAPIClient { this.endpoint = `${endpoint}/v1/full` this.isInitialized = true if (this.awaitFunc) this.awaitFunc() + console.debug('Initialized AudiusAPIClient') } catch { // TODO: handle this } @@ -103,4 +96,6 @@ class AudiusAPIClient { } } -export default AudiusAPIClient +const instance = new AudiusAPIClient({ environment: 'development' }) + +export default instance diff --git a/src/store/backend/sagas.js b/src/store/backend/sagas.js index 7fc6f9b21a..b363b9a5cb 100644 --- a/src/store/backend/sagas.js +++ b/src/store/backend/sagas.js @@ -19,6 +19,7 @@ import { getIsReachable } from 'store/reachability/selectors' import { hydrateStoreFromCache } from 'store/cache/sagas' import { getIsReadOnlyClient } from 'utils/clientUtil' import { RequestNetworkConnected } from 'services/native-mobile-interface/lifecycle' +import apiClient from 'services/audius-api-client/AudiusAPIClient' const NATIVE_MOBILE = process.env.REACT_APP_NATIVE_MOBILE const REACHABILITY_TIMEOUT_MS = 8 * 1000 @@ -74,6 +75,9 @@ export function* setupBackend() { console.info('Reconnected') } + // Fire and forget init AudiusAPIClient + apiClient.init() + if (getIsReadOnlyClient()) { // Read only clients do a paired down version of libs init // (no account, no creator nodes, etc.). From ea2baa43c9632effab35f3908d60331f5c1acc8f Mon Sep 17 00:00:00 2001 From: Michael Piazza Date: Thu, 24 Sep 2020 00:02:21 -0700 Subject: [PATCH 06/14] Handle environments better, add in URL construction --- .../audius-api-client/AudiusAPIClient.ts | 54 ++++++++++++++----- 1 file changed, 42 insertions(+), 12 deletions(-) diff --git a/src/services/audius-api-client/AudiusAPIClient.ts b/src/services/audius-api-client/AudiusAPIClient.ts index 7080e148bb..d55117cbd0 100644 --- a/src/services/audius-api-client/AudiusAPIClient.ts +++ b/src/services/audius-api-client/AudiusAPIClient.ts @@ -7,10 +7,14 @@ type Environment = 'production' | 'staging' | 'development' const ENDPOINT_PROVIDER_MAP: { [env in Environment]: string } = { development: 'http://docker.for.mac.localhost:5000', - staging: '', + staging: 'https://general-admission.staging.audius.co/api/', production: 'https://api.audius.co' } +const ENDPOINT_MAP = { + trending: '/tracks/trending' +} + type GetTrendingArgs = { timeRange?: TimeRange offset?: number @@ -38,16 +42,17 @@ class AudiusAPIClient { genre }: GetTrendingArgs) { await this._awaitInitialization() - // TODO: use a query builder - let endpoint = `${this.endpoint}/tracks/trending?time=${timeRange}&limit=${limit}&offset=${offset}` - if (currentUserId) { - endpoint = `${endpoint}&user_id=${currentUserId}` - } - if (genre) { - endpoint = `${endpoint}&genre=${genre}` + const params = { + time: timeRange, + limit, + offset, + user_id: currentUserId, + genre } - const trendingResponse: APIResponse = await this.getResponse( + const endpoint = this._constructUrl(ENDPOINT_MAP.trending, params) + + const trendingResponse: APIResponse = await this._getResponse( endpoint ) const adapted = trendingResponse.data @@ -82,6 +87,8 @@ class AudiusAPIClient { } } + // Helpers + _awaitInitialization() { if (this.isInitialized) return return new Promise(resolve => { @@ -89,13 +96,36 @@ class AudiusAPIClient { }) } - // Helpers - async getResponse(resource: string): Promise { + async _getResponse(resource: string): Promise { const response = await fetch(resource) return response.json() } + + _constructUrl( + path: string, + queryParams: { [key: string]: string | number | undefined | null } + ) { + const params = Object.entries(queryParams) + .filter(p => p[1] !== undefined && p[1] !== null) + .map(p => `${p[0]}=${p[1]}`) + .join('&') + return `${this.endpoint}${path}?${params}` + } +} + +const getEnv = () => { + const env = process.env.REACT_APP_ENVIRONMENT + switch (env) { + case 'production': + return 'production' + case 'staging': + return 'staging' + case 'development': + default: + return 'development' + } } -const instance = new AudiusAPIClient({ environment: 'development' }) +const instance = new AudiusAPIClient({ environment: getEnv() }) export default instance From 27221c79c29c36f8c848e35d67600579698ef886 Mon Sep 17 00:00:00 2001 From: Michael Piazza Date: Thu, 24 Sep 2020 16:13:45 -0700 Subject: [PATCH 07/14] Use libs selected discprov in AudiusAPIClient --- src/models/Track.ts | 5 - src/models/User.ts | 3 - src/services/AudiusBackend.js | 34 +++-- .../audius-api-client/AudiusAPIClient.ts | 120 ++++++++++-------- src/services/audius-api-client/types.ts | 1 - 5 files changed, 88 insertions(+), 75 deletions(-) diff --git a/src/models/Track.ts b/src/models/Track.ts index 2f6a51e96c..6d0fae0a9d 100644 --- a/src/models/Track.ts +++ b/src/models/Track.ts @@ -50,11 +50,6 @@ export type RemixOf = { } export type TrackMetadata = { - // create_date: string | null - // file_type: string | null - // is_current: boolean - // length: number | null - // cover_art_url: string activity_timestamp?: string is_delete: boolean track_id: number diff --git a/src/models/User.ts b/src/models/User.ts index cd9dc4526d..7e5a7b43b2 100644 --- a/src/models/User.ts +++ b/src/models/User.ts @@ -5,9 +5,6 @@ import { Nullable } from 'utils/typeUtils' import Timestamped from './common/Timestamped' export type UserMetadata = { - // profile_picture_url: string - // cover_photo_url: string - album_count: number bio: string | null cover_photo: Nullable diff --git a/src/services/AudiusBackend.js b/src/services/AudiusBackend.js index 8dd46faa65..7af38fde41 100644 --- a/src/services/AudiusBackend.js +++ b/src/services/AudiusBackend.js @@ -291,6 +291,15 @@ const fetchImageCID = async (cid, creatorNodeGateways = [], cache = true) => { } class AudiusBackend { + static currentDiscoveryProvider = null + static didSelectDiscoveryProviderListeners = [] + static addDiscoveryProviderSelectionListener(listener) { + AudiusBackend.didSelectDiscoveryProviderListeners.push(listener) + if (AudiusBackend.currentDiscoveryProvider !== null) { + listener(AudiusBackend.currentDiscoveryProvider) + } + } + static async getImageUrl(cid, size, gateways) { if (!cid) return '' try { @@ -357,6 +366,17 @@ class AudiusBackend { } } + // Record the endpoint and reason for selecting the endpoint + static discoveryProviderSelectionCallback(endpoint, decisionTree) { + track(Name.DISCOVERY_PROVIDER_SELECTION, { + endpoint, + reason: decisionTree.map(reason => reason.stage).join(' -> ') + }) + AudiusBackend.didSelectDiscoveryProviderListeners.forEach(listener => + listener(endpoint) + ) + } + static async setup() { // Wait for web3 to load if necessary if (!window.Web3) { @@ -410,14 +430,6 @@ class AudiusBackend { const { web3Error, web3Config } = await AudiusBackend.getWeb3Config() const { ethWeb3Config } = AudiusBackend.getEthWeb3Config() - // Recod the endpoint and reason for selecting the endpoint - const discoveryProviderSelectionCallback = (endpoint, decisionTree) => { - track(Name.DISCOVERY_PROVIDER_SELECTION, { - endpoint, - reason: decisionTree.map(reason => reason.stage).join(' -> ') - }) - } - try { audiusLibs = new AudiusLibs({ web3Config, @@ -425,7 +437,7 @@ class AudiusBackend { discoveryProviderConfig: AudiusLibs.configDiscoveryProvider( null, getRemoteVar(IntKeys.DISCOVERY_PROVIDER_SELECTION_TIMEOUT_MS), - discoveryProviderSelectionCallback + AudiusBackend.discoveryProviderSelectionCallback ), identityServiceConfig: AudiusLibs.configIdentityService( IDENTITY_SERVICE @@ -456,7 +468,9 @@ class AudiusBackend { IDENTITY_SERVICE ), discoveryProviderConfig: AudiusLibs.configDiscoveryProvider( - DISCOVERY_PROVIDER_FALLBACKS.values().next().value + DISCOVERY_PROVIDER_FALLBACKS.values().next().value, + getRemoteVar(IntKeys.DISCOVERY_PROVIDER_SELECTION_TIMEOUT_MS), + AudiusBackend.discoveryProviderSelectionCallback ) }) await audiusLibs.init() diff --git a/src/services/audius-api-client/AudiusAPIClient.ts b/src/services/audius-api-client/AudiusAPIClient.ts index d55117cbd0..e451a8d6d9 100644 --- a/src/services/audius-api-client/AudiusAPIClient.ts +++ b/src/services/audius-api-client/AudiusAPIClient.ts @@ -2,14 +2,7 @@ import TimeRange from 'models/TimeRange' import { removeNullable } from 'utils/typeUtils' import { APIResponse, APITrack } from './types' import * as adapter from './ResponseAdapter' - -type Environment = 'production' | 'staging' | 'development' - -const ENDPOINT_PROVIDER_MAP: { [env in Environment]: string } = { - development: 'http://docker.for.mac.localhost:5000', - staging: 'https://general-admission.staging.audius.co/api/', - production: 'https://api.audius.co' -} +import AudiusBackend from 'services/AudiusBackend' const ENDPOINT_MAP = { trending: '/tracks/trending' @@ -23,15 +16,24 @@ type GetTrendingArgs = { genre?: string } +type InitializationState = + | { state: 'uninitialized ' } + | { + state: 'initializing' + initPromise: Promise + } + | { + state: 'initialized' + endpoint: string + } + class AudiusAPIClient { - isInitialized = false - endpoint: string | null = null - environment: Environment - awaitFunc: (() => void) | null - - constructor({ environment }: { environment: Environment }) { - this.environment = environment - this.awaitFunc = null + initializationState: InitializationState = { state: 'uninitialized ' } + overrideEndpoint?: string + + constructor({ overrideEndpoint }: { overrideEndpoint?: string } = {}) { + console.debug('cons') + this.overrideEndpoint = overrideEndpoint } async getTrending({ @@ -41,7 +43,9 @@ class AudiusAPIClient { currentUserId, genre }: GetTrendingArgs) { + console.log('AWAITING TRENDING') await this._awaitInitialization() + console.log('GOING FORTH W TRENDING') const params = { time: timeRange, limit, @@ -62,38 +66,51 @@ class AudiusAPIClient { } async init() { - if (this.isInitialized) return + console.debug('init') + // Initialized state + if (this.initializationState.state === 'initialized') return - try { - console.debug('Initializing AudiusAPIClient') - let endpoint - if (this.environment === 'development') { - // Hardcode local DP as endpoint if in development - // env - endpoint = ENDPOINT_PROVIDER_MAP[this.environment] - } else { - const endpointProvider = ENDPOINT_PROVIDER_MAP[this.environment] - const endpointsResponse = await fetch(endpointProvider) - const endpointsJson = await endpointsResponse.json() - const { data: endpoints }: { data: string[] } = endpointsJson - endpoint = endpoints[Math.floor(Math.random() * endpoints.length)] - } - this.endpoint = `${endpoint}/v1/full` - this.isInitialized = true - if (this.awaitFunc) this.awaitFunc() - console.debug('Initialized AudiusAPIClient') - } catch { - // TODO: handle this + // Initializing state + if (this.initializationState.state === 'initializing') { + return this.initializationState.initPromise } + + // Uninitialized state + // If override passed, use that and return + if (this.overrideEndpoint) { + const endpoint = `${this.overrideEndpoint}/v1/full` + console.log('Using endpoint: ' + endpoint) + this.initializationState = { state: 'initialized', endpoint: endpoint } + console.debug('using override') + return + } + + // Await for libs discprov selection + const initPromise: Promise = new Promise(resolve => { + console.debug('Initializing AudiusAPIClient') + AudiusBackend.addDiscoveryProviderSelectionListener( + (endpoint: string) => { + const fullEndpoint = `${endpoint}/v1/full` + this.initializationState = { + state: 'initialized', + endpoint: fullEndpoint + } + console.debug('Initialized AudiusAPIClient') + resolve() + } + ) + }) + console.log('setting initializing') + this.initializationState = { state: 'initializing', initPromise } } // Helpers _awaitInitialization() { - if (this.isInitialized) return - return new Promise(resolve => { - this.awaitFunc = resolve - }) + if (this.initializationState.state === 'initialized') return + if (this.initializationState.state === 'initializing') + return this.initializationState.initPromise + throw new Error('Must call init before calling methods on AudiusAPIClient') } async _getResponse(resource: string): Promise { @@ -105,27 +122,18 @@ class AudiusAPIClient { path: string, queryParams: { [key: string]: string | number | undefined | null } ) { + if (this.initializationState.state !== 'initialized') + throw new Error("Can't construct URL in non-initialized state") const params = Object.entries(queryParams) .filter(p => p[1] !== undefined && p[1] !== null) .map(p => `${p[0]}=${p[1]}`) .join('&') - return `${this.endpoint}${path}?${params}` - } -} - -const getEnv = () => { - const env = process.env.REACT_APP_ENVIRONMENT - switch (env) { - case 'production': - return 'production' - case 'staging': - return 'staging' - case 'development': - default: - return 'development' + return `${this.initializationState.endpoint}${path}?${params}` } } -const instance = new AudiusAPIClient({ environment: getEnv() }) +const override = 'http://docker.for.mac.localhost:5000' +const instance = new AudiusAPIClient({ overrideEndpoint: override }) +// const instance = new AudiusAPIClient() export default instance diff --git a/src/services/audius-api-client/types.ts b/src/services/audius-api-client/types.ts index 79cda23e25..e90c849e15 100644 --- a/src/services/audius-api-client/types.ts +++ b/src/services/audius-api-client/types.ts @@ -70,7 +70,6 @@ export type APITrack = { favorite_count: number tags: Nullable title: string - // TODO user: APIUser duration: number downloadable: boolean From 9cc26c57168032bd326f9ff2ba9c859a6d5c0f3b Mon Sep 17 00:00:00 2001 From: Michael Piazza Date: Thu, 24 Sep 2020 17:26:28 -0700 Subject: [PATCH 08/14] Use eager discprov selection in APIClient --- src/services/AudiusBackend.js | 1 + .../audius-api-client/AudiusAPIClient.ts | 72 ++++++++----------- .../audius-api-client/ResponseAdapter.ts | 4 -- src/services/audius-backend/eagerLoadUtils.ts | 2 + src/store/backend/sagas.js | 1 + 5 files changed, 35 insertions(+), 45 deletions(-) diff --git a/src/services/AudiusBackend.js b/src/services/AudiusBackend.js index 7af38fde41..1f444d05e3 100644 --- a/src/services/AudiusBackend.js +++ b/src/services/AudiusBackend.js @@ -368,6 +368,7 @@ class AudiusBackend { // Record the endpoint and reason for selecting the endpoint static discoveryProviderSelectionCallback(endpoint, decisionTree) { + console.log('GET DP: ' + Date.now()) track(Name.DISCOVERY_PROVIDER_SELECTION, { endpoint, reason: decisionTree.map(reason => reason.stage).join(' -> ') diff --git a/src/services/audius-api-client/AudiusAPIClient.ts b/src/services/audius-api-client/AudiusAPIClient.ts index e451a8d6d9..59d08b5241 100644 --- a/src/services/audius-api-client/AudiusAPIClient.ts +++ b/src/services/audius-api-client/AudiusAPIClient.ts @@ -3,6 +3,7 @@ import { removeNullable } from 'utils/typeUtils' import { APIResponse, APITrack } from './types' import * as adapter from './ResponseAdapter' import AudiusBackend from 'services/AudiusBackend' +import { getEagerDiscprov } from 'services/audius-backend/eagerLoadUtils' const ENDPOINT_MAP = { trending: '/tracks/trending' @@ -18,10 +19,6 @@ type GetTrendingArgs = { type InitializationState = | { state: 'uninitialized ' } - | { - state: 'initializing' - initPromise: Promise - } | { state: 'initialized' endpoint: string @@ -32,7 +29,6 @@ class AudiusAPIClient { overrideEndpoint?: string constructor({ overrideEndpoint }: { overrideEndpoint?: string } = {}) { - console.debug('cons') this.overrideEndpoint = overrideEndpoint } @@ -43,9 +39,7 @@ class AudiusAPIClient { currentUserId, genre }: GetTrendingArgs) { - console.log('AWAITING TRENDING') - await this._awaitInitialization() - console.log('GOING FORTH W TRENDING') + this._assertInitialized() const params = { time: timeRange, limit, @@ -55,7 +49,6 @@ class AudiusAPIClient { } const endpoint = this._constructUrl(ENDPOINT_MAP.trending, params) - const trendingResponse: APIResponse = await this._getResponse( endpoint ) @@ -65,52 +58,45 @@ class AudiusAPIClient { return adapted } - async init() { - console.debug('init') + init() { // Initialized state if (this.initializationState.state === 'initialized') return - // Initializing state - if (this.initializationState.state === 'initializing') { - return this.initializationState.initPromise - } - // Uninitialized state // If override passed, use that and return if (this.overrideEndpoint) { - const endpoint = `${this.overrideEndpoint}/v1/full` - console.log('Using endpoint: ' + endpoint) + const endpoint = this._formatEndpoint(this.overrideEndpoint) + console.debug(`APIClient: Using override endpoint: ${endpoint}`) this.initializationState = { state: 'initialized', endpoint: endpoint } - console.debug('using override') return } - // Await for libs discprov selection - const initPromise: Promise = new Promise(resolve => { - console.debug('Initializing AudiusAPIClient') - AudiusBackend.addDiscoveryProviderSelectionListener( - (endpoint: string) => { - const fullEndpoint = `${endpoint}/v1/full` - this.initializationState = { - state: 'initialized', - endpoint: fullEndpoint - } - console.debug('Initialized AudiusAPIClient') - resolve() - } - ) + // Set the state to the eager discprov + const eagerDiscprov = getEagerDiscprov() + const fullDiscprov = this._formatEndpoint(eagerDiscprov) + console.debug(`APIClient: setting to eager discprov: ${fullDiscprov}`) + this.initializationState = { + state: 'initialized', + endpoint: fullDiscprov + } + + // Listen for libs on chain selection + AudiusBackend.addDiscoveryProviderSelectionListener((endpoint: string) => { + const fullEndpoint = this._formatEndpoint(endpoint) + console.debug(`APIClient: Setting to libs discprov: ${fullEndpoint}`) + this.initializationState = { + state: 'initialized', + endpoint: fullEndpoint + } }) - console.log('setting initializing') - this.initializationState = { state: 'initializing', initPromise } + console.debug('APIClient: Initialized') } // Helpers - _awaitInitialization() { - if (this.initializationState.state === 'initialized') return - if (this.initializationState.state === 'initializing') - return this.initializationState.initPromise - throw new Error('Must call init before calling methods on AudiusAPIClient') + _assertInitialized() { + if (this.initializationState.state !== 'initialized') + throw new Error('AudiusAPIClient must be initialized before use') } async _getResponse(resource: string): Promise { @@ -118,12 +104,16 @@ class AudiusAPIClient { return response.json() } + _formatEndpoint(endpoint: string) { + return `${endpoint}/v1/full` + } + _constructUrl( path: string, queryParams: { [key: string]: string | number | undefined | null } ) { if (this.initializationState.state !== 'initialized') - throw new Error("Can't construct URL in non-initialized state") + throw new Error('_constructURL called uninitialized') const params = Object.entries(queryParams) .filter(p => p[1] !== undefined && p[1] !== null) .map(p => `${p[0]}=${p[1]}`) diff --git a/src/services/audius-api-client/ResponseAdapter.ts b/src/services/audius-api-client/ResponseAdapter.ts index d0f200ab5e..6819eb1ca9 100644 --- a/src/services/audius-api-client/ResponseAdapter.ts +++ b/src/services/audius-api-client/ResponseAdapter.ts @@ -29,7 +29,6 @@ const makeFavorite = (favorite: APIFavorite): Favorite | undefined => { const decodedSaveItemId = decodeHashId(favorite.favorite_item_id) const decodedUserId = decodeHashId(favorite.user_id) if (!decodedSaveItemId || !decodedUserId) { - // TODO: handle this better return undefined } return { @@ -43,7 +42,6 @@ const makeRepost = (repost: APIRepost): Repost | undefined => { const decodedRepostItemId = decodeHashId(repost.repost_item_id) const decodedUserId = decodeHashId(repost.user_id) if (!decodedRepostItemId || !decodedUserId) { - // TODO return undefined } @@ -69,12 +67,10 @@ const makeRemix = (remix: APIRemix): Remix | undefined => { } export const makeTrack = (track: APITrack): UserTrackMetadata | undefined => { - // TODO: if I get this working, let's look into io.TS... const decodedTrackId = decodeHashId(track.id) const decodedOwnerId = decodeHashId(track.user_id) const user = makeUser(track.user) if (!decodedTrackId || !decodedOwnerId || !user) { - // TODO: handle errors better return undefined } diff --git a/src/services/audius-backend/eagerLoadUtils.ts b/src/services/audius-backend/eagerLoadUtils.ts index b70498df60..7731f9bf51 100644 --- a/src/services/audius-backend/eagerLoadUtils.ts +++ b/src/services/audius-backend/eagerLoadUtils.ts @@ -31,6 +31,8 @@ if (cachedDiscprov) { ] } +export const getEagerDiscprov = () => eagerDiscprov + /** * Wait for the `LIBS_INITTED_EVENT` or pass through if there * already exists a mounted `window.audiusLibs` object. diff --git a/src/store/backend/sagas.js b/src/store/backend/sagas.js index b363b9a5cb..45b9297e65 100644 --- a/src/store/backend/sagas.js +++ b/src/store/backend/sagas.js @@ -93,6 +93,7 @@ export function* setupBackend() { return } yield spawn(hydrateStoreFromCache) + console.log('BE SUCCEEDED: ' + Date.now()) yield put(backendActions.setupBackendSucceeded(web3Error)) } } From 9251826a6522c8fdfcc1ad965625a06de0d65906 Mon Sep 17 00:00:00 2001 From: Michael Piazza Date: Thu, 24 Sep 2020 17:46:54 -0700 Subject: [PATCH 09/14] Polish --- src/containers/discover-page/store/actions.js | 7 ------- .../store/lineups/trending/sagas.js | 19 +------------------ src/containers/discover-page/store/reducer.js | 10 +--------- .../discover-page/store/selectors.js | 2 -- src/containers/discover-page/store/types.ts | 1 - .../track-page/store/retrieveTrending.ts | 11 ++++++++--- 6 files changed, 10 insertions(+), 40 deletions(-) diff --git a/src/containers/discover-page/store/actions.js b/src/containers/discover-page/store/actions.js index d475d9c84f..0904162094 100644 --- a/src/containers/discover-page/store/actions.js +++ b/src/containers/discover-page/store/actions.js @@ -7,8 +7,6 @@ export const SET_TRENDING_GENRE = 'DISCOVER/SET_TRENDING_GENRE' export const SET_TRENDING_TIME_RANGE = 'DISCOVER/SET_TRENDING_TIME_RANGE' export const SET_LAST_FETCHED_TRENDING_GENRE = 'DISCOVER/SET_LAST_FETCHED_TRENDING_GENRE' -export const SET_LAST_FETCHED_TIME_RANGE = - 'DISCOVER/SET_LAST_FETCHED_TIME_RANGE' export const fetchSuggestedFollowUsers = () => ({ type: FETCH_SUGGESTED_FOLLOW_USERS @@ -43,8 +41,3 @@ export const setLastFetchedTrendingGenre = genre => ({ type: SET_LAST_FETCHED_TRENDING_GENRE, genre }) - -export const setLastFetchedTimeRange = timeRange => ({ - type: SET_LAST_FETCHED_TIME_RANGE, - timeRange -}) diff --git a/src/containers/discover-page/store/lineups/trending/sagas.js b/src/containers/discover-page/store/lineups/trending/sagas.js index 5e1977561d..77c0a11ac1 100644 --- a/src/containers/discover-page/store/lineups/trending/sagas.js +++ b/src/containers/discover-page/store/lineups/trending/sagas.js @@ -1,9 +1,5 @@ -import { put, select } from 'redux-saga/effects' +import { select } from 'redux-saga/effects' -import { - setLastFetchedTrendingGenre, - setLastFetchedTimeRange -} from 'containers/discover-page/store/actions' import { LineupSagas } from 'store/lineup/sagas' import TimeRange from 'models/TimeRange' @@ -24,12 +20,6 @@ function getTracks(timeRange) { return function* ({ offset, limit }) { // Possibly abort early const genreAtStart = yield select(getTrendingGenre) - // const trendingEntries = yield select(getTrendingEntries(timeRange)) - // TODO: figure out how to handle this with pagination now... - // const needsRefetch = - // !Object.keys(trendingEntries).length || genreAtStart !== lastGenre - // if (!needsRefetch) return [] - const userId = yield select(getUserId) const tracks = yield retrieveTrending({ timeRange, @@ -39,13 +29,6 @@ function getTracks(timeRange) { currentUserId: userId }) - // const genreAtEnd = yield select(getTrendingGenre) - // if (genreAtStart !== genreAtEnd) { - // return null - // } - - yield put(setLastFetchedTrendingGenre(genreAtStart)) - yield put(setLastFetchedTimeRange(timeRange)) return tracks } } diff --git a/src/containers/discover-page/store/reducer.js b/src/containers/discover-page/store/reducer.js index 69da3cb129..549c462667 100644 --- a/src/containers/discover-page/store/reducer.js +++ b/src/containers/discover-page/store/reducer.js @@ -19,8 +19,7 @@ import { SET_FEED_FILTER, SET_TRENDING_GENRE, SET_TRENDING_TIME_RANGE, - SET_LAST_FETCHED_TRENDING_GENRE, - SET_LAST_FETCHED_TIME_RANGE + SET_LAST_FETCHED_TRENDING_GENRE } from 'containers/discover-page/store/actions' import FeedFilter from 'models/FeedFilter' @@ -36,7 +35,6 @@ const initialState = { ? timeRange : TimeRange.WEEK, trendingGenre: Object.values(GENRES).includes(genre) ? genre : null, - lastFetchedTrendingTimeRange: null, lastFetchedTrendingGenre: null } @@ -70,12 +68,6 @@ const actionsMap = { ...state, lastFetchedTrendingGenre: action.genre } - }, - [SET_LAST_FETCHED_TIME_RANGE](state, action) { - return { - ...state, - lastFetchedTrendingTimeRange: action.timeRange - } } } diff --git a/src/containers/discover-page/store/selectors.js b/src/containers/discover-page/store/selectors.js index 7112c2b3ee..ea049d8b4f 100644 --- a/src/containers/discover-page/store/selectors.js +++ b/src/containers/discover-page/store/selectors.js @@ -29,8 +29,6 @@ export const getFeedFilter = state => state.discover.feedFilter export const getTrendingTimeRange = state => state.discover.trendingTimeRange export const getTrendingGenre = state => state.discover.trendingGenre -export const getLastFetchedTrendingTimeRange = state => - state.discover.lastFetchedTrendingTimeRange export const getLastFetchedTrendingGenre = state => state.discover.lastFetchedTrendingGenre diff --git a/src/containers/discover-page/store/types.ts b/src/containers/discover-page/store/types.ts index 295dd8aa57..0050b2871e 100644 --- a/src/containers/discover-page/store/types.ts +++ b/src/containers/discover-page/store/types.ts @@ -12,6 +12,5 @@ export default interface DiscoveryPageState { feedFilter: FeedFilter trendingTimeRange: TimeRange trendingGenre: string | null - lastFetchedTrendingTimeRange: TimeRange | null lastFetchedTrendingGenre: string | null } diff --git a/src/containers/track-page/store/retrieveTrending.ts b/src/containers/track-page/store/retrieveTrending.ts index 2b51bd5114..bee3683081 100644 --- a/src/containers/track-page/store/retrieveTrending.ts +++ b/src/containers/track-page/store/retrieveTrending.ts @@ -1,9 +1,10 @@ +import { setLastFetchedTrendingGenre } from 'containers/discover-page/store/actions' import { getTrendingEntries } from 'containers/discover-page/store/lineups/trending/selectors' import { getLastFetchedTrendingGenre } from 'containers/discover-page/store/selectors' import { ID } from 'models/common/Identifiers' import TimeRange from 'models/TimeRange' import Track, { UserTrackMetadata } from 'models/Track' -import { select } from 'redux-saga/effects' +import { put, select } from 'redux-saga/effects' import apiClient from 'services/audius-api-client/AudiusAPIClient' import { getTracks } from 'store/cache/tracks/selectors' import { processAndCacheTracks } from 'store/cache/tracks/utils' @@ -30,8 +31,7 @@ export function* retrieveTrending({ >> = yield select(getTrendingEntries(timeRange)) const lastGenre = yield select(getLastFetchedTrendingGenre) - // TODO: remove this out - // yield apiClient.init() + yield put(setLastFetchedTrendingGenre(genre)) const useCached = lastGenre === genre && @@ -59,6 +59,11 @@ export function* retrieveTrending({ timeRange }) + const currentGenre = yield select(getLastFetchedTrendingGenre) + + // If we changed genres, do nothing + if (currentGenre !== lastGenre) return [] + const processed: Track[] = yield processAndCacheTracks(apiTracks) return processed } From 455c48d99335744f54d81a8b8ca2e3865044b68b Mon Sep 17 00:00:00 2001 From: Michael Piazza Date: Thu, 24 Sep 2020 17:55:39 -0700 Subject: [PATCH 10/14] More polish --- .../discover-page/store/lineups/trending/sagas.js | 1 - src/containers/track-page/TrackPageProvider.tsx | 2 -- src/containers/track-page/store/retrieveTrending.ts | 5 +---- src/models/Favorite.ts | 1 - src/models/Track.ts | 1 - src/services/AudiusBackend.js | 1 - src/services/audius-api-client/AudiusAPIClient.ts | 8 ++++---- src/store/backend/sagas.js | 4 +--- src/store/cache/tracks/utils/reformat.ts | 1 - 9 files changed, 6 insertions(+), 18 deletions(-) diff --git a/src/containers/discover-page/store/lineups/trending/sagas.js b/src/containers/discover-page/store/lineups/trending/sagas.js index 77c0a11ac1..6e2dd7c696 100644 --- a/src/containers/discover-page/store/lineups/trending/sagas.js +++ b/src/containers/discover-page/store/lineups/trending/sagas.js @@ -18,7 +18,6 @@ import { retrieveTrending } from 'containers/track-page/store/retrieveTrending' function getTracks(timeRange) { return function* ({ offset, limit }) { - // Possibly abort early const genreAtStart = yield select(getTrendingGenre) const userId = yield select(getUserId) const tracks = yield retrieveTrending({ diff --git a/src/containers/track-page/TrackPageProvider.tsx b/src/containers/track-page/TrackPageProvider.tsx index e217b3f9eb..513db44b0d 100644 --- a/src/containers/track-page/TrackPageProvider.tsx +++ b/src/containers/track-page/TrackPageProvider.tsx @@ -197,7 +197,6 @@ class TrackPageProvider extends Component< } fetchTracks = (params: NonNullable) => { - console.log('fetch track') const { track } = this.props const { trackTitle, trackId, handle } = params @@ -209,7 +208,6 @@ class TrackPageProvider extends Component< } } this.props.reset() - console.log('setting track: ' + trackId) this.props.setTrackId(trackId) this.props.fetchTrack(trackId, trackTitle, handle, !!(trackTitle && handle)) if (handle) { diff --git a/src/containers/track-page/store/retrieveTrending.ts b/src/containers/track-page/store/retrieveTrending.ts index bee3683081..53f5bbd559 100644 --- a/src/containers/track-page/store/retrieveTrending.ts +++ b/src/containers/track-page/store/retrieveTrending.ts @@ -33,10 +33,7 @@ export function* retrieveTrending({ const lastGenre = yield select(getLastFetchedTrendingGenre) yield put(setLastFetchedTrendingGenre(genre)) - const useCached = - lastGenre === genre && - cachedTracks?.length > 0 && - cachedTracks.length > offset + limit + const useCached = lastGenre === genre && cachedTracks.length > offset + limit if (useCached) { const trackIds = cachedTracks.slice(offset, limit + offset).map(t => t.id) diff --git a/src/models/Favorite.ts b/src/models/Favorite.ts index f7261fc32e..04ffaf9bbd 100644 --- a/src/models/Favorite.ts +++ b/src/models/Favorite.ts @@ -6,7 +6,6 @@ export enum FavoriteType { } type Favorite = { - // is_delete: boolean save_item_id: ID save_type: FavoriteType user_id: number diff --git a/src/models/Track.ts b/src/models/Track.ts index 6d0fae0a9d..1ac4104ace 100644 --- a/src/models/Track.ts +++ b/src/models/Track.ts @@ -106,7 +106,6 @@ export type ComputedTrackProperties = { _cover_art_color?: Color _marked_deleted?: boolean _is_publishing?: boolean - // Computed! _stems?: Stem[] // Present iff remixes have been fetched for a track diff --git a/src/services/AudiusBackend.js b/src/services/AudiusBackend.js index 1f444d05e3..7af38fde41 100644 --- a/src/services/AudiusBackend.js +++ b/src/services/AudiusBackend.js @@ -368,7 +368,6 @@ class AudiusBackend { // Record the endpoint and reason for selecting the endpoint static discoveryProviderSelectionCallback(endpoint, decisionTree) { - console.log('GET DP: ' + Date.now()) track(Name.DISCOVERY_PROVIDER_SELECTION, { endpoint, reason: decisionTree.map(reason => reason.stage).join(' -> ') diff --git a/src/services/audius-api-client/AudiusAPIClient.ts b/src/services/audius-api-client/AudiusAPIClient.ts index 59d08b5241..b1af5cda61 100644 --- a/src/services/audius-api-client/AudiusAPIClient.ts +++ b/src/services/audius-api-client/AudiusAPIClient.ts @@ -9,6 +9,8 @@ const ENDPOINT_MAP = { trending: '/tracks/trending' } +const TRENDING_LIMIT = 100 + type GetTrendingArgs = { timeRange?: TimeRange offset?: number @@ -34,7 +36,7 @@ class AudiusAPIClient { async getTrending({ timeRange = TimeRange.WEEK, - limit = 200, + limit = TRENDING_LIMIT, offset = 0, currentUserId, genre @@ -122,8 +124,6 @@ class AudiusAPIClient { } } -const override = 'http://docker.for.mac.localhost:5000' -const instance = new AudiusAPIClient({ overrideEndpoint: override }) -// const instance = new AudiusAPIClient() +const instance = new AudiusAPIClient() export default instance diff --git a/src/store/backend/sagas.js b/src/store/backend/sagas.js index 45b9297e65..416105c9f9 100644 --- a/src/store/backend/sagas.js +++ b/src/store/backend/sagas.js @@ -75,8 +75,7 @@ export function* setupBackend() { console.info('Reconnected') } - // Fire and forget init AudiusAPIClient - apiClient.init() + yield call(apiClient.init) if (getIsReadOnlyClient()) { // Read only clients do a paired down version of libs init @@ -93,7 +92,6 @@ export function* setupBackend() { return } yield spawn(hydrateStoreFromCache) - console.log('BE SUCCEEDED: ' + Date.now()) yield put(backendActions.setupBackendSucceeded(web3Error)) } } diff --git a/src/store/cache/tracks/utils/reformat.ts b/src/store/cache/tracks/utils/reformat.ts index 324a878951..1c0cb52ad2 100644 --- a/src/store/cache/tracks/utils/reformat.ts +++ b/src/store/cache/tracks/utils/reformat.ts @@ -59,7 +59,6 @@ export const reformat = (track: T): Track => { const withImages = addTrackImages(withoutUser) const withCosign = setIsCoSigned(withImages) - // TODO: we can possibly remove this now, look into it const withDefaultSaves = setDefaultFolloweeSaves(withCosign) return withDefaultSaves } From 6373892f6d0e5cb1e64e510fad51c426494a4412 Mon Sep 17 00:00:00 2001 From: Michael Piazza Date: Thu, 24 Sep 2020 18:01:41 -0700 Subject: [PATCH 11/14] Fix apiClient.init() saga bug --- src/store/backend/sagas.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/store/backend/sagas.js b/src/store/backend/sagas.js index 416105c9f9..70e0f4d690 100644 --- a/src/store/backend/sagas.js +++ b/src/store/backend/sagas.js @@ -75,7 +75,7 @@ export function* setupBackend() { console.info('Reconnected') } - yield call(apiClient.init) + yield call(() => apiClient.init()) if (getIsReadOnlyClient()) { // Read only clients do a paired down version of libs init From 159822931186c67371594ba0cddf4c0bafc952f2 Mon Sep 17 00:00:00 2001 From: Michael Piazza Date: Thu, 24 Sep 2020 18:09:48 -0700 Subject: [PATCH 12/14] Handle trending response errors --- .../store/lineups/trending/sagas.js | 22 +++++++++++-------- .../audius-api-client/AudiusAPIClient.ts | 3 +-- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/containers/discover-page/store/lineups/trending/sagas.js b/src/containers/discover-page/store/lineups/trending/sagas.js index 6e2dd7c696..83330edd86 100644 --- a/src/containers/discover-page/store/lineups/trending/sagas.js +++ b/src/containers/discover-page/store/lineups/trending/sagas.js @@ -20,15 +20,19 @@ function getTracks(timeRange) { return function* ({ offset, limit }) { const genreAtStart = yield select(getTrendingGenre) const userId = yield select(getUserId) - const tracks = yield retrieveTrending({ - timeRange, - limit, - offset, - genre: genreAtStart, - currentUserId: userId - }) - - return tracks + try { + const tracks = yield retrieveTrending({ + timeRange, + limit, + offset, + genre: genreAtStart, + currentUserId: userId + }) + return tracks + } catch (e) { + console.error(`Trending error: ${e.message}`) + return [] + } } } diff --git a/src/services/audius-api-client/AudiusAPIClient.ts b/src/services/audius-api-client/AudiusAPIClient.ts index b1af5cda61..3b17661ad8 100644 --- a/src/services/audius-api-client/AudiusAPIClient.ts +++ b/src/services/audius-api-client/AudiusAPIClient.ts @@ -61,10 +61,8 @@ class AudiusAPIClient { } init() { - // Initialized state if (this.initializationState.state === 'initialized') return - // Uninitialized state // If override passed, use that and return if (this.overrideEndpoint) { const endpoint = this._formatEndpoint(this.overrideEndpoint) @@ -103,6 +101,7 @@ class AudiusAPIClient { async _getResponse(resource: string): Promise { const response = await fetch(resource) + if (!response.ok) throw new Error(response.statusText) return response.json() } From 4f5d45074323d53078664155949f22d3ae66e362 Mon Sep 17 00:00:00 2001 From: Michael Piazza Date: Thu, 24 Sep 2020 19:11:39 -0700 Subject: [PATCH 13/14] Fix trending tabs issue --- src/containers/track-page/store/retrieveTrending.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/containers/track-page/store/retrieveTrending.ts b/src/containers/track-page/store/retrieveTrending.ts index 53f5bbd559..b8832e45fb 100644 --- a/src/containers/track-page/store/retrieveTrending.ts +++ b/src/containers/track-page/store/retrieveTrending.ts @@ -1,6 +1,9 @@ import { setLastFetchedTrendingGenre } from 'containers/discover-page/store/actions' import { getTrendingEntries } from 'containers/discover-page/store/lineups/trending/selectors' -import { getLastFetchedTrendingGenre } from 'containers/discover-page/store/selectors' +import { + getLastFetchedTrendingGenre, + getTrendingGenre +} from 'containers/discover-page/store/selectors' import { ID } from 'models/common/Identifiers' import TimeRange from 'models/TimeRange' import Track, { UserTrackMetadata } from 'models/Track' @@ -56,10 +59,10 @@ export function* retrieveTrending({ timeRange }) - const currentGenre = yield select(getLastFetchedTrendingGenre) + const currentGenre = yield select(getTrendingGenre) // If we changed genres, do nothing - if (currentGenre !== lastGenre) return [] + if (currentGenre !== genre) return [] const processed: Track[] = yield processAndCacheTracks(apiTracks) return processed From aea1937249d5d37a1f0469c9dcfe36a6b271ade6 Mon Sep 17 00:00:00 2001 From: Raymond Jacobson Date: Fri, 25 Sep 2020 17:25:15 -0700 Subject: [PATCH 14/14] Clean up --- src/containers/track-page/TrackPageProvider.tsx | 2 +- src/services/audius-api-client/AudiusAPIClient.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/containers/track-page/TrackPageProvider.tsx b/src/containers/track-page/TrackPageProvider.tsx index 513db44b0d..46ce16b4d3 100644 --- a/src/containers/track-page/TrackPageProvider.tsx +++ b/src/containers/track-page/TrackPageProvider.tsx @@ -398,7 +398,7 @@ class TrackPageProvider extends Component< const description = getTrackPageDescription({ releaseDate: releaseDate ? formatDate(releaseDate) : '', description: track?.description ?? '', - mood: track?.description ?? '', + mood: track?.mood ?? '', genre: track ? getCannonicalName(track.genre) : '', duration: track ? formatSeconds(track.duration) : '', tags: track ? (track.tags || '').split(',').filter(Boolean) : [] diff --git a/src/services/audius-api-client/AudiusAPIClient.ts b/src/services/audius-api-client/AudiusAPIClient.ts index 3b17661ad8..f270365392 100644 --- a/src/services/audius-api-client/AudiusAPIClient.ts +++ b/src/services/audius-api-client/AudiusAPIClient.ts @@ -117,7 +117,7 @@ class AudiusAPIClient { throw new Error('_constructURL called uninitialized') const params = Object.entries(queryParams) .filter(p => p[1] !== undefined && p[1] !== null) - .map(p => `${p[0]}=${p[1]}`) + .map(p => `${p[0]}=${encodeURIComponent(p[1]!)}`) .join('&') return `${this.initializationState.endpoint}${path}?${params}` }