feat(db): add M6a home-section queries (recently added, most played, last played)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -313,6 +313,54 @@ func (q *Queries) ListAlbumsRandom(ctx context.Context, limit int32) ([]Album, e
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const listRecentlyAddedAlbumsWithArtist = `-- name: ListRecentlyAddedAlbumsWithArtist :many
|
||||
SELECT albums.id, albums.title, albums.sort_title, albums.artist_id, albums.release_date, albums.mbid, albums.cover_art_path, albums.created_at, albums.updated_at, artists.name AS artist_name
|
||||
FROM albums
|
||||
JOIN artists ON artists.id = albums.artist_id
|
||||
ORDER BY albums.created_at DESC, albums.id
|
||||
LIMIT $1
|
||||
`
|
||||
|
||||
type ListRecentlyAddedAlbumsWithArtistRow struct {
|
||||
Album Album
|
||||
ArtistName string
|
||||
}
|
||||
|
||||
// M6a: recently-added albums joined with artist_name + artist_id for the
|
||||
// home-page section. created_at is the row-insert timestamp from the
|
||||
// scanner — the closest proxy to "added to library". Stable secondary
|
||||
// ordering by id keeps pagination tie-break deterministic.
|
||||
func (q *Queries) ListRecentlyAddedAlbumsWithArtist(ctx context.Context, limit int32) ([]ListRecentlyAddedAlbumsWithArtistRow, error) {
|
||||
rows, err := q.db.Query(ctx, listRecentlyAddedAlbumsWithArtist, limit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []ListRecentlyAddedAlbumsWithArtistRow
|
||||
for rows.Next() {
|
||||
var i ListRecentlyAddedAlbumsWithArtistRow
|
||||
if err := rows.Scan(
|
||||
&i.Album.ID,
|
||||
&i.Album.Title,
|
||||
&i.Album.SortTitle,
|
||||
&i.Album.ArtistID,
|
||||
&i.Album.ReleaseDate,
|
||||
&i.Album.Mbid,
|
||||
&i.Album.CoverArtPath,
|
||||
&i.Album.CreatedAt,
|
||||
&i.Album.UpdatedAt,
|
||||
&i.ArtistName,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const searchAlbums = `-- name: SearchAlbums :many
|
||||
SELECT id, title, sort_title, artist_id, release_date, mbid, cover_art_path, created_at, updated_at FROM albums
|
||||
WHERE title ILIKE '%' || $1 || '%'
|
||||
|
||||
@@ -11,6 +11,151 @@ import (
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
)
|
||||
|
||||
const listLastPlayedArtistsForUser = `-- name: ListLastPlayedArtistsForUser :many
|
||||
SELECT a.id, a.name, a.sort_name, a.mbid, a.created_at, a.updated_at,
|
||||
cov.id AS cover_album_id,
|
||||
cnt.album_count::bigint AS album_count,
|
||||
max_started.started_at::timestamptz AS last_played_at
|
||||
FROM artists a
|
||||
JOIN LATERAL (
|
||||
SELECT max(pe.started_at) AS started_at
|
||||
FROM play_events pe
|
||||
JOIN tracks t ON t.id = pe.track_id
|
||||
WHERE pe.user_id = $1 AND t.artist_id = a.id
|
||||
) max_started ON max_started.started_at IS NOT NULL
|
||||
LEFT JOIN LATERAL (
|
||||
SELECT id FROM albums
|
||||
WHERE artist_id = a.id AND cover_art_path IS NOT NULL
|
||||
ORDER BY created_at DESC LIMIT 1
|
||||
) cov ON true
|
||||
LEFT JOIN LATERAL (
|
||||
SELECT count(*) AS album_count
|
||||
FROM albums WHERE artist_id = a.id
|
||||
) cnt ON true
|
||||
ORDER BY max_started.started_at DESC, a.id
|
||||
LIMIT $2
|
||||
`
|
||||
|
||||
type ListLastPlayedArtistsForUserParams struct {
|
||||
UserID pgtype.UUID
|
||||
Limit int32
|
||||
}
|
||||
|
||||
type ListLastPlayedArtistsForUserRow struct {
|
||||
Artist Artist
|
||||
CoverAlbumID pgtype.UUID
|
||||
AlbumCount int64
|
||||
LastPlayedAt pgtype.Timestamptz
|
||||
}
|
||||
|
||||
// M6a: artists ranked by max(play_events.started_at) for the user, with
|
||||
// a derived cover_album_id via a representative-album lateral join (most
|
||||
// recent album that has cover_art_path set). album_count joined for the
|
||||
// ArtistRef wire shape.
|
||||
func (q *Queries) ListLastPlayedArtistsForUser(ctx context.Context, arg ListLastPlayedArtistsForUserParams) ([]ListLastPlayedArtistsForUserRow, error) {
|
||||
rows, err := q.db.Query(ctx, listLastPlayedArtistsForUser, arg.UserID, arg.Limit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []ListLastPlayedArtistsForUserRow
|
||||
for rows.Next() {
|
||||
var i ListLastPlayedArtistsForUserRow
|
||||
if err := rows.Scan(
|
||||
&i.Artist.ID,
|
||||
&i.Artist.Name,
|
||||
&i.Artist.SortName,
|
||||
&i.Artist.Mbid,
|
||||
&i.Artist.CreatedAt,
|
||||
&i.Artist.UpdatedAt,
|
||||
&i.CoverAlbumID,
|
||||
&i.AlbumCount,
|
||||
&i.LastPlayedAt,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const listMostPlayedTracksForUser = `-- name: ListMostPlayedTracksForUser :many
|
||||
SELECT t.id, t.title, t.album_id, t.artist_id, t.track_number, t.disc_number, t.duration_ms, t.file_path, t.file_size, t.file_format, t.bitrate, t.mbid, t.genre, t.added_at, t.updated_at,
|
||||
albums.title AS album_title,
|
||||
artists.name AS artist_name
|
||||
FROM tracks t
|
||||
JOIN albums ON albums.id = t.album_id
|
||||
JOIN artists ON artists.id = t.artist_id
|
||||
JOIN play_events pe ON pe.track_id = t.id
|
||||
WHERE pe.user_id = $1
|
||||
AND pe.was_skipped = false
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM lidarr_quarantine q
|
||||
WHERE q.user_id = $1 AND q.track_id = t.id
|
||||
)
|
||||
GROUP BY t.id, t.title, t.album_id, t.artist_id, t.duration_ms, t.file_path,
|
||||
t.file_format, t.file_size, t.bitrate, t.track_number, t.disc_number,
|
||||
t.mbid, t.genre, t.added_at, t.updated_at,
|
||||
albums.title, artists.name
|
||||
ORDER BY count(*) DESC, t.id
|
||||
LIMIT $2
|
||||
`
|
||||
|
||||
type ListMostPlayedTracksForUserParams struct {
|
||||
UserID pgtype.UUID
|
||||
Limit int32
|
||||
}
|
||||
|
||||
type ListMostPlayedTracksForUserRow struct {
|
||||
Track Track
|
||||
AlbumTitle string
|
||||
ArtistName string
|
||||
}
|
||||
|
||||
// M6a: top-N tracks by completed-play count for the user. was_skipped
|
||||
// excludes skips so a user spamming next doesn't fabricate a top track.
|
||||
// Quarantined tracks (per-user soft-hide from M5b) are filtered out.
|
||||
func (q *Queries) ListMostPlayedTracksForUser(ctx context.Context, arg ListMostPlayedTracksForUserParams) ([]ListMostPlayedTracksForUserRow, error) {
|
||||
rows, err := q.db.Query(ctx, listMostPlayedTracksForUser, arg.UserID, arg.Limit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []ListMostPlayedTracksForUserRow
|
||||
for rows.Next() {
|
||||
var i ListMostPlayedTracksForUserRow
|
||||
if err := rows.Scan(
|
||||
&i.Track.ID,
|
||||
&i.Track.Title,
|
||||
&i.Track.AlbumID,
|
||||
&i.Track.ArtistID,
|
||||
&i.Track.TrackNumber,
|
||||
&i.Track.DiscNumber,
|
||||
&i.Track.DurationMs,
|
||||
&i.Track.FilePath,
|
||||
&i.Track.FileSize,
|
||||
&i.Track.FileFormat,
|
||||
&i.Track.Bitrate,
|
||||
&i.Track.Mbid,
|
||||
&i.Track.Genre,
|
||||
&i.Track.AddedAt,
|
||||
&i.Track.UpdatedAt,
|
||||
&i.AlbumTitle,
|
||||
&i.ArtistName,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const loadRadioCandidates = `-- name: LoadRadioCandidates :many
|
||||
SELECT
|
||||
t.id, t.title, t.album_id, t.artist_id, t.track_number, t.disc_number, t.duration_ms, t.file_path, t.file_size, t.file_format, t.bitrate, t.mbid, t.genre, t.added_at, t.updated_at,
|
||||
|
||||
@@ -56,3 +56,14 @@ LIMIT $2 OFFSET $3;
|
||||
|
||||
-- name: CountAlbumsMatching :one
|
||||
SELECT COUNT(*) FROM albums WHERE title ILIKE '%' || $1::text || '%';
|
||||
|
||||
-- name: ListRecentlyAddedAlbumsWithArtist :many
|
||||
-- M6a: recently-added albums joined with artist_name + artist_id for the
|
||||
-- home-page section. created_at is the row-insert timestamp from the
|
||||
-- scanner — the closest proxy to "added to library". Stable secondary
|
||||
-- ordering by id keeps pagination tie-break deterministic.
|
||||
SELECT sqlc.embed(albums), artists.name AS artist_name
|
||||
FROM albums
|
||||
JOIN artists ON artists.id = albums.artist_id
|
||||
ORDER BY albums.created_at DESC, albums.id
|
||||
LIMIT $1;
|
||||
|
||||
@@ -201,3 +201,55 @@ FROM contributions
|
||||
GROUP BY candidate_mbid, candidate_name
|
||||
ORDER BY total_score DESC
|
||||
LIMIT $3;
|
||||
|
||||
-- name: ListMostPlayedTracksForUser :many
|
||||
-- M6a: top-N tracks by completed-play count for the user. was_skipped
|
||||
-- excludes skips so a user spamming next doesn't fabricate a top track.
|
||||
-- Quarantined tracks (per-user soft-hide from M5b) are filtered out.
|
||||
SELECT sqlc.embed(t),
|
||||
albums.title AS album_title,
|
||||
artists.name AS artist_name
|
||||
FROM tracks t
|
||||
JOIN albums ON albums.id = t.album_id
|
||||
JOIN artists ON artists.id = t.artist_id
|
||||
JOIN play_events pe ON pe.track_id = t.id
|
||||
WHERE pe.user_id = $1
|
||||
AND pe.was_skipped = false
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM lidarr_quarantine q
|
||||
WHERE q.user_id = $1 AND q.track_id = t.id
|
||||
)
|
||||
GROUP BY t.id, t.title, t.album_id, t.artist_id, t.duration_ms, t.file_path,
|
||||
t.file_format, t.file_size, t.bitrate, t.track_number, t.disc_number,
|
||||
t.mbid, t.genre, t.added_at, t.updated_at,
|
||||
albums.title, artists.name
|
||||
ORDER BY count(*) DESC, t.id
|
||||
LIMIT $2;
|
||||
|
||||
-- name: ListLastPlayedArtistsForUser :many
|
||||
-- M6a: artists ranked by max(play_events.started_at) for the user, with
|
||||
-- a derived cover_album_id via a representative-album lateral join (most
|
||||
-- recent album that has cover_art_path set). album_count joined for the
|
||||
-- ArtistRef wire shape.
|
||||
SELECT sqlc.embed(a),
|
||||
cov.id AS cover_album_id,
|
||||
cnt.album_count::bigint AS album_count,
|
||||
max_started.started_at::timestamptz AS last_played_at
|
||||
FROM artists a
|
||||
JOIN LATERAL (
|
||||
SELECT max(pe.started_at) AS started_at
|
||||
FROM play_events pe
|
||||
JOIN tracks t ON t.id = pe.track_id
|
||||
WHERE pe.user_id = $1 AND t.artist_id = a.id
|
||||
) max_started ON max_started.started_at IS NOT NULL
|
||||
LEFT JOIN LATERAL (
|
||||
SELECT id FROM albums
|
||||
WHERE artist_id = a.id AND cover_art_path IS NOT NULL
|
||||
ORDER BY created_at DESC LIMIT 1
|
||||
) cov ON true
|
||||
LEFT JOIN LATERAL (
|
||||
SELECT count(*) AS album_count
|
||||
FROM albums WHERE artist_id = a.id
|
||||
) cnt ON true
|
||||
ORDER BY max_started.started_at DESC, a.id
|
||||
LIMIT $2;
|
||||
|
||||
Reference in New Issue
Block a user