diff --git a/.gitignore b/.gitignore index 203b36b6..c93ae266 100644 --- a/.gitignore +++ b/.gitignore @@ -105,3 +105,4 @@ dist docker-compose-personal.yml client/public/index.html client/public/variables.js +db_data \ No newline at end of file diff --git a/apps/client/src/App.tsx b/apps/client/src/App.tsx index b4016020..9a0cc345 100644 --- a/apps/client/src/App.tsx +++ b/apps/client/src/App.tsx @@ -28,6 +28,7 @@ import PlaylistDialog from "./components/PlaylistDialog"; import TrackStats from "./scenes/TrackStats"; import LongestSessions from "./scenes/LongestSessions"; import AlbumStats from "./scenes/AlbumStats"; +import Benchmarks from "./scenes/Benchmarks"; function App() { const dark = useSelector(selectDarkMode); @@ -169,6 +170,14 @@ function App() { } /> + + + + } + /> diff --git a/apps/client/src/components/ImplementedCharts/BestOfHour/BestOfHour.tsx b/apps/client/src/components/ImplementedCharts/BestOfHour/BestOfHour.tsx index 25880f73..1cdcc46c 100644 --- a/apps/client/src/components/ImplementedCharts/BestOfHour/BestOfHour.tsx +++ b/apps/client/src/components/ImplementedCharts/BestOfHour/BestOfHour.tsx @@ -33,23 +33,14 @@ function getElementName( >["data"][number], id: string, ) { - if ("tracks" in result) { - return result.tracks.find(t => t.track.id === id)?.track.name; - } - if ("albums" in result) { - return result.albums.find(t => t.album.id === id)?.album.name; - } - if ("artists" in result) { - return result.artists.find(t => t.artist.id === id)?.artist.name; - } - return ""; + return result.full_items[id]?.name; } function getElementData( result: UnboxPromise>["data"], index: number, ) { - const foundIndex = result.findIndex(r => r._id === index); + const foundIndex = result.findIndex(r => r.hour === index); if (foundIndex === -1) { return { x: index }; } @@ -61,34 +52,13 @@ function getElementData( const { total } = found; - if ("tracks" in found) { - return found.tracks.reduce( - (acc, curr) => { - acc[curr.track.id] = Math.floor((curr.count / total) * 1000) / 10; - return acc; - }, - { x: index }, - ); - } - if ("albums" in found) { - return found.albums.reduce( - (acc, curr) => { - acc[curr.album.id] = Math.floor((curr.count / total) * 1000) / 10; - return acc; - }, - { x: index }, - ); - } - if ("artists" in found) { - return found.artists.reduce( - (acc, curr) => { - acc[curr.artist.id] = Math.floor((curr.count / total) * 1000) / 10; - return acc; - }, - { x: index }, - ); - } - return { x: index }; + return found.items.reduce( + (acc, curr) => { + acc[curr.itemId] = Math.floor((curr.total / total) * 1000) / 10; + return acc; + }, + { x: index }, + ); } function formatX(value: any) { @@ -116,7 +86,7 @@ export default function BestOfHour({ className }: BestOfHourProps) { const tooltipValue = useCallback>( (payload, value, root) => { - const foundIndex = result?.findIndex(r => r._id === payload.x); + const foundIndex = result?.findIndex(r => r.hour === payload.x); if (!result || foundIndex === undefined || foundIndex === -1) { return null; } diff --git a/apps/client/src/components/InlineArtist/InlineArtist.tsx b/apps/client/src/components/InlineArtist/InlineArtist.tsx index 2d78fbc5..2872df11 100644 --- a/apps/client/src/components/InlineArtist/InlineArtist.tsx +++ b/apps/client/src/components/InlineArtist/InlineArtist.tsx @@ -13,7 +13,6 @@ export default function InlineArtist({ ...other }: InlineArtistProps) { return ( - // eslint-disable-next-line react/jsx-props-no-spreading {artist.name} diff --git a/apps/client/src/scenes/Benchmarks/Benchmarks.tsx b/apps/client/src/scenes/Benchmarks/Benchmarks.tsx new file mode 100644 index 00000000..9f4adc76 --- /dev/null +++ b/apps/client/src/scenes/Benchmarks/Benchmarks.tsx @@ -0,0 +1,245 @@ +import { useSelector } from "react-redux"; +import { + Button, + IconButton, + Table, + TableCell, + TableHead, + TableRow, +} from "@mui/material"; +import { PlayArrow } from "@mui/icons-material"; +import { useState } from "react"; +import Header from "../../components/Header"; +import { + selectRawIntervalDetail, + selectUser, +} from "../../services/redux/modules/user/selector"; +import { api } from "../../services/apis/api"; +import { Timesplit } from "../../services/types"; +import TitleCard from "../../components/TitleCard"; +import Text from "../../components/Text"; + +interface Request { + title: string; + prepare?: () => Promise; + request: (prepared: T) => Promise; +} + +const NOT_FINISHED_REQUEST = -1; +const FAILED_REQUEST = -2; + +export default function Benchmarks() { + const { interval } = useSelector(selectRawIntervalDetail); + + const [elapsedTime, setElapsedTime] = useState>({}); + + const user = useSelector(selectUser); + + if (!user) { + return null; + } + + const NB = 30; + const OFFSET = 0; + + const requests = [ + { + title: "Get tracks", + request: () => api.getTracks(interval.start, interval.end, 20, OFFSET), + }, + { + title: "Get most listened", + request: () => + api.mostListened(interval.start, interval.end, Timesplit.all), + }, + { + title: "Get most listened artists", + request: () => + api.mostListenedArtist(interval.start, interval.end, Timesplit.all), + }, + { + title: "Get songs per", + request: () => api.songsPer(interval.start, interval.end, Timesplit.all), + }, + { + title: "Get time per", + request: () => api.timePer(interval.start, interval.end, Timesplit.all), + }, + { + title: "Get feat ratio", + request: () => api.featRatio(interval.start, interval.end, Timesplit.all), + }, + { + title: "Get album date ratio", + request: () => + api.albumDateRatio(interval.start, interval.end, Timesplit.all), + }, + { + title: "Get popularity per", + request: () => + api.popularityPer(interval.start, interval.end, Timesplit.all), + }, + { + title: "Get different artists per", + request: () => + api.differentArtistsPer(interval.start, interval.end, Timesplit.all), + }, + { + title: "Get time per hour of day", + request: () => api.timePerHourOfDay(interval.start, interval.end), + }, + { + title: "Get best songs", + request: () => api.getBestSongs(interval.start, interval.end, NB, OFFSET), + }, + { + title: "Get best artists", + request: () => + api.getBestArtists(interval.start, interval.end, NB, OFFSET), + }, + { + title: "Get best albums", + request: () => + api.getBestAlbums(interval.start, interval.end, NB, OFFSET), + }, + { + title: "Get best songs of hour", + request: () => api.getBestSongsOfHour(interval.start, interval.end), + }, + { + title: "Get best albums of hour", + request: () => api.getBestAlbumsOfHour(interval.start, interval.end), + }, + { + title: "Get best artists of hour", + request: () => api.getBestArtistsOfHour(interval.start, interval.end), + }, + { + title: "Get longest sessions", + request: () => api.getLongestSessions(interval.start, interval.end), + }, + { + title: "Get artist page", + prepare: async () => { + const { data: bestArtists } = await api.getBestArtists( + interval.start, + interval.end, + 1, + 0, + ); + const [bestArtist] = bestArtists; + return bestArtist?.artist.id; + }, + request: async (bestArtistId: string) => api.getArtistStats(bestArtistId), + }, + { + title: "Get album page", + prepare: async () => { + const { data: bestAlbums } = await api.getBestAlbums( + interval.start, + interval.end, + 1, + 0, + ); + const [bestAlbum] = bestAlbums; + return bestAlbum?.album.id; + }, + request: async (bestAlbumId: string) => api.getAlbumStats(bestAlbumId), + }, + { + title: "Get track page", + prepare: async () => { + const { data: bestTracks } = await api.getBestSongs( + interval.start, + interval.end, + 1, + 0, + ); + const [bestTrack] = bestTracks; + return bestTrack?.track.id; + }, + request: async (bestTrackId: string) => api.getTrackStats(bestTrackId), + }, + ] as const satisfies Request[]; + + const run = async (req: Request) => { + setElapsedTime(prev => ({ ...prev, [req.title]: NOT_FINISHED_REQUEST })); + let prepared = undefined; + if (req.prepare) { + prepared = await req.prepare(); + } + if (!prepared && req.prepare) { + setElapsedTime(prev => ({ ...prev, [req.title]: FAILED_REQUEST })); + return; + } + let start = Date.now(); + const { data: result } = await req.request(prepared); + console.log("Result", result); + const end = Date.now(); + setElapsedTime(prev => ({ ...prev, [req.title]: end - start })); + }; + + const runAll = async () => { + // We need to run the requests in sequence to get a more + // accurate measure of the time it takes to run each request separately. + for (const req of requests) { + await run(req); + } + }; + + return ( +
+
+ + Run All + + }> + + + Request + Elapsed time + + Actions + + + {requests.map(req => { + let elapsed; + const requestTimeElapsed = elapsedTime[req.title]; + if (requestTimeElapsed === NOT_FINISHED_REQUEST) { + elapsed = Loading...; + } else if (requestTimeElapsed === FAILED_REQUEST) { + elapsed = Failed; + } else if (requestTimeElapsed) { + elapsed = `${requestTimeElapsed}ms`; + } + + return ( + + + {req.title} + + + {elapsed} + + + run(req)}> + + + + + ); + })} +
+
+
+ ); +} diff --git a/apps/client/src/scenes/Benchmarks/index.ts b/apps/client/src/scenes/Benchmarks/index.ts new file mode 100644 index 00000000..d2de13c9 --- /dev/null +++ b/apps/client/src/scenes/Benchmarks/index.ts @@ -0,0 +1 @@ +export { default } from "./Benchmarks"; diff --git a/apps/client/src/scenes/LongestSessions/LongestSession/LongestSession.tsx b/apps/client/src/scenes/LongestSessions/LongestSession/LongestSession.tsx index 815f1a67..c78386b9 100644 --- a/apps/client/src/scenes/LongestSessions/LongestSession/LongestSession.tsx +++ b/apps/client/src/scenes/LongestSessions/LongestSession/LongestSession.tsx @@ -10,22 +10,22 @@ import Text from "../../../components/Text"; import { intervalToHoursAndMinutes } from "../../../services/date"; import { useLoadArtists } from "../../../services/hooks/artist"; import { dateToListenedAt } from "../../../services/stats"; -import { TrackInfoWithTrack } from "../../../services/types"; +import { Track, TrackInfo } from "../../../services/types"; import s from "./index.module.css"; interface LongestSessionProps { - tracks: TrackInfoWithTrack[]; + tracks: TrackInfo[]; + fullTracks: Record; } -export default function LongestSession({ tracks }: LongestSessionProps) { +export default function LongestSession({ + tracks, + fullTracks, +}: LongestSessionProps) { const artistIds = useMemo( () => [ ...tracks.reduce((acc, track) => { - const artist = track.track.artists[0]; - if (!artist) { - return acc; - } - acc.add(artist); + acc.add(track.primaryArtistId); return acc; }, new Set()), ], @@ -42,7 +42,7 @@ export default function LongestSession({ tracks }: LongestSessionProps) { } return intervalToHoursAndMinutes( new Date(first.played_at), - new Date(new Date(last.played_at).getTime() + last.track.duration_ms), + new Date(new Date(last.played_at).getTime() + last.durationMs), ); }, [tracks]); @@ -66,7 +66,8 @@ export default function LongestSession({ tracks }: LongestSessionProps) { {tracks.map((track, index) => { - const artist = artists[track.track.artists[0] ?? ""]; + const artist = artists[track.primaryArtistId]; + const fullTrack = fullTracks[track.id]; return (
#{index + 1} @@ -74,11 +75,15 @@ export default function LongestSession({ tracks }: LongestSessionProps) { image={ } - first={} - second={artist ? : null} + first={fullTrack ? : null} + second={ + artist ? ( + + ) : null + } />
); diff --git a/apps/client/src/scenes/LongestSessions/LongestSession/index.module.css b/apps/client/src/scenes/LongestSessions/LongestSession/index.module.css index 32ab2935..4613d99f 100644 --- a/apps/client/src/scenes/LongestSessions/LongestSession/index.module.css +++ b/apps/client/src/scenes/LongestSessions/LongestSession/index.module.css @@ -16,3 +16,7 @@ margin-right: 8px; width: 32px; } + +.artist { + color: var(--text-grey); +} diff --git a/apps/client/src/scenes/LongestSessions/LongestSessions.tsx b/apps/client/src/scenes/LongestSessions/LongestSessions.tsx index 18d236d5..33199198 100644 --- a/apps/client/src/scenes/LongestSessions/LongestSessions.tsx +++ b/apps/client/src/scenes/LongestSessions/LongestSessions.tsx @@ -18,6 +18,7 @@ export default function LongestSessions() { ); const hasValidSessions = validResults && validResults.length > 0; + console.log(validResults); return (
@@ -39,6 +40,7 @@ export default function LongestSessions() { e.info._id).join(",")} tracks={r.distanceToLast.distance.map(e => e.info)} + fullTracks={r.full_tracks} /> ))} diff --git a/apps/client/src/services/apis/api.ts b/apps/client/src/services/apis/api.ts index 4421a635..bff093f0 100644 --- a/apps/client/src/services/apis/api.ts +++ b/apps/client/src/services/apis/api.ts @@ -329,9 +329,9 @@ export const api = { start, end, }), - getAlbums: (ids: string[]) => get(`/album/${ids.join(',')}`), + getAlbums: (ids: string[]) => get(`/album/${ids.join(",")}`), getAlbumStats: (id: string) => - get(`/album/${id}/stats`), + get(`/album/${id}/stats`), getAlbumRank: (id: string) => get<{ index: number; @@ -342,7 +342,7 @@ export const api = { count: number; }[]; }>(`/album/${id}/rank`), - getArtists: (ids: string[]) => get(`/artist/${ids.join(',')}`), + getArtists: (ids: string[]) => get(`/artist/${ids.join(",")}`), getArtistStats: (id: string) => get( `/artist/${id}/stats`, @@ -489,25 +489,28 @@ export const api = { getBestSongsOfHour: (start: Date, end: Date) => get< { - _id: number; + hour: number; total: number; - tracks: { count: number; track: Track; artist: Artist }[]; + items: { itemId: string; total: number }[]; + full_items: Record; }[] >("/spotify/top/hour-repartition/songs", { start, end }), getBestAlbumsOfHour: (start: Date, end: Date) => get< { - _id: number; + hour: number; total: number; - albums: { count: number; album: Album; artist: Artist }[]; + items: { itemId: string; total: number }[]; + full_items: Record; }[] >("/spotify/top/hour-repartition/albums", { start, end }), getBestArtistsOfHour: (start: Date, end: Date) => get< { - _id: number; + hour: number; total: number; - artists: { count: number; artist: Artist }[]; + items: { itemId: string; total: number }[]; + full_items: Record; }[] >("/spotify/top/hour-repartition/artists", { start, end }), getPlaylists: () => get("/spotify/playlists"), @@ -536,10 +539,11 @@ export const api = { get< { sessionLength: number; + full_tracks: Record; distanceToLast: { distance: { subtract: number; - info: TrackInfo & { track: Track }; + info: TrackInfo; }[]; }; }[] diff --git a/apps/client/src/services/types.ts b/apps/client/src/services/types.ts index 179d25cd..de524851 100644 --- a/apps/client/src/services/types.ts +++ b/apps/client/src/services/types.ts @@ -105,6 +105,10 @@ export interface TrackInfo { owner: string; id: string; played_at: string; + durationMs: number; + albumId: string; + primaryArtistId: string; + artists: string[]; } export type TrackInfoWithFullTrack = TrackInfo & { @@ -114,10 +118,6 @@ export type TrackInfoWithFullTrack = TrackInfo & { }; }; -export type TrackInfoWithTrack = TrackInfo & { - track: Track; -}; - export interface DateId { year: number; month?: number; diff --git a/apps/server/src/database/queries/album.ts b/apps/server/src/database/queries/album.ts index 0133a61d..2b6debf3 100644 --- a/apps/server/src/database/queries/album.ts +++ b/apps/server/src/database/queries/album.ts @@ -1,127 +1,84 @@ -import { AlbumModel, InfosModel } from '../Models'; -import { User } from '../schemas/user'; +import { AlbumModel, InfosModel } from "../Models"; +import { User } from "../schemas/user"; export const getAlbums = (albumsId: string[]) => AlbumModel.find({ id: { $in: albumsId } }); export const searchAlbum = (str: string) => - AlbumModel.find({ name: { $regex: new RegExp(str, 'i') } }); + AlbumModel.find({ name: { $regex: new RegExp(str, "i") } }); export const getAlbumInfos = (albumId: string) => [ { $lookup: { - from: 'tracks', - let: { targetId: '$id' }, + from: "tracks", + let: { targetId: "$id" }, pipeline: [ { $match: { $expr: { $and: [ - { $eq: ['$album', albumId] }, - { $eq: ['$id', '$$targetId'] }, + { $eq: ["$album", albumId] }, + { $eq: ["$id", "$$targetId"] }, ], }, }, }, - { $project: { trackId: '$id', albumId: '$album' } }, + { $project: { trackId: "$id", albumId: "$album" } }, ], - as: 'albumInfos', - } + as: "albumInfos", + }, }, - { $match: { 'albumInfos.albumId': { $exists: true } } }, - { $unwind: '$albumInfos' } -] + { $match: { "albumInfos.albumId": { $exists: true } } }, + { $unwind: "$albumInfos" }, +]; -export const getFirstAndLastListenedAlbum = async (user: User, albumId: string) => { +export const getFirstAndLastListenedAlbum = async ( + user: User, + albumId: string, +) => { const res = await InfosModel.aggregate([ - { $match: { owner: user._id } }, + { $match: { owner: user._id, albumId: albumId } }, ...getAlbumInfos(albumId), { $sort: { played_at: 1 } }, { $group: { _id: null, - first: { $first: '$$ROOT' }, - last: { $last: '$$ROOT' }, + first: { $first: "$$ROOT" }, + last: { $last: "$$ROOT" }, }, }, - ...['first', 'last'] + ...["first", "last"] .map(e => [ { $lookup: { - from: 'tracks', + from: "tracks", localField: `${e}.albumInfos.trackId`, - foreignField: 'id', + foreignField: "id", as: `${e}.track`, }, }, - { $unwind: `$${e}.track` } + { $unwind: `$${e}.track` }, ]) .flat(1), ]); return res[0]; -} +}; export const getAlbumSongs = async (user: User, albumId: string) => { const res = await InfosModel.aggregate([ - { $match: { owner: user._id } }, + { $match: { owner: user._id, albumId: albumId } }, ...getAlbumInfos(albumId), - { $group: { _id: '$id', count: { $sum: 1 } } }, + { $group: { _id: "$id", count: { $sum: 1 } } }, { $sort: { count: -1 } }, { $lookup: { - from: 'tracks', - localField: '_id', - foreignField: 'id', - as: 'track', + from: "tracks", + localField: "_id", + foreignField: "id", + as: "track", }, }, - { $unwind: '$track' }, + { $unwind: "$track" }, ]); return res; -} - -export const getRankOfAlbum = async (user: User, albumId: string) => { - const res = await InfosModel.aggregate([ - { $match: { owner: user._id } }, - { $lookup: { - from: 'tracks', - localField: 'id', - foreignField: 'id', - as: 'track', - }}, - { $unwind: '$track' }, - { $group: { - _id: '$track.album', - count: { $sum: 1 } - }}, - { $sort: { count: -1, _id: 1 } }, - { $group: { _id: 1, array: { $push: { id: '$_id', count: '$count' } } } }, - { - $project: { - index: { - $indexOfArray: ['$array.id', albumId], - }, - array: 1, - }, - }, - { - $project: { - index: 1, - isMax: { - $cond: { if: { $eq: ['$index', 0] }, then: true, else: false }, - }, - isMin: { - $cond: { - if: { $eq: ['$index', { $subtract: [{ $size: '$array' }, 1] }] }, - then: true, - else: false, - }, - }, - results: { - $slice: ['$array', { $max: [{ $subtract: ['$index', 1] }, 0] }, 3], - }, - }, - } - ]); - return res[0]; -} \ No newline at end of file +}; diff --git a/apps/server/src/database/queries/artist.ts b/apps/server/src/database/queries/artist.ts index cc387e77..43a9baa0 100644 --- a/apps/server/src/database/queries/artist.ts +++ b/apps/server/src/database/queries/artist.ts @@ -37,7 +37,7 @@ export const getArtistInfos = (artistId: string) => [ export const getFirstAndLastListened = async (user: User, artistId: string) => { // Non sense to compute blacklist here const res = await InfosModel.aggregate([ - { $match: { owner: user._id } }, + { $match: { owner: user._id, primaryArtistId: artistId } }, ...getArtistInfos(artistId), { $sort: { played_at: 1 } }, { @@ -79,7 +79,7 @@ export const getMostListenedSongOfArtist = async ( ) => { const res = await InfosModel.aggregate([ // Non sense to compute blacklist here - { $match: { owner: user._id } }, + { $match: { owner: user._id, primaryArtistId: artistId } }, ...getArtistInfos(artistId), { $group: { _id: "$id", count: { $sum: 1 } } }, { $sort: { count: -1 } }, @@ -109,7 +109,7 @@ export const getMostListenedSongOfArtist = async ( export const bestPeriodOfArtist = async (user: User, artistId: string) => { // Non sense to compute blacklist here const res = await InfosModel.aggregate([ - { $match: { owner: user._id } }, + { $match: { owner: user._id, primaryArtistId: artistId } }, ...getArtistInfos(artistId), { $project: { @@ -141,7 +141,7 @@ export const getTotalListeningOfArtist = async ( ) => { // Non sense to compute blacklist here const res = await InfosModel.aggregate([ - { $match: { owner: user._id } }, + { $match: { owner: user._id, primaryArtistId: artistId } }, ...getArtistInfos(artistId), { $group: { @@ -167,37 +167,7 @@ export const getMostListenedAlbumOfArtist = async ( artistId: string, ) => { const res = await InfosModel.aggregate([ - { $match: { owner: user._id } }, - { $lookup: { - from: 'tracks', - localField: 'id', - foreignField: 'id', - as: 'track' - }}, - { $match: { 'track.artists.0': artistId } }, - { $group: { - _id: '$track.album', - count: { $sum: 1 } - }}, - { $sort: { count: -1 } }, - { - $lookup: { - from: 'albums', - localField: '_id', - foreignField: 'id', - as: 'album', - }, - }, - { $unwind: '$album' }, - { $limit: 10} - ]); - return res; -}; - -export const getRankOfArtist = async (user: User, artistId: string) => { - // Non sense to compute blacklist here - const res = await InfosModel.aggregate([ - { $match: { owner: user._id } }, + { $match: { owner: user._id, primaryArtistId: artistId } }, { $lookup: { from: "tracks", @@ -206,47 +176,31 @@ export const getRankOfArtist = async (user: User, artistId: string) => { as: "track", }, }, - { $unwind: "$track" }, { $group: { - _id: { $arrayElemAt: ["$track.artists", 0] }, + _id: "$track.album", count: { $sum: 1 }, }, }, - { $sort: { count: -1, _id: 1 } }, - { $group: { _id: 1, array: { $push: { id: "$_id", count: "$count" } } } }, - { - $project: { - index: { $indexOfArray: ["$array.id", artistId] }, - array: 1, - }, - }, + { $sort: { count: -1 } }, { - $project: { - index: 1, - isMax: { - $cond: { if: { $eq: ["$index", 0] }, then: true, else: false }, - }, - isMin: { - $cond: { - if: { $eq: ["$index", { $subtract: [{ $size: "$array" }, 1] }] }, - then: true, - else: false, - }, - }, - results: { - $slice: ["$array", { $max: [{ $subtract: ["$index", 1] }, 0] }, 3], - }, + $lookup: { + from: "albums", + localField: "_id", + foreignField: "id", + as: "album", }, }, + { $unwind: "$album" }, + { $limit: 10 }, ]); - return res[0]; + return res; }; export const getDayRepartitionOfArtist = (user: User, artistId: string) => // Non sense to compute blacklist here InfosModel.aggregate([ - { $match: { owner: user._id } }, + { $match: { owner: user._id, primaryArtistId: artistId } }, { $addFields: getGroupByDateProjection(user.settings.timezone) }, ...getArtistInfos(artistId), { diff --git a/apps/server/src/database/queries/stats.ts b/apps/server/src/database/queries/stats.ts index c43dfd07..eae51381 100644 --- a/apps/server/src/database/queries/stats.ts +++ b/apps/server/src/database/queries/stats.ts @@ -3,7 +3,6 @@ import { InfosModel } from "../Models"; import { User } from "../schemas/user"; import { basicMatch, - getBestInfos, getGroupByDateProjection, getGroupingByTimeSplit, getTrackSumType, @@ -13,6 +12,26 @@ import { sortByTimeSplit, } from "./statsTools"; +export type ItemType = { + field: string; + collection: string; +}; + +export const ItemType = { + track: { + field: "$id", + collection: "tracks", + }, + album: { + field: "$albumId", + collection: "albums", + }, + artist: { + field: "$primaryArtistId", + collection: "artists", + }, +} as const satisfies Record; + export const getMostListenedSongs = async ( user: User, start: Date, @@ -20,7 +39,7 @@ export const getMostListenedSongs = async ( timeSplit: Timesplit = Timesplit.hour, ) => { const res = await InfosModel.aggregate([ - { $match: basicMatch(user._id, start, end) }, + ...basicMatch(user._id, start, end), { $project: { ...getGroupByDateProjection(user.settings.timezone), id: 1 }, }, @@ -84,33 +103,28 @@ export const getMostListenedArtist = async ( timeSplit = Timesplit.hour, ) => { const res = await InfosModel.aggregate([ - { $match: basicMatch(user._id, start, end) }, - { - $project: { ...getGroupByDateProjection(user.settings.timezone), id: 1 }, - }, + ...basicMatch(user._id, start, end), { - $lookup: { - from: "tracks", - localField: "id", - foreignField: "id", - as: "track", + $project: { + ...getGroupByDateProjection(user.settings.timezone), + primaryArtistId: 1, + id: 1, }, }, - { $unwind: "$track" }, { $group: { _id: { ...getGroupingByTimeSplit(timeSplit), - art: { $arrayElemAt: ["$track.artists", 0] }, + artistId: "$primaryArtistId", }, - count: { $sum: getTrackSumType(user) }, + count: { $sum: getTrackSumType(user, "$durationMs") }, }, }, - { $sort: { count: -1, "_id.art": 1 } }, + { $sort: { count: -1, "_id.artistId": 1 } }, { $group: { _id: getGroupingByTimeSplit(timeSplit, "_id"), - artists: { $push: "$_id.art" }, + artists: { $push: "$_id.artistId" }, counts: { $push: "$count" }, }, }, @@ -150,9 +164,12 @@ export const getSongsPer = async ( timeSplit = Timesplit.day, ) => { const res = await InfosModel.aggregate([ - { $match: basicMatch(user._id, start, end) }, + ...basicMatch(user._id, start, end), { - $project: { ...getGroupByDateProjection(user.settings.timezone), id: 1 }, + $project: { + ...getGroupByDateProjection(user.settings.timezone), + id: 1, + }, }, { $group: { @@ -181,26 +198,18 @@ export const getTimePer = async ( timeSplit = Timesplit.day, ) => { const res = await InfosModel.aggregate([ - { $match: basicMatch(user._id, start, end) }, + ...basicMatch(user._id, start, end), { $project: { ...getGroupByDateProjection(user.settings.timezone), + durationMs: 1, id: 1, }, }, - { - $lookup: { - from: "tracks", - localField: "id", - foreignField: "id", - as: "track", - }, - }, - { $unwind: "$track" }, { $group: { _id: getGroupingByTimeSplit(timeSplit), - count: { $sum: "$track.duration_ms" }, + count: { $sum: "$durationMs" }, }, }, ...sortByTimeSplit(timeSplit, "_id"), @@ -215,7 +224,7 @@ export const albumDateRatio = async ( timeSplit = Timesplit.day, ) => { const res = await InfosModel.aggregate([ - { $match: basicMatch(user._id, start, end) }, + ...basicMatch(user._id, start, end), { $project: { ...getGroupByDateProjection(user.settings.timezone), @@ -273,7 +282,7 @@ export const featRatio = async ( timeSplit: Timesplit, ) => { const res = await InfosModel.aggregate([ - { $match: basicMatch(user._id, start, end) }, + ...basicMatch(user._id, start, end), { $project: { ...getGroupByDateProjection(user.settings.timezone), @@ -366,7 +375,7 @@ export const popularityPer = async ( timeSplit = Timesplit.day, ) => { const res = await InfosModel.aggregate([ - { $match: basicMatch(user._id, start, end) }, + ...basicMatch(user._id, start, end), { $project: { ...getGroupByDateProjection(user.settings.timezone), @@ -408,36 +417,29 @@ export const differentArtistsPer = async ( timeSplit = Timesplit.day, ) => { const res = await InfosModel.aggregate([ - { $match: basicMatch(user._id, start, end) }, + ...basicMatch(user._id, start, end), { $project: { ...getGroupByDateProjection(user.settings.timezone), + primaryArtistId: 1, + durationMs: 1, id: 1, }, }, - { - $lookup: { - from: "tracks", - localField: "id", - foreignField: "id", - as: "track", - }, - }, - { $unwind: "$track" }, { $group: { _id: { ...getGroupingByTimeSplit(timeSplit), - artId: { $arrayElemAt: ["$track.artists", 0] }, + artistId: `$primaryArtistId`, }, count: { $sum: 1 }, }, }, - { $sort: { count: -1, "_id.artId": 1 } }, + { $sort: { count: -1, "_id.artistId": 1 } }, { $lookup: { from: "artists", - localField: "_id.artId", + localField: "_id.artistId", foreignField: "id", as: "artist", }, @@ -459,26 +461,18 @@ export const differentArtistsPer = async ( export const getDayRepartition = async (user: User, start: Date, end: Date) => { const res = await InfosModel.aggregate([ - { $match: basicMatch(user._id, start, end) }, + ...basicMatch(user._id, start, end), { $project: { ...getGroupByDateProjection(user.settings.timezone), + durationMs: 1, id: 1, }, }, - { - $lookup: { - from: "tracks", - localField: "id", - foreignField: "id", - as: "track", - }, - }, - { $unwind: "$track" }, { $group: { _id: "$hour", - count: { $sum: getTrackSumType(user) }, + count: { $sum: getTrackSumType(user, "$durationMs") }, }, }, { $sort: { _id: 1 } }, @@ -493,7 +487,7 @@ export const getBestArtistsPer = async ( timeSplit = Timesplit.day, ) => { const res = await InfosModel.aggregate([ - { $match: basicMatch(user._id, start, end) }, + ...basicMatch(user._id, start, end), { $project: { ...getGroupByDateProjection(user.settings.timezone), id: 1 }, }, @@ -552,166 +546,124 @@ export const getBestArtistsPer = async ( return res; }; -export const getBestSongsNbOffseted = ( - user: User, - start: Date, - end: Date, - nb: number, - offset: number, -) => getBestInfos("id", user, start, end, nb, offset); - -export const getBestArtistsNbOffseted = ( - user: User, - start: Date, - end: Date, - nb: number, - offset: number, -) => getBestInfos("primaryArtistId", user, start, end, nb, offset); - -export const getBestAlbumsNbOffseted = ( +export const getBest = ( + itemType: ItemType, user: User, start: Date, end: Date, nb: number, offset: number, -) => getBestInfos("albumId", user, start, end, nb, offset); - -export const getBestSongsOfHour = (user: User, start: Date, end: Date) => { - return InfosModel.aggregate([ - { $match: basicMatch(user._id, start, end) }, - { - $addFields: { - hour: getGroupByDateProjection(user.settings.timezone).hour, - }, - }, - { $group: { _id: "$hour", songs: { $push: "$id" }, total: { $sum: 1 } } }, - { $unwind: "$songs" }, +) => + InfosModel.aggregate([ + ...basicMatch(user._id, start, end), { $group: { - _id: { _id: "$_id", song: "$songs" }, + _id: itemType.field, + duration_ms: { $sum: "$durationMs" }, count: { $sum: 1 }, - total: { $first: "$total" }, + trackId: { $first: "$id" }, + albumId: { $first: "$albumId" }, + primaryArtistId: { $first: "$primaryArtistId" }, + trackIds: { $addToSet: "$id" }, }, }, - { $sort: { count: -1 } }, + { $addFields: { differents: { $size: "$trackIds" } } }, { - $group: { - _id: "$_id._id", - songs: { $push: { song: "$_id.song", count: "$count" } }, - total: { $first: "$total" }, + $facet: { + infos: [ + { $sort: { count: -1, _id: 1 } }, + { $skip: offset }, + { $limit: nb }, + ], + computations: [ + { + $group: { + _id: null, + total_duration_ms: { $sum: "$duration_ms" }, + total_count: { $sum: "$count" }, + }, + }, + ], }, }, - { $addFields: { songs: { $slice: ["$songs", 20] } } }, - { $unwind: "$songs" }, - { $lookup: lightTrackLookupPipeline("songs.song") }, - { $unwind: "$track" }, - { $lookup: lightArtistLookupPipeline("track.artists", true) }, - { $unwind: "$artist" }, + { $unwind: "$infos" }, + { $unwind: "$computations" }, { - $group: { - _id: "$_id", - tracks: { - $push: { track: "$track", artist: "$artist", count: "$songs.count" }, + $project: { + _id: "$infos._id", + result: { + $mergeObjects: ["$infos", "$computations"], }, - total: { $first: "$total" }, }, }, - { $sort: { _id: 1 } }, - ]); -}; - -export const getBestAlbumsOfHour = (user: User, start: Date, end: Date) => { - return InfosModel.aggregate([ - { $match: basicMatch(user._id, start, end) }, { - $addFields: { - hour: getGroupByDateProjection(user.settings.timezone).hour, + $replaceRoot: { + newRoot: { + $mergeObjects: ["$result", { _id: "$_id" }], + }, }, }, - { $group: { _id: "$hour", songs: { $push: "$id" }, total: { $sum: 1 } } }, - { $unwind: "$songs" }, - { $lookup: lightTrackLookupPipeline("songs") }, + { $lookup: lightTrackLookupPipeline("trackId") }, { $unwind: "$track" }, - { - $group: { - _id: { _id: "$_id", album: "$track.album" }, - count: { $sum: 1 }, - - total: { $first: "$total" }, - }, - }, - { $sort: { count: -1 } }, - { - $group: { - _id: "$_id._id", - albums: { $push: { album: "$_id.album", count: "$count" } }, - - total: { $first: "$total" }, - }, - }, - { $addFields: { albums: { $slice: ["$albums", 20] } } }, - { $unwind: "$albums" }, - { $lookup: lightAlbumLookupPipeline("albums.album") }, + { $lookup: lightAlbumLookupPipeline("albumId") }, { $unwind: "$album" }, - { $lookup: lightArtistLookupPipeline("album.artists", true) }, + { $lookup: lightArtistLookupPipeline("primaryArtistId", false) }, { $unwind: "$artist" }, - { - $group: { - _id: "$_id", - albums: { - $push: { album: "$album", artist: "$artist", count: "$albums.count" }, - }, - total: { $first: "$total" }, - }, - }, - { $sort: { _id: 1 } }, ]); -}; -export const getBestArtistsOfHour = (user: User, start: Date, end: Date) => { - return InfosModel.aggregate([ - { $match: basicMatch(user._id, start, end) }, +export const getBestOfHour = async ( + itemType: ItemType, + user: User, + start: Date, + end: Date, +) => { + const bestOfHour = await InfosModel.aggregate([ + ...basicMatch(user._id, start, end), { - $addFields: { - hour: getGroupByDateProjection(user.settings.timezone).hour, + $group: { + _id: { + hour: getGroupByDateProjection(user.settings.timezone).hour, + itemId: itemType.field, + }, + total: { $sum: 1 }, }, }, - { $group: { _id: "$hour", songs: { $push: "$id" }, total: { $sum: 1 } } }, - { $unwind: "$songs" }, - { $lookup: lightTrackLookupPipeline("songs") }, - { $unwind: "$track" }, + { $sort: { total: -1 } }, { $group: { - _id: { _id: "$_id", artist: { $first: "$track.artists" } }, - count: { $sum: 1 }, - - total: { $first: "$total" }, + _id: "$_id.hour", + items: { + $push: { itemId: "$_id.itemId", total: "$total" }, + }, + total: { $sum: "$total" }, }, }, - { $sort: { count: -1 } }, { - $group: { - _id: "$_id._id", - artists: { $push: { artist: "$_id.artist", count: "$count" } }, - total: { $first: "$total" }, + $project: { + hour: "$_id", + total: "$total", + items: { $slice: ["$items", 20] }, }, }, - { $addFields: { artists: { $slice: ["$artists", 20] } } }, - { $unwind: "$artists" }, - { $lookup: lightArtistLookupPipeline("artists.artist", false) }, - { $unwind: "$artist" }, { - $group: { - _id: "$_id", - artists: { $push: { artist: "$artist", count: "$artists.count" } }, - total: { $first: "$total" }, + $lookup: { + from: itemType.collection, + localField: "items.itemId", + foreignField: "id", + as: "full_items", }, }, { $sort: { _id: 1 } }, ]); + bestOfHour.forEach((hour: any) => { + hour.full_items = Object.fromEntries( + hour.full_items.map((e: any) => [e.id, e]), + ); + }); + return bestOfHour; }; -export const getLongestListeningSession = ( +export const getLongestListeningSession = async ( userId: string, start: Date, end: Date, @@ -724,7 +676,7 @@ export const getLongestListeningSession = ( $subtract: [ "$$this.played_at", { - $add: ["$$value.last.played_at", "$$value.last.track.duration_ms"], + $add: ["$$value.last.played_at", "$$value.last.durationMs"], }, ], }, @@ -734,11 +686,9 @@ export const getLongestListeningSession = ( const item = { subtract, info: "$$this" }; - return InfosModel.aggregate([ - { $match: basicMatch(userId, start, end) }, + const longestSessions = await InfosModel.aggregate([ + ...basicMatch(userId, start, end), { $sort: { played_at: 1 } }, - { $lookup: lightTrackLookupPipeline() }, - { $unwind: "$track" }, { $group: { _id: "$owner", @@ -812,5 +762,59 @@ export const getLongestListeningSession = ( }, { $sort: { sessionLength: -1 } }, { $limit: 5 }, + { + $lookup: { + from: "tracks", + localField: "distanceToLast.distance.info.id", + foreignField: "id", + as: "full_tracks", + }, + }, + ]); + + longestSessions.forEach(longestSession => { + longestSession.full_tracks = Object.fromEntries( + longestSession.full_tracks.map((track: any) => [track.id, track]), + ); + }); + + return longestSessions; +}; + +export const getRankOf = async ( + itemType: ItemType, + user: User, + itemId: string, +) => { + const res = await InfosModel.aggregate([ + { $match: { owner: user._id } }, + { $group: { _id: itemType.field, count: { $sum: 1 } } }, + { $sort: { count: -1, _id: 1 } }, + { $group: { _id: 1, array: { $push: { id: "$_id", count: "$count" } } } }, + { + $project: { + index: { $indexOfArray: ["$array.id", itemId] }, + array: 1, + }, + }, + { + $project: { + index: 1, + isMax: { + $cond: { if: { $eq: ["$index", 0] }, then: true, else: false }, + }, + isMin: { + $cond: { + if: { $eq: ["$index", { $subtract: [{ $size: "$array" }, 1] }] }, + then: true, + else: false, + }, + }, + results: { + $slice: ["$array", { $max: [{ $subtract: ["$index", 1] }, 0] }, 3], + }, + }, + }, ]); + return res[0]; }; diff --git a/apps/server/src/database/queries/statsTools.ts b/apps/server/src/database/queries/statsTools.ts index 94a11648..df320985 100644 --- a/apps/server/src/database/queries/statsTools.ts +++ b/apps/server/src/database/queries/statsTools.ts @@ -2,17 +2,21 @@ import { PipelineStage, Types } from "mongoose"; import { getWithDefault } from "../../tools/env"; import { Timesplit } from "../../tools/types"; import { User } from "../schemas/user"; -import { InfosModel } from "../Models"; export const basicMatch = ( userId: string | Types.ObjectId, start: Date, end: Date, -) => ({ - owner: userId instanceof Types.ObjectId ? userId : new Types.ObjectId(userId), - blacklistedBy: { $exists: 0 }, - played_at: { $gt: start, $lt: end }, -}); +) => [ + { + $match: { + owner: + userId instanceof Types.ObjectId ? userId : new Types.ObjectId(userId), + blacklistedBy: { $exists: 0 }, + played_at: { $gt: start, $lt: end }, + }, + }, +]; export const basicMatchUsers = ( userIds: string[] | Types.ObjectId[], @@ -131,12 +135,12 @@ export const getGroupByDateProjection = (userTimezone: string | undefined) => ({ }, }); -export const getTrackSumType = (user: User) => { +export const getTrackSumType = (user: User, idField = "$track.duration_ms") => { if (user.settings.metricUsed === "number") { return 1; } if (user.settings.metricUsed === "duration") { - return "$track.duration_ms"; + return idField; } return 1; }; @@ -183,68 +187,3 @@ export const lightArtistLookupPipeline = ( from: "artists", as: "artist", }); - -export const getBestInfos = ( - idField: string, - user: User, - start: Date, - end: Date, - nb: number, - offset: number, -) => - InfosModel.aggregate([ - { $match: basicMatch(user._id, start, end) }, - { - $group: { - _id: `$${idField}`, - duration_ms: { $sum: "$durationMs" }, - count: { $sum: 1 }, - trackId: { $first: "$id" }, - albumId: { $first: "$albumId" }, - primaryArtistId: { $first: "$primaryArtistId" }, - trackIds: { $addToSet: "$id" }, - }, - }, - { $addFields: { differents: { $size: "$trackIds" } } }, - { - $facet: { - infos: [ - { $sort: { count: -1, _id: 1 } }, - { $skip: offset }, - { $limit: nb }, - ], - computations: [ - { - $group: { - _id: null, - total_duration_ms: { $sum: "$duration_ms" }, - total_count: { $sum: "$count" }, - }, - }, - ], - }, - }, - { $unwind: "$infos" }, - { $unwind: "$computations" }, - { - $project: { - _id: "$infos._id", - result: { - $mergeObjects: ["$infos", "$computations"], - }, - }, - }, - { - $replaceRoot: { - newRoot: { - $mergeObjects: ["$result", { _id: "$_id" }], - }, - }, - }, - { $lookup: lightTrackLookupPipeline("trackId") }, - { $unwind: "$track" }, - { $lookup: lightAlbumLookupPipeline("albumId") }, - { $unwind: "$album" }, - { $lookup: lightArtistLookupPipeline("primaryArtistId", false) }, - { $unwind: "$artist" }, - ]); diff --git a/apps/server/src/database/queries/track.ts b/apps/server/src/database/queries/track.ts index 26b036f4..fb671d4a 100644 --- a/apps/server/src/database/queries/track.ts +++ b/apps/server/src/database/queries/track.ts @@ -11,49 +11,6 @@ export const searchTrack = (str: string) => "full_album", ); -export const getRankOfTrack = async (user: User, trackId: string) => { - const res = await InfosModel.aggregate([ - { $match: { owner: user._id } }, - { - $lookup: { - from: "tracks", - localField: "id", - foreignField: "id", - as: "track", - }, - }, - { $unwind: "$track" }, - { $group: { _id: "$id", count: { $sum: 1 } } }, - { $sort: { count: -1, _id: 1 } }, - { $group: { _id: 1, array: { $push: { id: "$_id", count: "$count" } } } }, - { - $project: { - index: { $indexOfArray: ["$array.id", trackId] }, - array: 1, - }, - }, - { - $project: { - index: 1, - isMax: { - $cond: { if: { $eq: ["$index", 0] }, then: true, else: false }, - }, - isMin: { - $cond: { - if: { $eq: ["$index", { $subtract: [{ $size: "$array" }, 1] }] }, - then: true, - else: false, - }, - }, - results: { - $slice: ["$array", { $max: [{ $subtract: ["$index", 1] }, 0] }, 3], - }, - }, - }, - ]); - return res[0]; -}; - export const getTrackListenedCount = (user: User, trackId: string) => InfosModel.where({ owner: user._id, id: trackId }).countDocuments(); diff --git a/apps/server/src/routes/album.ts b/apps/server/src/routes/album.ts index 4f48d057..1a5e2ded 100644 --- a/apps/server/src/routes/album.ts +++ b/apps/server/src/routes/album.ts @@ -1,10 +1,14 @@ -import { Router } from 'express'; -import { z } from 'zod'; -import { getAlbumSongs, getAlbums, getFirstAndLastListenedAlbum, getRankOfAlbum } from '../database/queries/album'; -import { logger } from '../tools/logger'; -import { isLoggedOrGuest, validating } from '../tools/middleware'; -import { LoggedRequest, TypedPayload } from '../tools/types'; -import { getArtists } from '../database'; +import { Router } from "express"; +import { z } from "zod"; +import { + getAlbumSongs, + getAlbums, + getFirstAndLastListenedAlbum, +} from "../database/queries/album"; +import { logger } from "../tools/logger"; +import { isLoggedOrGuest, validating } from "../tools/middleware"; +import { LoggedRequest, TypedPayload } from "../tools/types"; +import { getArtists, getRankOf, ItemType } from "../database"; export const router = Router(); @@ -19,7 +23,7 @@ router.get( async (req, res) => { try { const { ids } = req.params as TypedPayload; - const albums = await getAlbums(ids.split(',')); + const albums = await getAlbums(ids.split(",")); if (!albums || albums.length === 0) { return res.status(404).end(); } @@ -36,8 +40,8 @@ const getAlbumStats = z.object({ }); router.get( - '/:id/stats', - validating(getAlbumStats, 'params'), + "/:id/stats", + validating(getAlbumStats, "params"), isLoggedOrGuest, async (req, res) => { try { @@ -58,18 +62,18 @@ router.get( album, artists, firstLast, - tracks + tracks, }); } catch (e) { logger.error(e); return res.status(500).end(); } - } -) + }, +); router.get( - '/:id/rank', - validating(getAlbumStats, 'params'), + "/:id/rank", + validating(getAlbumStats, "params"), isLoggedOrGuest, async (req, res) => { try { @@ -79,11 +83,11 @@ router.get( if (!album) { return res.status(404).end(); } - const rank = await getRankOfAlbum(user, id); + const rank = await getRankOf(ItemType.album, user, id); return res.status(200).send(rank); } catch (e) { logger.error(e); return res.status(500).end(); } - } -) \ No newline at end of file + }, +); diff --git a/apps/server/src/routes/artist.ts b/apps/server/src/routes/artist.ts index 8a74bc05..287e9d89 100644 --- a/apps/server/src/routes/artist.ts +++ b/apps/server/src/routes/artist.ts @@ -6,7 +6,6 @@ import { getMostListenedSongOfArtist, bestPeriodOfArtist, getTotalListeningOfArtist, - getRankOfArtist, searchArtist, getDayRepartitionOfArtist, blacklistArtist, @@ -14,10 +13,12 @@ import { blacklistByArtist, unblacklistByArtist, getMostListenedAlbumOfArtist, -} from '../database'; -import { logger } from '../tools/logger'; -import { isLoggedOrGuest, logged, validating } from '../tools/middleware'; -import { LoggedRequest, TypedPayload } from '../tools/types'; + getRankOf, + ItemType, +} from "../database"; +import { logger } from "../tools/logger"; +import { isLoggedOrGuest, logged, validating } from "../tools/middleware"; +import { LoggedRequest, TypedPayload } from "../tools/types"; export const router = Router(); @@ -69,8 +70,14 @@ router.get( getTotalListeningOfArtist(user, id), getDayRepartitionOfArtist(user, id), ]; - const [firstLast, mostListened, albumMostListened, bestPeriod, total, dayRepartition] = - await Promise.all(promises); + const [ + firstLast, + mostListened, + albumMostListened, + bestPeriod, + total, + dayRepartition, + ] = await Promise.all(promises); if (!total) { return res.status(200).send({ code: "NEVER_LISTENED", @@ -105,7 +112,7 @@ router.get( if (!artist) { return res.status(404).end(); } - const rank = await getRankOfArtist(user, id); + const rank = await getRankOf(ItemType.artist, user, id); return res.status(200).send(rank); } catch (e) { logger.error(e); diff --git a/apps/server/src/routes/spotify.ts b/apps/server/src/routes/spotify.ts index 96ce8cd7..f523cde0 100644 --- a/apps/server/src/routes/spotify.ts +++ b/apps/server/src/routes/spotify.ts @@ -13,13 +13,10 @@ import { differentArtistsPer, getDayRepartition, getBestArtistsPer, - getBestSongsNbOffseted, - getBestArtistsNbOffseted, - getBestAlbumsNbOffseted, - getBestSongsOfHour, - getBestAlbumsOfHour, - getBestArtistsOfHour, getLongestListeningSession, + getBest, + ItemType, + getBestOfHour, } from "../database"; import { CollaborativeMode, @@ -359,7 +356,14 @@ router.get( >; try { - const result = await getBestSongsNbOffseted(user, start, end, nb, offset); + const result = await getBest( + ItemType.track, + user, + start, + end, + nb, + offset, + ); return res.status(200).send(result); } catch (e) { logger.error(e); @@ -379,7 +383,8 @@ router.get( >; try { - const result = await getBestArtistsNbOffseted( + const result = await getBest( + ItemType.artist, user, start, end, @@ -405,7 +410,8 @@ router.get( >; try { - const result = await getBestAlbumsNbOffseted( + const result = await getBest( + ItemType.album, user, start, end, @@ -512,8 +518,8 @@ router.get( const { start, end } = req.query as TypedPayload; try { - const result = await getBestSongsOfHour(user, start, end); - return res.status(200).send(result); + const tracks = await getBestOfHour(ItemType.track, user, start, end); + return res.status(200).send(tracks); } catch (e) { logger.error(e); return res.status(500).end(); @@ -530,8 +536,8 @@ router.get( const { start, end } = req.query as TypedPayload; try { - const result = await getBestAlbumsOfHour(user, start, end); - return res.status(200).send(result); + const albums = await getBestOfHour(ItemType.album, user, start, end); + return res.status(200).send(albums); } catch (e) { logger.error(e); return res.status(500).end(); @@ -548,8 +554,8 @@ router.get( const { start, end } = req.query as TypedPayload; try { - const result = await getBestArtistsOfHour(user, start, end); - return res.status(200).send(result); + const artists = await getBestOfHour(ItemType.artist, user, start, end); + return res.status(200).send(artists); } catch (e) { logger.error(e); return res.status(500).end(); @@ -653,7 +659,8 @@ router.post( let spotifyIds: string[]; if (body.type === "top") { const { interval: intervalData, nb } = body; - const items = await getBestSongsNbOffseted( + const items = await getBest( + ItemType.track, user, intervalData.start, intervalData.end, diff --git a/apps/server/src/routes/track.ts b/apps/server/src/routes/track.ts index 2da015ea..54c75ba7 100644 --- a/apps/server/src/routes/track.ts +++ b/apps/server/src/routes/track.ts @@ -2,12 +2,13 @@ import { Router } from "express"; import { z } from "zod"; import { getArtists, - getRankOfTrack, getTracks, getTrackListenedCount, getTrackFirstAndLastListened, bestPeriodOfTrack, getTrackRecentHistory, + getRankOf, + ItemType, } from "../database"; import { getAlbums } from "../database/queries/album"; import { logger } from "../tools/logger"; @@ -100,7 +101,7 @@ router.get( if (!track) { return res.status(404).end(); } - const rank = await getRankOfTrack(user, id); + const rank = await getRankOf(ItemType.track, user, id); return res.status(200).send(rank); } catch (e) { logger.error(e); diff --git a/docker-compose-dev.yml b/docker-compose-dev.yml index 5054282b..eac7618f 100644 --- a/docker-compose-dev.yml +++ b/docker-compose-dev.yml @@ -28,7 +28,7 @@ services: logging: driver: none volumes: - - db_data:/data/db + - ./db_data:/data/db ports: - "27017:27017" @@ -48,6 +48,3 @@ services: environment: NODE_ENV: development API_ENDPOINT: http://localhost:8080 - -volumes: - db_data: