mirror of
https://github.com/gabehf/Koito.git
synced 2026-03-07 13:38:15 -08:00
feat: all time rank display (#149)
* add all time rank to item pages * fix artist albums component * add no rows check * fix rewind page
This commit is contained in:
parent
d08e05220f
commit
5e294b839c
20 changed files with 301 additions and 202 deletions
|
|
@ -367,6 +367,7 @@ type Track = {
|
||||||
musicbrainz_id: string;
|
musicbrainz_id: string;
|
||||||
time_listened: number;
|
time_listened: number;
|
||||||
first_listen: number;
|
first_listen: number;
|
||||||
|
all_time_rank: number;
|
||||||
};
|
};
|
||||||
type Artist = {
|
type Artist = {
|
||||||
id: number;
|
id: number;
|
||||||
|
|
@ -378,6 +379,7 @@ type Artist = {
|
||||||
time_listened: number;
|
time_listened: number;
|
||||||
first_listen: number;
|
first_listen: number;
|
||||||
is_primary: boolean;
|
is_primary: boolean;
|
||||||
|
all_time_rank: number;
|
||||||
};
|
};
|
||||||
type Album = {
|
type Album = {
|
||||||
id: number;
|
id: number;
|
||||||
|
|
@ -389,6 +391,7 @@ type Album = {
|
||||||
musicbrainz_id: string;
|
musicbrainz_id: string;
|
||||||
time_listened: number;
|
time_listened: number;
|
||||||
first_listen: number;
|
first_listen: number;
|
||||||
|
all_time_rank: number;
|
||||||
};
|
};
|
||||||
type Alias = {
|
type Alias = {
|
||||||
id: number;
|
id: number;
|
||||||
|
|
@ -459,9 +462,9 @@ type NowPlaying = {
|
||||||
};
|
};
|
||||||
type RewindStats = {
|
type RewindStats = {
|
||||||
title: string;
|
title: string;
|
||||||
top_artists: Artist[];
|
top_artists: Ranked<Artist>[];
|
||||||
top_albums: Album[];
|
top_albums: Ranked<Album>[];
|
||||||
top_tracks: Track[];
|
top_tracks: Ranked<Track>[];
|
||||||
minutes_listened: number;
|
minutes_listened: number;
|
||||||
avg_minutes_listened_per_day: number;
|
avg_minutes_listened_per_day: number;
|
||||||
plays: number;
|
plays: number;
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,11 @@ interface Props {
|
||||||
period: string;
|
period: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ArtistAlbums({ artistId, name, period }: Props) {
|
export default function ArtistAlbums({ artistId, name }: Props) {
|
||||||
const { isPending, isError, data, error } = useQuery({
|
const { isPending, isError, data, error } = useQuery({
|
||||||
queryKey: [
|
queryKey: [
|
||||||
"top-albums",
|
"top-albums",
|
||||||
{ limit: 99, period: "all_time", artist_id: artistId, page: 0 },
|
{ limit: 99, period: "all_time", artist_id: artistId },
|
||||||
],
|
],
|
||||||
queryFn: ({ queryKey }) => getTopAlbums(queryKey[1] as getItemsArgs),
|
queryFn: ({ queryKey }) => getTopAlbums(queryKey[1] as getItemsArgs),
|
||||||
});
|
});
|
||||||
|
|
@ -39,16 +39,20 @@ export default function ArtistAlbums({ artistId, name, period }: Props) {
|
||||||
<h3>Albums featuring {name}</h3>
|
<h3>Albums featuring {name}</h3>
|
||||||
<div className="flex flex-wrap gap-8">
|
<div className="flex flex-wrap gap-8">
|
||||||
{data.items.map((item) => (
|
{data.items.map((item) => (
|
||||||
<Link to={`/album/${item.id}`} className="flex gap-2 items-start">
|
<Link
|
||||||
|
to={`/album/${item.item.id}`}
|
||||||
|
className="flex gap-2 items-start"
|
||||||
|
>
|
||||||
<img
|
<img
|
||||||
src={imageUrl(item.image, "medium")}
|
src={imageUrl(item.item.image, "medium")}
|
||||||
alt={item.title}
|
alt={item.item.title}
|
||||||
style={{ width: 130 }}
|
style={{ width: 130 }}
|
||||||
/>
|
/>
|
||||||
<div className="w-[180px] flex flex-col items-start gap-1">
|
<div className="w-[180px] flex flex-col items-start gap-1">
|
||||||
<p>{item.title}</p>
|
<p>{item.item.title}</p>
|
||||||
<p className="text-sm color-fg-secondary">
|
<p className="text-sm color-fg-secondary">
|
||||||
{item.listen_count} play{item.listen_count > 1 ? "s" : ""}
|
{item.item.listen_count} play
|
||||||
|
{item.item.listen_count > 1 ? "s" : ""}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,9 @@ interface Props {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Rewind(props: Props) {
|
export default function Rewind(props: Props) {
|
||||||
const artistimg = props.stats.top_artists[0]?.image;
|
const artistimg = props.stats.top_artists[0]?.item.image;
|
||||||
const albumimg = props.stats.top_albums[0]?.image;
|
const albumimg = props.stats.top_albums[0]?.item.image;
|
||||||
const trackimg = props.stats.top_tracks[0]?.image;
|
const trackimg = props.stats.top_tracks[0]?.item.image;
|
||||||
if (
|
if (
|
||||||
!props.stats.top_artists[0] ||
|
!props.stats.top_artists[0] ||
|
||||||
!props.stats.top_albums[0] ||
|
!props.stats.top_albums[0] ||
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
|
import type { Ranked } from "api/api";
|
||||||
|
|
||||||
type TopItemProps<T> = {
|
type TopItemProps<T> = {
|
||||||
title: string;
|
title: string;
|
||||||
imageSrc: string;
|
imageSrc: string;
|
||||||
items: T[];
|
items: Ranked<T>[];
|
||||||
getLabel: (item: T) => string;
|
getLabel: (item: T) => string;
|
||||||
includeTime?: boolean;
|
includeTime?: boolean;
|
||||||
};
|
};
|
||||||
|
|
@ -28,23 +30,23 @@ export function RewindTopItem<
|
||||||
|
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div className="flex flex-col items-start mb-2">
|
<div className="flex flex-col items-start mb-2">
|
||||||
<h2>{getLabel(top)}</h2>
|
<h2>{getLabel(top.item)}</h2>
|
||||||
<span className="text-(--color-fg-tertiary) -mt-3 text-sm">
|
<span className="text-(--color-fg-tertiary) -mt-3 text-sm">
|
||||||
{`${top.listen_count} plays`}
|
{`${top.item.listen_count} plays`}
|
||||||
{includeTime
|
{includeTime
|
||||||
? ` (${Math.floor(top.time_listened / 60)} minutes)`
|
? ` (${Math.floor(top.item.time_listened / 60)} minutes)`
|
||||||
: ``}
|
: ``}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{rest.map((e) => (
|
{rest.map((e) => (
|
||||||
<div key={e.id} className="text-sm">
|
<div key={e.item.id} className="text-sm">
|
||||||
{getLabel(e)}
|
{getLabel(e.item)}
|
||||||
<span className="text-(--color-fg-tertiary)">
|
<span className="text-(--color-fg-tertiary)">
|
||||||
{` - ${e.listen_count} plays`}
|
{` - ${e.item.listen_count} plays`}
|
||||||
{includeTime
|
{includeTime
|
||||||
? ` (${Math.floor(e.time_listened / 60)} minutes)`
|
? ` (${Math.floor(e.item.time_listened / 60)} minutes)`
|
||||||
: ``}
|
: ``}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ export default function Album() {
|
||||||
title={album.title}
|
title={album.title}
|
||||||
img={album.image}
|
img={album.image}
|
||||||
id={album.id}
|
id={album.id}
|
||||||
|
rank={album.all_time_rank}
|
||||||
musicbrainzId={album.musicbrainz_id}
|
musicbrainzId={album.musicbrainz_id}
|
||||||
imgItemId={album.id}
|
imgItemId={album.id}
|
||||||
mergeFunc={mergeAlbums}
|
mergeFunc={mergeAlbums}
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ export default function Artist() {
|
||||||
title={artist.name}
|
title={artist.name}
|
||||||
img={artist.image}
|
img={artist.image}
|
||||||
id={artist.id}
|
id={artist.id}
|
||||||
|
rank={artist.all_time_rank}
|
||||||
musicbrainzId={artist.musicbrainz_id}
|
musicbrainzId={artist.musicbrainz_id}
|
||||||
imgItemId={artist.id}
|
imgItemId={artist.id}
|
||||||
mergeFunc={mergeArtists}
|
mergeFunc={mergeArtists}
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ interface Props {
|
||||||
title: string;
|
title: string;
|
||||||
img: string;
|
img: string;
|
||||||
id: number;
|
id: number;
|
||||||
|
rank: number;
|
||||||
musicbrainzId: string;
|
musicbrainzId: string;
|
||||||
imgItemId: number;
|
imgItemId: number;
|
||||||
mergeFunc: MergeFunc;
|
mergeFunc: MergeFunc;
|
||||||
|
|
@ -96,7 +97,15 @@ export default function MediaLayout(props: Props) {
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col items-start">
|
<div className="flex flex-col items-start">
|
||||||
<h3>{props.type}</h3>
|
<h3>{props.type}</h3>
|
||||||
<h1>{props.title}</h1>
|
<div className="flex">
|
||||||
|
<h1>
|
||||||
|
{props.title}
|
||||||
|
<span className="text-xl font-medium text-(--color-fg-secondary)">
|
||||||
|
{" "}
|
||||||
|
#{props.rank}
|
||||||
|
</span>
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
{props.subContent}
|
{props.subContent}
|
||||||
</div>
|
</div>
|
||||||
<div className="absolute left-1 sm:right-1 sm:left-auto -top-9 sm:top-1 flex gap-3 items-center">
|
<div className="absolute left-1 sm:right-1 sm:left-auto -top-9 sm:top-1 flex gap-3 items-center">
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ export default function Track() {
|
||||||
title={track.title}
|
title={track.title}
|
||||||
img={track.image}
|
img={track.image}
|
||||||
id={track.id}
|
id={track.id}
|
||||||
|
rank={track.all_time_rank}
|
||||||
musicbrainzId={track.musicbrainz_id}
|
musicbrainzId={track.musicbrainz_id}
|
||||||
imgItemId={track.album_id}
|
imgItemId={track.album_id}
|
||||||
mergeFunc={mergeTracks}
|
mergeFunc={mergeTracks}
|
||||||
|
|
|
||||||
|
|
@ -81,6 +81,26 @@ FROM (
|
||||||
ORDER BY x.listen_count DESC, x.id
|
ORDER BY x.listen_count DESC, x.id
|
||||||
LIMIT $3 OFFSET $4;
|
LIMIT $3 OFFSET $4;
|
||||||
|
|
||||||
|
-- name: GetArtistAllTimeRank :one
|
||||||
|
SELECT
|
||||||
|
artist_id,
|
||||||
|
rank
|
||||||
|
FROM (
|
||||||
|
SELECT
|
||||||
|
x.artist_id,
|
||||||
|
RANK() OVER (ORDER BY x.listen_count DESC) AS rank
|
||||||
|
FROM (
|
||||||
|
SELECT
|
||||||
|
at.artist_id,
|
||||||
|
COUNT(*) AS listen_count
|
||||||
|
FROM listens l
|
||||||
|
JOIN tracks t ON l.track_id = t.id
|
||||||
|
JOIN artist_tracks at ON t.id = at.track_id
|
||||||
|
GROUP BY at.artist_id
|
||||||
|
) x
|
||||||
|
)
|
||||||
|
WHERE artist_id = $1;
|
||||||
|
|
||||||
-- name: CountTopArtists :one
|
-- name: CountTopArtists :one
|
||||||
SELECT COUNT(DISTINCT at.artist_id) AS total_count
|
SELECT COUNT(DISTINCT at.artist_id) AS total_count
|
||||||
FROM listens l
|
FROM listens l
|
||||||
|
|
|
||||||
|
|
@ -83,6 +83,25 @@ FROM (
|
||||||
ORDER BY listen_count DESC, x.id
|
ORDER BY listen_count DESC, x.id
|
||||||
LIMIT $3 OFFSET $4;
|
LIMIT $3 OFFSET $4;
|
||||||
|
|
||||||
|
-- name: GetReleaseAllTimeRank :one
|
||||||
|
SELECT
|
||||||
|
release_id,
|
||||||
|
rank
|
||||||
|
FROM (
|
||||||
|
SELECT
|
||||||
|
x.release_id,
|
||||||
|
RANK() OVER (ORDER BY x.listen_count DESC) AS rank
|
||||||
|
FROM (
|
||||||
|
SELECT
|
||||||
|
t.release_id,
|
||||||
|
COUNT(*) AS listen_count
|
||||||
|
FROM listens l
|
||||||
|
JOIN tracks t ON l.track_id = t.id
|
||||||
|
GROUP BY t.release_id
|
||||||
|
) x
|
||||||
|
)
|
||||||
|
WHERE release_id = $1;
|
||||||
|
|
||||||
-- name: CountTopReleases :one
|
-- name: CountTopReleases :one
|
||||||
SELECT COUNT(DISTINCT r.id) AS total_count
|
SELECT COUNT(DISTINCT r.id) AS total_count
|
||||||
FROM listens l
|
FROM listens l
|
||||||
|
|
|
||||||
|
|
@ -124,6 +124,24 @@ FROM (
|
||||||
ORDER BY x.listen_count DESC, x.id
|
ORDER BY x.listen_count DESC, x.id
|
||||||
LIMIT $3 OFFSET $4;
|
LIMIT $3 OFFSET $4;
|
||||||
|
|
||||||
|
-- name: GetTrackAllTimeRank :one
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
rank
|
||||||
|
FROM (
|
||||||
|
SELECT
|
||||||
|
x.id,
|
||||||
|
RANK() OVER (ORDER BY x.listen_count DESC) AS rank
|
||||||
|
FROM (
|
||||||
|
SELECT
|
||||||
|
t.id,
|
||||||
|
COUNT(*) AS listen_count
|
||||||
|
FROM listens l
|
||||||
|
JOIN tracks_with_title t ON l.track_id = t.id
|
||||||
|
GROUP BY t.id) x
|
||||||
|
) y
|
||||||
|
WHERE id = $1;
|
||||||
|
|
||||||
-- name: CountTopTracks :one
|
-- name: CountTopTracks :one
|
||||||
SELECT COUNT(DISTINCT l.track_id) AS total_count
|
SELECT COUNT(DISTINCT l.track_id) AS total_count
|
||||||
FROM listens l
|
FROM listens l
|
||||||
|
|
|
||||||
|
|
@ -23,32 +23,13 @@ func (d *Psql) GetAlbum(ctx context.Context, opts db.GetAlbumOpts) (*models.Albu
|
||||||
var err error
|
var err error
|
||||||
var ret = new(models.Album)
|
var ret = new(models.Album)
|
||||||
|
|
||||||
if opts.ID != 0 {
|
if opts.MusicBrainzID != uuid.Nil {
|
||||||
l.Debug().Msgf("Fetching album from DB with id %d", opts.ID)
|
|
||||||
row, err := d.q.GetRelease(ctx, opts.ID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("GetAlbum: %w", err)
|
|
||||||
}
|
|
||||||
ret.ID = row.ID
|
|
||||||
ret.MbzID = row.MusicBrainzID
|
|
||||||
ret.Title = row.Title
|
|
||||||
ret.Image = row.Image
|
|
||||||
ret.VariousArtists = row.VariousArtists
|
|
||||||
err = json.Unmarshal(row.Artists, &ret.Artists)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("GetAlbum: json.Unmarshal: %w", err)
|
|
||||||
}
|
|
||||||
} else if opts.MusicBrainzID != uuid.Nil {
|
|
||||||
l.Debug().Msgf("Fetching album from DB with MusicBrainz Release ID %s", opts.MusicBrainzID)
|
l.Debug().Msgf("Fetching album from DB with MusicBrainz Release ID %s", opts.MusicBrainzID)
|
||||||
row, err := d.q.GetReleaseByMbzID(ctx, &opts.MusicBrainzID)
|
row, err := d.q.GetReleaseByMbzID(ctx, &opts.MusicBrainzID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("GetAlbum: %w", err)
|
return nil, fmt.Errorf("GetAlbum: %w", err)
|
||||||
}
|
}
|
||||||
ret.ID = row.ID
|
opts.ID = row.ID
|
||||||
ret.MbzID = row.MusicBrainzID
|
|
||||||
ret.Title = row.Title
|
|
||||||
ret.Image = row.Image
|
|
||||||
ret.VariousArtists = row.VariousArtists
|
|
||||||
} else if opts.ArtistID != 0 && opts.Title != "" {
|
} else if opts.ArtistID != 0 && opts.Title != "" {
|
||||||
l.Debug().Msgf("Fetching album from DB with artist_id %d and title %s", opts.ArtistID, opts.Title)
|
l.Debug().Msgf("Fetching album from DB with artist_id %d and title %s", opts.ArtistID, opts.Title)
|
||||||
row, err := d.q.GetReleaseByArtistAndTitle(ctx, repository.GetReleaseByArtistAndTitleParams{
|
row, err := d.q.GetReleaseByArtistAndTitle(ctx, repository.GetReleaseByArtistAndTitleParams{
|
||||||
|
|
@ -58,11 +39,7 @@ func (d *Psql) GetAlbum(ctx context.Context, opts db.GetAlbumOpts) (*models.Albu
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("GetAlbum: %w", err)
|
return nil, fmt.Errorf("GetAlbum: %w", err)
|
||||||
}
|
}
|
||||||
ret.ID = row.ID
|
opts.ID = row.ID
|
||||||
ret.MbzID = row.MusicBrainzID
|
|
||||||
ret.Title = row.Title
|
|
||||||
ret.Image = row.Image
|
|
||||||
ret.VariousArtists = row.VariousArtists
|
|
||||||
} else if opts.ArtistID != 0 && len(opts.Titles) > 0 {
|
} else if opts.ArtistID != 0 && len(opts.Titles) > 0 {
|
||||||
l.Debug().Msgf("Fetching release group from DB with artist_id %d and titles %v", opts.ArtistID, opts.Titles)
|
l.Debug().Msgf("Fetching release group from DB with artist_id %d and titles %v", opts.ArtistID, opts.Titles)
|
||||||
row, err := d.q.GetReleaseByArtistAndTitles(ctx, repository.GetReleaseByArtistAndTitlesParams{
|
row, err := d.q.GetReleaseByArtistAndTitles(ctx, repository.GetReleaseByArtistAndTitlesParams{
|
||||||
|
|
@ -72,19 +49,19 @@ func (d *Psql) GetAlbum(ctx context.Context, opts db.GetAlbumOpts) (*models.Albu
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("GetAlbum: %w", err)
|
return nil, fmt.Errorf("GetAlbum: %w", err)
|
||||||
}
|
}
|
||||||
ret.ID = row.ID
|
opts.ID = row.ID
|
||||||
ret.MbzID = row.MusicBrainzID
|
}
|
||||||
ret.Title = row.Title
|
|
||||||
ret.Image = row.Image
|
l.Debug().Msgf("Fetching album from DB with id %d", opts.ID)
|
||||||
ret.VariousArtists = row.VariousArtists
|
row, err := d.q.GetRelease(ctx, opts.ID)
|
||||||
} else {
|
if err != nil {
|
||||||
return nil, errors.New("GetAlbum: insufficient information to get album")
|
return nil, fmt.Errorf("GetAlbum: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
count, err := d.q.CountListensFromRelease(ctx, repository.CountListensFromReleaseParams{
|
count, err := d.q.CountListensFromRelease(ctx, repository.CountListensFromReleaseParams{
|
||||||
ListenedAt: time.Unix(0, 0),
|
ListenedAt: time.Unix(0, 0),
|
||||||
ListenedAt_2: time.Now(),
|
ListenedAt_2: time.Now(),
|
||||||
ReleaseID: ret.ID,
|
ReleaseID: opts.ID,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("GetAlbum: CountListensFromRelease: %w", err)
|
return nil, fmt.Errorf("GetAlbum: CountListensFromRelease: %w", err)
|
||||||
|
|
@ -92,17 +69,32 @@ func (d *Psql) GetAlbum(ctx context.Context, opts db.GetAlbumOpts) (*models.Albu
|
||||||
|
|
||||||
seconds, err := d.CountTimeListenedToItem(ctx, db.TimeListenedOpts{
|
seconds, err := d.CountTimeListenedToItem(ctx, db.TimeListenedOpts{
|
||||||
Timeframe: db.Timeframe{Period: db.PeriodAllTime},
|
Timeframe: db.Timeframe{Period: db.PeriodAllTime},
|
||||||
AlbumID: ret.ID,
|
AlbumID: opts.ID,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("GetAlbum: CountTimeListenedToItem: %w", err)
|
return nil, fmt.Errorf("GetAlbum: CountTimeListenedToItem: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
firstListen, err := d.q.GetFirstListenFromRelease(ctx, ret.ID)
|
firstListen, err := d.q.GetFirstListenFromRelease(ctx, opts.ID)
|
||||||
if err != nil && !errors.Is(err, pgx.ErrNoRows) {
|
if err != nil && !errors.Is(err, pgx.ErrNoRows) {
|
||||||
return nil, fmt.Errorf("GetAlbum: GetFirstListenFromRelease: %w", err)
|
return nil, fmt.Errorf("GetAlbum: GetFirstListenFromRelease: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rank, err := d.q.GetReleaseAllTimeRank(ctx, opts.ID)
|
||||||
|
if err != nil && !errors.Is(err, pgx.ErrNoRows) {
|
||||||
|
return nil, fmt.Errorf("GetAlbum: GetReleaseAllTimeRank: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ret.ID = row.ID
|
||||||
|
ret.MbzID = row.MusicBrainzID
|
||||||
|
ret.Title = row.Title
|
||||||
|
ret.Image = row.Image
|
||||||
|
ret.VariousArtists = row.VariousArtists
|
||||||
|
err = json.Unmarshal(row.Artists, &ret.Artists)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("GetAlbum: json.Unmarshal: %w", err)
|
||||||
|
}
|
||||||
|
ret.AllTimeRank = rank.Rank
|
||||||
ret.ListenCount = count
|
ret.ListenCount = count
|
||||||
ret.TimeListened = seconds
|
ret.TimeListened = seconds
|
||||||
ret.FirstListen = firstListen.ListenedAt.Unix()
|
ret.FirstListen = firstListen.ListenedAt.Unix()
|
||||||
|
|
|
||||||
|
|
@ -20,114 +20,60 @@ import (
|
||||||
// this function sucks because sqlc keeps making new types for rows that are the same
|
// this function sucks because sqlc keeps making new types for rows that are the same
|
||||||
func (d *Psql) GetArtist(ctx context.Context, opts db.GetArtistOpts) (*models.Artist, error) {
|
func (d *Psql) GetArtist(ctx context.Context, opts db.GetArtistOpts) (*models.Artist, error) {
|
||||||
l := logger.FromContext(ctx)
|
l := logger.FromContext(ctx)
|
||||||
if opts.ID != 0 {
|
if opts.MusicBrainzID != uuid.Nil {
|
||||||
l.Debug().Msgf("Fetching artist from DB with id %d", opts.ID)
|
|
||||||
row, err := d.q.GetArtist(ctx, opts.ID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("GetArtist: GetArtist by ID: %w", err)
|
|
||||||
}
|
|
||||||
count, err := d.q.CountListensFromArtist(ctx, repository.CountListensFromArtistParams{
|
|
||||||
ListenedAt: time.Unix(0, 0),
|
|
||||||
ListenedAt_2: time.Now(),
|
|
||||||
ArtistID: row.ID,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("GetArtist: CountListensFromArtist: %w", err)
|
|
||||||
}
|
|
||||||
seconds, err := d.CountTimeListenedToItem(ctx, db.TimeListenedOpts{
|
|
||||||
Timeframe: db.Timeframe{Period: db.PeriodAllTime},
|
|
||||||
ArtistID: row.ID,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("GetArtist: CountTimeListenedToItem: %w", err)
|
|
||||||
}
|
|
||||||
firstListen, err := d.q.GetFirstListenFromArtist(ctx, row.ID)
|
|
||||||
if err != nil && !errors.Is(err, pgx.ErrNoRows) {
|
|
||||||
return nil, fmt.Errorf("GetAlbum: GetFirstListenFromArtist: %w", err)
|
|
||||||
}
|
|
||||||
return &models.Artist{
|
|
||||||
ID: row.ID,
|
|
||||||
MbzID: row.MusicBrainzID,
|
|
||||||
Name: row.Name,
|
|
||||||
Aliases: row.Aliases,
|
|
||||||
Image: row.Image,
|
|
||||||
ListenCount: count,
|
|
||||||
TimeListened: seconds,
|
|
||||||
FirstListen: firstListen.ListenedAt.Unix(),
|
|
||||||
}, nil
|
|
||||||
} else if opts.MusicBrainzID != uuid.Nil {
|
|
||||||
l.Debug().Msgf("Fetching artist from DB with MusicBrainz ID %s", opts.MusicBrainzID)
|
l.Debug().Msgf("Fetching artist from DB with MusicBrainz ID %s", opts.MusicBrainzID)
|
||||||
row, err := d.q.GetArtistByMbzID(ctx, &opts.MusicBrainzID)
|
row, err := d.q.GetArtistByMbzID(ctx, &opts.MusicBrainzID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("GetArtist: GetArtistByMbzID: %w", err)
|
return nil, fmt.Errorf("GetArtist: GetArtistByMbzID: %w", err)
|
||||||
}
|
}
|
||||||
count, err := d.q.CountListensFromArtist(ctx, repository.CountListensFromArtistParams{
|
opts.ID = row.ID
|
||||||
ListenedAt: time.Unix(0, 0),
|
|
||||||
ListenedAt_2: time.Now(),
|
|
||||||
ArtistID: row.ID,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("GetArtist: CountListensFromArtist: %w", err)
|
|
||||||
}
|
|
||||||
seconds, err := d.CountTimeListenedToItem(ctx, db.TimeListenedOpts{
|
|
||||||
Timeframe: db.Timeframe{Period: db.PeriodAllTime},
|
|
||||||
ArtistID: row.ID,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("GetArtist: CountTimeListenedToItem: %w", err)
|
|
||||||
}
|
|
||||||
firstListen, err := d.q.GetFirstListenFromArtist(ctx, row.ID)
|
|
||||||
if err != nil && !errors.Is(err, pgx.ErrNoRows) {
|
|
||||||
return nil, fmt.Errorf("GetAlbum: GetFirstListenFromArtist: %w", err)
|
|
||||||
}
|
|
||||||
return &models.Artist{
|
|
||||||
ID: row.ID,
|
|
||||||
MbzID: row.MusicBrainzID,
|
|
||||||
Name: row.Name,
|
|
||||||
Aliases: row.Aliases,
|
|
||||||
Image: row.Image,
|
|
||||||
ListenCount: count,
|
|
||||||
TimeListened: seconds,
|
|
||||||
FirstListen: firstListen.ListenedAt.Unix(),
|
|
||||||
}, nil
|
|
||||||
} else if opts.Name != "" {
|
} else if opts.Name != "" {
|
||||||
l.Debug().Msgf("Fetching artist from DB with name '%s'", opts.Name)
|
l.Debug().Msgf("Fetching artist from DB with name '%s'", opts.Name)
|
||||||
row, err := d.q.GetArtistByName(ctx, opts.Name)
|
row, err := d.q.GetArtistByName(ctx, opts.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("GetArtist: GetArtistByName: %w", err)
|
return nil, fmt.Errorf("GetArtist: GetArtistByName: %w", err)
|
||||||
}
|
}
|
||||||
count, err := d.q.CountListensFromArtist(ctx, repository.CountListensFromArtistParams{
|
opts.ID = row.ID
|
||||||
ListenedAt: time.Unix(0, 0),
|
|
||||||
ListenedAt_2: time.Now(),
|
|
||||||
ArtistID: row.ID,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("GetArtist: CountListensFromArtist: %w", err)
|
|
||||||
}
|
|
||||||
seconds, err := d.CountTimeListenedToItem(ctx, db.TimeListenedOpts{
|
|
||||||
Timeframe: db.Timeframe{Period: db.PeriodAllTime},
|
|
||||||
ArtistID: row.ID,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("GetArtist: CountTimeListenedToItem: %w", err)
|
|
||||||
}
|
|
||||||
firstListen, err := d.q.GetFirstListenFromArtist(ctx, row.ID)
|
|
||||||
if err != nil && !errors.Is(err, pgx.ErrNoRows) {
|
|
||||||
return nil, fmt.Errorf("GetAlbum: GetFirstListenFromArtist: %w", err)
|
|
||||||
}
|
|
||||||
return &models.Artist{
|
|
||||||
ID: row.ID,
|
|
||||||
MbzID: row.MusicBrainzID,
|
|
||||||
Name: row.Name,
|
|
||||||
Aliases: row.Aliases,
|
|
||||||
Image: row.Image,
|
|
||||||
ListenCount: count,
|
|
||||||
TimeListened: seconds,
|
|
||||||
FirstListen: firstListen.ListenedAt.Unix(),
|
|
||||||
}, nil
|
|
||||||
} else {
|
|
||||||
return nil, errors.New("insufficient information to get artist")
|
|
||||||
}
|
}
|
||||||
|
l.Debug().Msgf("Fetching artist from DB with id %d", opts.ID)
|
||||||
|
row, err := d.q.GetArtist(ctx, opts.ID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("GetArtist: GetArtist by ID: %w", err)
|
||||||
|
}
|
||||||
|
count, err := d.q.CountListensFromArtist(ctx, repository.CountListensFromArtistParams{
|
||||||
|
ListenedAt: time.Unix(0, 0),
|
||||||
|
ListenedAt_2: time.Now(),
|
||||||
|
ArtistID: row.ID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("GetArtist: CountListensFromArtist: %w", err)
|
||||||
|
}
|
||||||
|
seconds, err := d.CountTimeListenedToItem(ctx, db.TimeListenedOpts{
|
||||||
|
Timeframe: db.Timeframe{Period: db.PeriodAllTime},
|
||||||
|
ArtistID: row.ID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("GetArtist: CountTimeListenedToItem: %w", err)
|
||||||
|
}
|
||||||
|
firstListen, err := d.q.GetFirstListenFromArtist(ctx, row.ID)
|
||||||
|
if err != nil && !errors.Is(err, pgx.ErrNoRows) {
|
||||||
|
return nil, fmt.Errorf("GetAlbum: GetFirstListenFromArtist: %w", err)
|
||||||
|
}
|
||||||
|
rank, err := d.q.GetArtistAllTimeRank(ctx, opts.ID)
|
||||||
|
if err != nil && !errors.Is(err, pgx.ErrNoRows) {
|
||||||
|
return nil, fmt.Errorf("GetArtist: GetArtistAllTimeRank: %w", err)
|
||||||
|
}
|
||||||
|
return &models.Artist{
|
||||||
|
ID: row.ID,
|
||||||
|
MbzID: row.MusicBrainzID,
|
||||||
|
Name: row.Name,
|
||||||
|
Aliases: row.Aliases,
|
||||||
|
Image: row.Image,
|
||||||
|
ListenCount: count,
|
||||||
|
TimeListened: seconds,
|
||||||
|
AllTimeRank: rank.Rank,
|
||||||
|
FirstListen: firstListen.ListenedAt.Unix(),
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inserts all unique aliases into the DB with specified source
|
// Inserts all unique aliases into the DB with specified source
|
||||||
|
|
|
||||||
|
|
@ -21,37 +21,13 @@ func (d *Psql) GetTrack(ctx context.Context, opts db.GetTrackOpts) (*models.Trac
|
||||||
l := logger.FromContext(ctx)
|
l := logger.FromContext(ctx)
|
||||||
var track models.Track
|
var track models.Track
|
||||||
|
|
||||||
if opts.ID != 0 {
|
if opts.MusicBrainzID != uuid.Nil {
|
||||||
l.Debug().Msgf("Fetching track from DB with id %d", opts.ID)
|
|
||||||
t, err := d.q.GetTrack(ctx, opts.ID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("GetTrack: GetTrack By ID: %w", err)
|
|
||||||
}
|
|
||||||
track = models.Track{
|
|
||||||
ID: t.ID,
|
|
||||||
MbzID: t.MusicBrainzID,
|
|
||||||
Title: t.Title,
|
|
||||||
AlbumID: t.ReleaseID,
|
|
||||||
Image: t.Image,
|
|
||||||
Duration: t.Duration,
|
|
||||||
}
|
|
||||||
err = json.Unmarshal(t.Artists, &track.Artists)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("GetTrack: json.Unmarshal: %w", err)
|
|
||||||
}
|
|
||||||
} else if opts.MusicBrainzID != uuid.Nil {
|
|
||||||
l.Debug().Msgf("Fetching track from DB with MusicBrainz ID %s", opts.MusicBrainzID)
|
l.Debug().Msgf("Fetching track from DB with MusicBrainz ID %s", opts.MusicBrainzID)
|
||||||
t, err := d.q.GetTrackByMbzID(ctx, &opts.MusicBrainzID)
|
t, err := d.q.GetTrackByMbzID(ctx, &opts.MusicBrainzID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("GetTrack: GetTrackByMbzID: %w", err)
|
return nil, fmt.Errorf("GetTrack: GetTrackByMbzID: %w", err)
|
||||||
}
|
}
|
||||||
track = models.Track{
|
opts.ID = t.ID
|
||||||
ID: t.ID,
|
|
||||||
MbzID: t.MusicBrainzID,
|
|
||||||
Title: t.Title,
|
|
||||||
AlbumID: t.ReleaseID,
|
|
||||||
Duration: t.Duration,
|
|
||||||
}
|
|
||||||
} else if len(opts.ArtistIDs) > 0 && opts.ReleaseID != 0 {
|
} else if len(opts.ArtistIDs) > 0 && opts.ReleaseID != 0 {
|
||||||
l.Debug().Msgf("Fetching track from DB from release id %d with title '%s' and artist id(s) '%v'", opts.ReleaseID, opts.Title, opts.ArtistIDs)
|
l.Debug().Msgf("Fetching track from DB from release id %d with title '%s' and artist id(s) '%v'", opts.ReleaseID, opts.Title, opts.ArtistIDs)
|
||||||
t, err := d.q.GetTrackByTrackInfo(ctx, repository.GetTrackByTrackInfoParams{
|
t, err := d.q.GetTrackByTrackInfo(ctx, repository.GetTrackByTrackInfoParams{
|
||||||
|
|
@ -62,21 +38,19 @@ func (d *Psql) GetTrack(ctx context.Context, opts db.GetTrackOpts) (*models.Trac
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("GetTrack: GetTrackByTrackInfo: %w", err)
|
return nil, fmt.Errorf("GetTrack: GetTrackByTrackInfo: %w", err)
|
||||||
}
|
}
|
||||||
track = models.Track{
|
opts.ID = t.ID
|
||||||
ID: t.ID,
|
}
|
||||||
MbzID: t.MusicBrainzID,
|
|
||||||
Title: t.Title,
|
l.Debug().Msgf("Fetching track from DB with id %d", opts.ID)
|
||||||
AlbumID: t.ReleaseID,
|
t, err := d.q.GetTrack(ctx, opts.ID)
|
||||||
Duration: t.Duration,
|
if err != nil {
|
||||||
}
|
return nil, fmt.Errorf("GetTrack: GetTrack By ID: %w", err)
|
||||||
} else {
|
|
||||||
return nil, errors.New("GetTrack: insufficient information to get track")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
count, err := d.q.CountListensFromTrack(ctx, repository.CountListensFromTrackParams{
|
count, err := d.q.CountListensFromTrack(ctx, repository.CountListensFromTrackParams{
|
||||||
ListenedAt: time.Unix(0, 0),
|
ListenedAt: time.Unix(0, 0),
|
||||||
ListenedAt_2: time.Now(),
|
ListenedAt_2: time.Now(),
|
||||||
TrackID: track.ID,
|
TrackID: opts.ID,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("GetTrack: CountListensFromTrack: %w", err)
|
return nil, fmt.Errorf("GetTrack: CountListensFromTrack: %w", err)
|
||||||
|
|
@ -84,20 +58,37 @@ func (d *Psql) GetTrack(ctx context.Context, opts db.GetTrackOpts) (*models.Trac
|
||||||
|
|
||||||
seconds, err := d.CountTimeListenedToItem(ctx, db.TimeListenedOpts{
|
seconds, err := d.CountTimeListenedToItem(ctx, db.TimeListenedOpts{
|
||||||
Timeframe: db.Timeframe{Period: db.PeriodAllTime},
|
Timeframe: db.Timeframe{Period: db.PeriodAllTime},
|
||||||
TrackID: track.ID,
|
TrackID: opts.ID,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("GetTrack: CountTimeListenedToItem: %w", err)
|
return nil, fmt.Errorf("GetTrack: CountTimeListenedToItem: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
firstListen, err := d.q.GetFirstListenFromTrack(ctx, track.ID)
|
firstListen, err := d.q.GetFirstListenFromTrack(ctx, opts.ID)
|
||||||
if err != nil && !errors.Is(err, pgx.ErrNoRows) {
|
if err != nil && !errors.Is(err, pgx.ErrNoRows) {
|
||||||
return nil, fmt.Errorf("GetAlbum: GetFirstListenFromRelease: %w", err)
|
return nil, fmt.Errorf("GetAlbum: GetFirstListenFromRelease: %w", err)
|
||||||
}
|
}
|
||||||
|
rank, err := d.q.GetTrackAllTimeRank(ctx, opts.ID)
|
||||||
|
if err != nil && !errors.Is(err, pgx.ErrNoRows) {
|
||||||
|
return nil, fmt.Errorf("GetAlbum: GetTrackAllTimeRank: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
track.ListenCount = count
|
track = models.Track{
|
||||||
track.TimeListened = seconds
|
ID: t.ID,
|
||||||
track.FirstListen = firstListen.ListenedAt.Unix()
|
MbzID: t.MusicBrainzID,
|
||||||
|
Title: t.Title,
|
||||||
|
AlbumID: t.ReleaseID,
|
||||||
|
Image: t.Image,
|
||||||
|
Duration: t.Duration,
|
||||||
|
AllTimeRank: rank.Rank,
|
||||||
|
ListenCount: count,
|
||||||
|
TimeListened: seconds,
|
||||||
|
FirstListen: firstListen.ListenedAt.Unix(),
|
||||||
|
}
|
||||||
|
err = json.Unmarshal(t.Artists, &track.Artists)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("GetTrack: json.Unmarshal: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
return &track, nil
|
return &track, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,11 +12,5 @@ type Album struct {
|
||||||
ListenCount int64 `json:"listen_count"`
|
ListenCount int64 `json:"listen_count"`
|
||||||
TimeListened int64 `json:"time_listened"`
|
TimeListened int64 `json:"time_listened"`
|
||||||
FirstListen int64 `json:"first_listen"`
|
FirstListen int64 `json:"first_listen"`
|
||||||
|
AllTimeRank int64 `json:"all_time_rank"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// type SimpleAlbum struct {
|
|
||||||
// ID int32 `json:"id"`
|
|
||||||
// Title string `json:"title"`
|
|
||||||
// VariousArtists bool `json:"is_various_artists"`
|
|
||||||
// Image uuid.UUID `json:"image"`
|
|
||||||
// }
|
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ type Artist struct {
|
||||||
TimeListened int64 `json:"time_listened"`
|
TimeListened int64 `json:"time_listened"`
|
||||||
FirstListen int64 `json:"first_listen"`
|
FirstListen int64 `json:"first_listen"`
|
||||||
IsPrimary bool `json:"is_primary,omitempty"`
|
IsPrimary bool `json:"is_primary,omitempty"`
|
||||||
|
AllTimeRank int64 `json:"all_time_rank"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type SimpleArtist struct {
|
type SimpleArtist struct {
|
||||||
|
|
|
||||||
|
|
@ -13,4 +13,5 @@ type Track struct {
|
||||||
AlbumID int32 `json:"album_id"`
|
AlbumID int32 `json:"album_id"`
|
||||||
TimeListened int64 `json:"time_listened"`
|
TimeListened int64 `json:"time_listened"`
|
||||||
FirstListen int64 `json:"first_listen"`
|
FirstListen int64 `json:"first_listen"`
|
||||||
|
AllTimeRank int64 `json:"all_time_rank"`
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -134,6 +134,39 @@ func (q *Queries) GetArtist(ctx context.Context, id int32) (GetArtistRow, error)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getArtistAllTimeRank = `-- name: GetArtistAllTimeRank :one
|
||||||
|
SELECT
|
||||||
|
artist_id,
|
||||||
|
rank
|
||||||
|
FROM (
|
||||||
|
SELECT
|
||||||
|
x.artist_id,
|
||||||
|
RANK() OVER (ORDER BY x.listen_count DESC) AS rank
|
||||||
|
FROM (
|
||||||
|
SELECT
|
||||||
|
at.artist_id,
|
||||||
|
COUNT(*) AS listen_count
|
||||||
|
FROM listens l
|
||||||
|
JOIN tracks t ON l.track_id = t.id
|
||||||
|
JOIN artist_tracks at ON t.id = at.track_id
|
||||||
|
GROUP BY at.artist_id
|
||||||
|
) x
|
||||||
|
)
|
||||||
|
WHERE artist_id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
type GetArtistAllTimeRankRow struct {
|
||||||
|
ArtistID int32
|
||||||
|
Rank int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) GetArtistAllTimeRank(ctx context.Context, artistID int32) (GetArtistAllTimeRankRow, error) {
|
||||||
|
row := q.db.QueryRow(ctx, getArtistAllTimeRank, artistID)
|
||||||
|
var i GetArtistAllTimeRankRow
|
||||||
|
err := row.Scan(&i.ArtistID, &i.Rank)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
const getArtistByImage = `-- name: GetArtistByImage :one
|
const getArtistByImage = `-- name: GetArtistByImage :one
|
||||||
SELECT id, musicbrainz_id, image, image_source FROM artists WHERE image = $1 LIMIT 1
|
SELECT id, musicbrainz_id, image, image_source FROM artists WHERE image = $1 LIMIT 1
|
||||||
`
|
`
|
||||||
|
|
|
||||||
|
|
@ -141,6 +141,38 @@ func (q *Queries) GetRelease(ctx context.Context, id int32) (GetReleaseRow, erro
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getReleaseAllTimeRank = `-- name: GetReleaseAllTimeRank :one
|
||||||
|
SELECT
|
||||||
|
release_id,
|
||||||
|
rank
|
||||||
|
FROM (
|
||||||
|
SELECT
|
||||||
|
x.release_id,
|
||||||
|
RANK() OVER (ORDER BY x.listen_count DESC) AS rank
|
||||||
|
FROM (
|
||||||
|
SELECT
|
||||||
|
t.release_id,
|
||||||
|
COUNT(*) AS listen_count
|
||||||
|
FROM listens l
|
||||||
|
JOIN tracks t ON l.track_id = t.id
|
||||||
|
GROUP BY t.release_id
|
||||||
|
) x
|
||||||
|
)
|
||||||
|
WHERE release_id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
type GetReleaseAllTimeRankRow struct {
|
||||||
|
ReleaseID int32
|
||||||
|
Rank int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) GetReleaseAllTimeRank(ctx context.Context, releaseID int32) (GetReleaseAllTimeRankRow, error) {
|
||||||
|
row := q.db.QueryRow(ctx, getReleaseAllTimeRank, releaseID)
|
||||||
|
var i GetReleaseAllTimeRankRow
|
||||||
|
err := row.Scan(&i.ReleaseID, &i.Rank)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
const getReleaseByArtistAndTitle = `-- name: GetReleaseByArtistAndTitle :one
|
const getReleaseByArtistAndTitle = `-- name: GetReleaseByArtistAndTitle :one
|
||||||
SELECT r.id, r.musicbrainz_id, r.image, r.various_artists, r.image_source, r.title
|
SELECT r.id, r.musicbrainz_id, r.image, r.various_artists, r.image_source, r.title
|
||||||
FROM releases_with_title r
|
FROM releases_with_title r
|
||||||
|
|
|
||||||
|
|
@ -438,6 +438,37 @@ func (q *Queries) GetTrack(ctx context.Context, id int32) (GetTrackRow, error) {
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getTrackAllTimeRank = `-- name: GetTrackAllTimeRank :one
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
rank
|
||||||
|
FROM (
|
||||||
|
SELECT
|
||||||
|
x.id,
|
||||||
|
RANK() OVER (ORDER BY x.listen_count DESC) AS rank
|
||||||
|
FROM (
|
||||||
|
SELECT
|
||||||
|
t.id,
|
||||||
|
COUNT(*) AS listen_count
|
||||||
|
FROM listens l
|
||||||
|
JOIN tracks_with_title t ON l.track_id = t.id
|
||||||
|
GROUP BY t.id) x
|
||||||
|
) y
|
||||||
|
WHERE id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
type GetTrackAllTimeRankRow struct {
|
||||||
|
ID int32
|
||||||
|
Rank int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) GetTrackAllTimeRank(ctx context.Context, id int32) (GetTrackAllTimeRankRow, error) {
|
||||||
|
row := q.db.QueryRow(ctx, getTrackAllTimeRank, id)
|
||||||
|
var i GetTrackAllTimeRankRow
|
||||||
|
err := row.Scan(&i.ID, &i.Rank)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
const getTrackByMbzID = `-- name: GetTrackByMbzID :one
|
const getTrackByMbzID = `-- name: GetTrackByMbzID :one
|
||||||
SELECT id, musicbrainz_id, duration, release_id, title FROM tracks_with_title
|
SELECT id, musicbrainz_id, duration, release_id, title FROM tracks_with_title
|
||||||
WHERE musicbrainz_id = $1 LIMIT 1
|
WHERE musicbrainz_id = $1 LIMIT 1
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue