diff --git a/db/entities.go b/db/entities.go index 8278cf4..0862edf 100644 --- a/db/entities.go +++ b/db/entities.go @@ -394,6 +394,58 @@ func GetSongByName(userId int, title string, artistId int) (Song, error) { return song, nil } +func GetSongsByName(userId int, title string, artistId int) ([]Song, error) { + var query string + var args []interface{} + if artistId > 0 { + query = `SELECT id, user_id, title, artist_id, album_id, duration_ms, spotify_id, musicbrainz_id + FROM songs WHERE user_id = $1 AND title = $2 AND artist_id = $3 ORDER BY id` + args = []interface{}{userId, title, artistId} + } else { + query = `SELECT id, user_id, title, artist_id, album_id, duration_ms, spotify_id, musicbrainz_id + FROM songs WHERE user_id = $1 AND title = $2 ORDER BY id` + args = []interface{}{userId, title} + } + + rows, err := Pool.Query(context.Background(), query, args...) + if err != nil { + return nil, err + } + defer rows.Close() + + var songs []Song + for rows.Next() { + var song Song + var artistIdVal, albumIdVal pgtype.Int4 + var durationMs *int + var spotifyIdPg, musicbrainzIdPg pgtype.Text + + err := rows.Scan( + &song.Id, &song.UserId, &song.Title, &artistIdVal, &albumIdVal, + &durationMs, &spotifyIdPg, &musicbrainzIdPg) + if err != nil { + return nil, err + } + if artistIdVal.Status == pgtype.Present { + song.ArtistId = int(artistIdVal.Int) + } + if albumIdVal.Status == pgtype.Present { + song.AlbumId = int(albumIdVal.Int) + } + if durationMs != nil { + song.DurationMs = *durationMs + } + if spotifyIdPg.Status == pgtype.Present { + song.SpotifyId = spotifyIdPg.String + } + if musicbrainzIdPg.Status == pgtype.Present { + song.MusicbrainzId = musicbrainzIdPg.String + } + songs = append(songs, song) + } + return songs, nil +} + func UpdateSong(id int, title string, durationMs int, spotifyId, musicbrainzId string) error { _, err := Pool.Exec(context.Background(), `UPDATE songs SET title = $1, duration_ms = $2, spotify_id = $3, musicbrainz_id = $4 WHERE id = $5`, @@ -673,3 +725,41 @@ func GetHistoryForAlbum(userId, albumId int, limit, offset int) ([]ScrobbleEntry } return entries, nil } + +func GetSongStatsForSongs(userId int, songIds []int) (int, error) { + if len(songIds) == 0 { + return 0, nil + } + var count int + err := Pool.QueryRow(context.Background(), + "SELECT COUNT(*) FROM history WHERE user_id = $1 AND song_id = ANY($2)", + userId, songIds).Scan(&count) + return count, err +} + +func GetHistoryForSongs(userId int, songIds []int, limit, offset int) ([]ScrobbleEntry, error) { + if len(songIds) == 0 { + return []ScrobbleEntry{}, nil + } + rows, err := Pool.Query(context.Background(), + `SELECT h.timestamp, h.song_name, h.album_name, h.ms_played, h.platform, + (SELECT name FROM artists WHERE id = h.artist_id) as artist_name + FROM history h WHERE h.user_id = $1 AND h.song_id = ANY($2) + ORDER BY h.timestamp DESC LIMIT $3 OFFSET $4`, + userId, songIds, limit, offset) + if err != nil { + return nil, err + } + defer rows.Close() + + var entries []ScrobbleEntry + for rows.Next() { + var e ScrobbleEntry + err := rows.Scan(&e.Timestamp, &e.SongName, &e.AlbumName, &e.MsPlayed, &e.Platform, &e.ArtistName) + if err != nil { + return nil, err + } + entries = append(entries, e) + } + return entries, nil +} diff --git a/templates/artist.gohtml b/templates/artist.gohtml index cb27d7a..88628cf 100644 --- a/templates/artist.gohtml +++ b/templates/artist.gohtml @@ -33,7 +33,7 @@ {{.ArtistName}} {{.SongName}} - {{.AlbumName}} + {{.AlbumName}} {{formatTimestamp .Timestamp}} {{end}} diff --git a/templates/song.gohtml b/templates/song.gohtml index 8a61f22..df57b40 100644 --- a/templates/song.gohtml +++ b/templates/song.gohtml @@ -10,8 +10,8 @@ {{if .Artist.Name}}

{{.Artist.Name}}

{{end}} - {{if .Album.Title}} -

{{.Album.Title}}

+ {{range .Albums}} +

{{.Title}}

{{end}}
@@ -34,7 +34,7 @@ {{.ArtistName}} {{.SongName}} - {{.AlbumName}} + {{.AlbumName}} {{formatTimestamp .Timestamp}} {{end}} diff --git a/web/entity.go b/web/entity.go index 7c23f45..042d1ca 100644 --- a/web/entity.go +++ b/web/entity.go @@ -35,7 +35,7 @@ type SongData struct { Username string Song db.Song Artist db.Artist - Album db.Album + Albums []db.Album ListenCount int Times []db.ScrobbleEntry Page int @@ -151,11 +151,11 @@ func songPageHandler() http.HandlerFunc { return } - song, err := db.GetSongByName(userId, songTitle, artist.Id) - if err != nil { - songs, _, searchErr := db.SearchSongs(userId, songTitle) - if searchErr == nil && len(songs) > 0 { - song = songs[0] + songs, err := db.GetSongsByName(userId, songTitle, artist.Id) + if err != nil || len(songs) == 0 { + songList, _, searchErr := db.SearchSongs(userId, songTitle) + if searchErr == nil && len(songList) > 0 { + songs = songList } else { fmt.Fprintf(os.Stderr, "Cannot find song %s: %v\n", songTitle, err) http.Error(w, "Song not found", http.StatusNotFound) @@ -163,10 +163,19 @@ func songPageHandler() http.HandlerFunc { } } + song := songs[0] artist, _ = db.GetArtistById(song.ArtistId) - var album db.Album - if song.AlbumId > 0 { - album, _ = db.GetAlbumById(song.AlbumId) + + var songIds []int + var albums []db.Album + seenAlbums := make(map[int]bool) + for _, s := range songs { + songIds = append(songIds, s.Id) + if s.AlbumId > 0 && !seenAlbums[s.AlbumId] { + seenAlbums[s.AlbumId] = true + album, _ := db.GetAlbumById(s.AlbumId) + albums = append(albums, album) + } } pageStr := r.URL.Query().Get("page") @@ -183,12 +192,12 @@ func songPageHandler() http.HandlerFunc { lim := 15 off := (pageInt - 1) * lim - listenCount, err := db.GetSongStats(userId, song.Id) + listenCount, err := db.GetSongStatsForSongs(userId, songIds) if err != nil { fmt.Fprintf(os.Stderr, "Cannot get song stats: %v\n", err) } - entries, err := db.GetHistoryForSong(userId, song.Id, lim, off) + entries, err := db.GetHistoryForSongs(userId, songIds, lim, off) if err != nil { fmt.Fprintf(os.Stderr, "Cannot get history for song: %v\n", err) http.Error(w, err.Error(), http.StatusInternalServerError) @@ -199,7 +208,7 @@ func songPageHandler() http.HandlerFunc { Username: username, Song: song, Artist: artist, - Album: album, + Albums: albums, ListenCount: listenCount, Times: entries, Page: pageInt,