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/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/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/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 1d2ca34806..83330edd86 100644 --- a/src/containers/discover-page/store/lineups/trending/sagas.js +++ b/src/containers/discover-page/store/lineups/trending/sagas.js @@ -1,20 +1,9 @@ -import { call, put, select } from 'redux-saga/effects' +import { select } from 'redux-saga/effects' -import AudiusBackend from 'services/AudiusBackend' -import { makeGetTrendingOrder, makeGetTrendingStats } from './selectors' -import { - setLastFetchedTrendingGenre, - setLastFetchedTimeRange -} from 'containers/discover-page/store/actions' 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,108 +13,27 @@ 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 - }) - const trendingOrder = trendingResponse.listen_counts - .sort(sortByTrendingScore(timeRange)) - .map(t => t.track_id) - const trendingStats = trendingResponse.listen_counts.reduce((stats, t) => { - stats[t.track_id] = { - ...t, - score: getTrendingScore(t, timeRange) - } - return stats - }, {}) - return { trendingOrder, trendingStats } -} +import { getUserId } from 'store/account/selectors' +import { retrieveTrending } from 'containers/track-page/store/retrieveTrending' -// 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 -} - -// 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 }) { - 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) - }) - - // 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 genreAtStart = yield select(getTrendingGenre) + const userId = yield select(getUserId) + try { + const tracks = yield retrieveTrending({ + timeRange, + limit, + offset, + genre: genreAtStart, + currentUserId: userId + }) + return tracks + } catch (e) { + console.error(`Trending error: ${e.message}`) + return [] } - - ret = trackIds - } else { - ret = [] } - - yield put(setLastFetchedTrendingGenre(genreAtStart)) - yield put(setLastFetchedTimeRange(timeRange)) - return ret } class TrendingWeekSagas extends LineupSagas { @@ -134,9 +42,7 @@ class TrendingWeekSagas extends LineupSagas { TRENDING_WEEK_PREFIX, trendingWeekActions, store => store.discover.trendingWeek, - makeGetTracks(function* () { - return yield TimeRange.WEEK - }) + getTracks(TimeRange.WEEK) ) } } @@ -147,9 +53,7 @@ class TrendingMonthSagas extends LineupSagas { TRENDING_MONTH_PREFIX, trendingMonthActions, store => store.discover.trendingMonth, - makeGetTracks(function* () { - return yield TimeRange.MONTH - }) + getTracks(TimeRange.MONTH) ) } } @@ -160,9 +64,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/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/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/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..46ce16b4d3 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?.mood ?? '', genre: track ? getCannonicalName(track.genre) : '', duration: track ? formatSeconds(track.duration) : '', tags: track ? (track.tags || '').split(',').filter(Boolean) : [] diff --git a/src/containers/track-page/store/retrieveTrending.ts b/src/containers/track-page/store/retrieveTrending.ts new file mode 100644 index 0000000000..b8832e45fb --- /dev/null +++ b/src/containers/track-page/store/retrieveTrending.ts @@ -0,0 +1,69 @@ +import { setLastFetchedTrendingGenre } from 'containers/discover-page/store/actions' +import { getTrendingEntries } from 'containers/discover-page/store/lineups/trending/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' +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' +import { AppState } from 'store/types' +import { encodeHashId } from 'utils/route/hashIds' + +type RetrieveTrendingArgs = { + timeRange: TimeRange + genre?: string + offset: number + limit: number + currentUserId?: ID +} + +export function* retrieveTrending({ + timeRange, + genre, + offset, + limit, + currentUserId +}: RetrieveTrendingArgs): Generator { + const cachedTracks: ReturnType> = yield select(getTrendingEntries(timeRange)) + + const lastGenre = yield select(getLastFetchedTrendingGenre) + yield put(setLastFetchedTrendingGenre(genre)) + + const useCached = lastGenre === genre && 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 currentGenre = yield select(getTrendingGenre) + + // If we changed genres, do nothing + if (currentGenre !== genre) return [] + + 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/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..04ffaf9bbd 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,7 @@ export enum FavoriteType { PLAYLIST = 'playlist' } -type Favorite = OnChain & { - created_at: string - is_current: boolean - is_delete: boolean +type Favorite = { 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..1ac4104ace 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 User, { UserMetadata } 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,47 @@ export type RemixOf = { } export type TrackMetadata = { - create_date: string | null + 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 + cover_art_sizes: 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,13 +98,8 @@ 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 ComputedTrackProperties = { + // All below, added clientside _cover_art_sizes: CoverArtSizes _first_segment?: string _followees?: Followee[] @@ -108,13 +111,15 @@ export type Track = { // 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 = TrackMetadata & ComputedTrackProperties + +export type UserTrackMetadata = TrackMetadata & { user: UserMetadata } export type UserTrack = Track & { user: User diff --git a/src/models/User.ts b/src/models/User.ts index 3a116eb68e..7e5a7b43b2 100644 --- a/src/models/User.ts +++ b/src/models/User.ts @@ -1,14 +1,14 @@ 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 default interface User extends OnChain, Timestamped { +export type UserMetadata = { 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 +16,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 + _artist_pick?: ID } + +type User = UserMetadata & ComputedUserProperties + +export default User diff --git a/src/services/AudiusBackend.js b/src/services/AudiusBackend.js index e6dd2cc835..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() @@ -1041,25 +1055,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 new file mode 100644 index 0000000000..f270365392 --- /dev/null +++ b/src/services/audius-api-client/AudiusAPIClient.ts @@ -0,0 +1,128 @@ +import TimeRange from 'models/TimeRange' +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' +} + +const TRENDING_LIMIT = 100 + +type GetTrendingArgs = { + timeRange?: TimeRange + offset?: number + limit?: number + currentUserId?: string + genre?: string +} + +type InitializationState = + | { state: 'uninitialized ' } + | { + state: 'initialized' + endpoint: string + } + +class AudiusAPIClient { + initializationState: InitializationState = { state: 'uninitialized ' } + overrideEndpoint?: string + + constructor({ overrideEndpoint }: { overrideEndpoint?: string } = {}) { + this.overrideEndpoint = overrideEndpoint + } + + async getTrending({ + timeRange = TimeRange.WEEK, + limit = TRENDING_LIMIT, + offset = 0, + currentUserId, + genre + }: GetTrendingArgs) { + this._assertInitialized() + const params = { + time: timeRange, + limit, + offset, + user_id: currentUserId, + genre + } + + const endpoint = this._constructUrl(ENDPOINT_MAP.trending, params) + const trendingResponse: APIResponse = await this._getResponse( + endpoint + ) + const adapted = trendingResponse.data + .map(adapter.makeTrack) + .filter(removeNullable) + return adapted + } + + init() { + if (this.initializationState.state === 'initialized') return + + // If override passed, use that and return + if (this.overrideEndpoint) { + const endpoint = this._formatEndpoint(this.overrideEndpoint) + console.debug(`APIClient: Using override endpoint: ${endpoint}`) + this.initializationState = { state: 'initialized', endpoint: endpoint } + return + } + + // 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.debug('APIClient: Initialized') + } + + // Helpers + + _assertInitialized() { + if (this.initializationState.state !== 'initialized') + throw new Error('AudiusAPIClient must be initialized before use') + } + + async _getResponse(resource: string): Promise { + const response = await fetch(resource) + if (!response.ok) throw new Error(response.statusText) + 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('_constructURL called uninitialized') + const params = Object.entries(queryParams) + .filter(p => p[1] !== undefined && p[1] !== null) + .map(p => `${p[0]}=${encodeURIComponent(p[1]!)}`) + .join('&') + return `${this.initializationState.endpoint}${path}?${params}` + } +} + +const instance = new AudiusAPIClient() + +export default instance diff --git a/src/services/audius-api-client/ResponseAdapter.ts b/src/services/audius-api-client/ResponseAdapter.ts new file mode 100644 index 0000000000..6819eb1ca9 --- /dev/null +++ b/src/services/audius-api-client/ResponseAdapter.ts @@ -0,0 +1,120 @@ +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) { + 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) { + 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 => { + const decodedTrackId = decodeHashId(track.id) + const decodedOwnerId = decodeHashId(track.user_id) + const user = makeUser(track.user) + if (!decodedTrackId || !decodedOwnerId || !user) { + 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..e90c849e15 --- /dev/null +++ b/src/services/audius-api-client/types.ts @@ -0,0 +1,102 @@ +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 + 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/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 7fc6f9b21a..70e0f4d690 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,8 @@ export function* setupBackend() { console.info('Reconnected') } + yield call(() => apiClient.init()) + if (getIsReadOnlyClient()) { // Read only clients do a paired down version of libs init // (no account, no creator nodes, etc.). diff --git a/src/store/cache/tracks/utils/helpers.ts b/src/store/cache/tracks/utils/helpers.ts index 0c6aeeee4a..662c1b8f0f 100644 --- a/src/store/cache/tracks/utils/helpers.ts +++ b/src/store/cache/tracks/utils/helpers.ts @@ -1,4 +1,4 @@ -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' @@ -10,9 +10,7 @@ import { makeUid } from 'utils/uid' * 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..5bf9aa87dd 100644 --- a/src/store/cache/tracks/utils/processAndCacheTracks.ts +++ b/src/store/cache/tracks/utils/processAndCacheTracks.ts @@ -1,22 +1,24 @@ -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 Track, { UserTrackMetadata } 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 de0096132c..1c0cb52ad2 100644 --- a/src/store/cache/tracks/utils/reformat.ts +++ b/src/store/cache/tracks/utils/reformat.ts @@ -1,13 +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_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 +const addTrackImages = ( + track: T +): T & { duration: number; _cover_art_sizes: CoverArtSizes } => { return AudiusBackend.getTrackImages(track) } @@ -15,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] @@ -40,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 ?? [] @@ -51,11 +53,12 @@ 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) + + 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/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/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 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