From 369aae818c43ebeae5aaad7a538fdd065442c0d8 Mon Sep 17 00:00:00 2001 From: riwiwa Date: Sun, 1 Mar 2026 17:18:19 -0800 Subject: [PATCH] add multi-artist support for comma separated artists --- db/db.go | 4 ++- db/entities.go | 25 +++++++++------- migrate/lastfm.go | 61 ++++++++++++++++++++++++++++++++++------ migrate/spotify.go | 58 ++++++++++++++++++++++++++++++++++++++ scrobble/scrobble.go | 47 ++++++++++++++++++++++++++----- static/style.css | 2 ++ templates/album.gohtml | 11 ++++++-- templates/artist.gohtml | 5 +++- templates/profile.gohtml | 10 +++++-- templates/song.gohtml | 11 ++++++-- web/entity.go | 48 +++++++++++++++++++++++++++++++ web/profile.go | 21 +++++++++++--- web/utils.go | 17 +++++++++++ web/web.go | 1 + 14 files changed, 281 insertions(+), 40 deletions(-) diff --git a/db/db.go b/db/db.go index 5f4f92d..c67b056 100644 --- a/db/db.go +++ b/db/db.go @@ -250,8 +250,10 @@ func AddHistoryEntityColumns() error { _, err := Pool.Exec(context.Background(), `ALTER TABLE history ADD COLUMN IF NOT EXISTS artist_id INTEGER REFERENCES artists(id) ON DELETE SET NULL; ALTER TABLE history ADD COLUMN IF NOT EXISTS song_id INTEGER REFERENCES songs(id) ON DELETE SET NULL; + ALTER TABLE history ADD COLUMN IF NOT EXISTS artist_ids INTEGER[] DEFAULT '{}'; CREATE INDEX IF NOT EXISTS idx_history_artist_id ON history(artist_id); - CREATE INDEX IF NOT EXISTS idx_history_song_id ON history(song_id);`) + CREATE INDEX IF NOT EXISTS idx_history_song_id ON history(song_id); + CREATE INDEX IF NOT EXISTS idx_history_artist_ids ON history USING gin(artist_ids);`) if err != nil { fmt.Fprintf(os.Stderr, "Error adding history entity columns: %v\n", err) return err diff --git a/db/entities.go b/db/entities.go index 0862edf..c095631 100644 --- a/db/entities.go +++ b/db/entities.go @@ -499,7 +499,7 @@ func SearchSongs(userId int, query string) ([]Song, float64, error) { func GetArtistStats(userId, artistId int) (int, error) { var count int err := Pool.QueryRow(context.Background(), - "SELECT COUNT(*) FROM history WHERE user_id = $1 AND artist_id = $2", + "SELECT COUNT(*) FROM history WHERE user_id = $1 AND $2 = ANY(artist_ids)", userId, artistId).Scan(&count) return count, err } @@ -534,8 +534,9 @@ func MergeArtists(userId int, fromArtistId, toArtistId int) error { func GetHistoryForArtist(userId, artistId int, limit, offset int) ([]ScrobbleEntry, error) { 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.artist_id = $2 + (SELECT name FROM artists WHERE id = h.artist_id) as artist_name, + h.artist_ids + FROM history h WHERE h.user_id = $1 AND $2 = ANY(h.artist_ids) ORDER BY h.timestamp DESC LIMIT $3 OFFSET $4`, userId, artistId, limit, offset) if err != nil { @@ -546,7 +547,7 @@ func GetHistoryForArtist(userId, artistId int, limit, offset int) ([]ScrobbleEnt var entries []ScrobbleEntry for rows.Next() { var e ScrobbleEntry - err := rows.Scan(&e.Timestamp, &e.SongName, &e.AlbumName, &e.MsPlayed, &e.Platform, &e.ArtistName) + err := rows.Scan(&e.Timestamp, &e.SongName, &e.AlbumName, &e.MsPlayed, &e.Platform, &e.ArtistName, &e.ArtistIds) if err != nil { return nil, err } @@ -558,7 +559,8 @@ func GetHistoryForArtist(userId, artistId int, limit, offset int) ([]ScrobbleEnt func GetHistoryForSong(userId, songId int, limit, offset int) ([]ScrobbleEntry, error) { 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 + (SELECT name FROM artists WHERE id = h.artist_id) as artist_name, + h.artist_ids FROM history h WHERE h.user_id = $1 AND h.song_id = $2 ORDER BY h.timestamp DESC LIMIT $3 OFFSET $4`, userId, songId, limit, offset) @@ -570,7 +572,7 @@ func GetHistoryForSong(userId, songId int, limit, offset int) ([]ScrobbleEntry, var entries []ScrobbleEntry for rows.Next() { var e ScrobbleEntry - err := rows.Scan(&e.Timestamp, &e.SongName, &e.AlbumName, &e.MsPlayed, &e.Platform, &e.ArtistName) + err := rows.Scan(&e.Timestamp, &e.SongName, &e.AlbumName, &e.MsPlayed, &e.Platform, &e.ArtistName, &e.ArtistIds) if err != nil { return nil, err } @@ -586,6 +588,7 @@ type ScrobbleEntry struct { AlbumName string MsPlayed int Platform string + ArtistIds []int } func MigrateHistoryEntities() error { @@ -703,7 +706,8 @@ func GetAlbumStats(userId, albumId int) (int, error) { func GetHistoryForAlbum(userId, albumId int, limit, offset int) ([]ScrobbleEntry, error) { 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 + (SELECT name FROM artists WHERE id = h.artist_id) as artist_name, + h.artist_ids FROM history h JOIN songs s ON h.song_id = s.id WHERE h.user_id = $1 AND s.album_id = $2 @@ -717,7 +721,7 @@ func GetHistoryForAlbum(userId, albumId int, limit, offset int) ([]ScrobbleEntry var entries []ScrobbleEntry for rows.Next() { var e ScrobbleEntry - err := rows.Scan(&e.Timestamp, &e.SongName, &e.AlbumName, &e.MsPlayed, &e.Platform, &e.ArtistName) + err := rows.Scan(&e.Timestamp, &e.SongName, &e.AlbumName, &e.MsPlayed, &e.Platform, &e.ArtistName, &e.ArtistIds) if err != nil { return nil, err } @@ -743,7 +747,8 @@ func GetHistoryForSongs(userId int, songIds []int, limit, offset int) ([]Scrobbl } 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 + (SELECT name FROM artists WHERE id = h.artist_id) as artist_name, + h.artist_ids 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) @@ -755,7 +760,7 @@ func GetHistoryForSongs(userId int, songIds []int, limit, offset int) ([]Scrobbl var entries []ScrobbleEntry for rows.Next() { var e ScrobbleEntry - err := rows.Scan(&e.Timestamp, &e.SongName, &e.AlbumName, &e.MsPlayed, &e.Platform, &e.ArtistName) + err := rows.Scan(&e.Timestamp, &e.SongName, &e.AlbumName, &e.MsPlayed, &e.Platform, &e.ArtistName, &e.ArtistIds) if err != nil { return nil, err } diff --git a/migrate/lastfm.go b/migrate/lastfm.go index 70bca85..91bf713 100644 --- a/migrate/lastfm.go +++ b/migrate/lastfm.go @@ -236,20 +236,65 @@ func ImportLastFM( } func insertBatch(tracks []LastFMTrack, totalImported *int) error { + if len(tracks) == 0 { + return nil + } + + artistIdMap, err := resolveLastFMArtistIds(tracks) + if err != nil { + fmt.Fprintf(os.Stderr, "Error resolving artist IDs: %v\n", err) + return err + } + + rows := make([][]any, 0, len(tracks)) + for _, t := range tracks { + artistNames := parseArtistString(t.Artist) + var artistIds []int + for _, name := range artistNames { + if ids, ok := artistIdMap[name]; ok { + artistIds = append(artistIds, ids...) + } + } + + primaryArtistId := 0 + if len(artistIds) > 0 { + primaryArtistId = artistIds[0] + } + + rows = append(rows, []any{ + t.UserId, t.Timestamp, t.SongName, t.Artist, + t.Album, 0, "lastfm", primaryArtistId, artistIds, + }) + } + copyCount, err := db.Pool.CopyFrom(context.Background(), pgx.Identifier{"history"}, []string{ "user_id", "timestamp", "song_name", "artist", "album_name", - "ms_played", "platform", + "ms_played", "platform", "artist_id", "artist_ids", }, - pgx.CopyFromSlice(len(tracks), func(i int) ([]any, error) { - t := tracks[i] - return []any{ - t.UserId, t.Timestamp, t.SongName, t.Artist, - t.Album, 0, "lastfm", - }, nil - }), + pgx.CopyFromRows(rows), ) *totalImported += int(copyCount) return err } + +func resolveLastFMArtistIds(tracks []LastFMTrack) (map[string][]int, error) { + artistIdMap := make(map[string][]int) + + for _, t := range tracks { + artistNames := parseArtistString(t.Artist) + for _, name := range artistNames { + if _, exists := artistIdMap[name]; !exists { + artistId, _, err := db.GetOrCreateArtist(t.UserId, name) + if err != nil { + fmt.Fprintf(os.Stderr, "Error creating artist %s: %v\n", name, err) + continue + } + artistIdMap[name] = []int{artistId} + } + } + } + + return artistIdMap, nil +} diff --git a/migrate/spotify.go b/migrate/spotify.go index 696ad16..ff3390d 100644 --- a/migrate/spotify.go +++ b/migrate/spotify.go @@ -43,6 +43,7 @@ type trackSource struct { tracksToSkip map[string]struct{} // Set of duplicate keys to skip idx int // Current position in tracks slice userId int // User ID to associate with imported tracks + artistIdMap map[string][]int // Map of track key to artist IDs } // Represents a track already stored in the database, used for duplicate @@ -107,11 +108,19 @@ func ImportSpotify(tracks []SpotifyTrack, continue } + artistIdMap, err := resolveArtistIds(userId, validTracks) + if err != nil { + fmt.Fprintf(os.Stderr, "Error resolving artist IDs: %v\n", err) + batchStart += batchSize + continue + } + src := &trackSource{ tracks: validTracks, tracksToSkip: tracksToSkip, idx: 0, userId: userId, + artistIdMap: artistIdMap, } copyCount, err := db.Pool.CopyFrom( @@ -125,6 +134,8 @@ func ImportSpotify(tracks []SpotifyTrack, "album_name", "ms_played", "platform", + "artist_id", + "artist_ids", }, src, ) @@ -218,6 +229,43 @@ func getDupes(userId int, tracks []SpotifyTrack) (map[string]struct{}, error) { return duplicates, nil } +func resolveArtistIds(userId int, tracks []SpotifyTrack) (map[string][]int, error) { + artistIdMap := make(map[string][]int) + + for _, track := range tracks { + trackKey := createTrackKey(track) + artistNames := parseArtistString(track.Artist) + + var artistIds []int + for _, name := range artistNames { + artistId, _, err := db.GetOrCreateArtist(userId, name) + if err != nil { + fmt.Fprintf(os.Stderr, "Error creating artist %s: %v\n", name, err) + continue + } + artistIds = append(artistIds, artistId) + } + + artistIdMap[trackKey] = artistIds + } + + return artistIdMap, nil +} + +func parseArtistString(artist string) []string { + if artist == "" { + return nil + } + var artists []string + for _, a := range strings.Split(artist, ",") { + a = strings.TrimSpace(a) + if a != "" { + artists = append(artists, a) + } + } + return artists +} + // Get the min/max timestamp range for a batch of tracks func findTimeRange(tracks []SpotifyTrack) (time.Time, time.Time) { var minTs, maxTs time.Time @@ -319,6 +367,14 @@ func (s *trackSource) Next() bool { func (s *trackSource) Values() ([]any, error) { // idx is already incremented in Next(), so use idx-1 t := s.tracks[s.idx-1] + trackKey := createTrackKey(t) + artistIds := s.artistIdMap[trackKey] + + primaryArtistId := 0 + if len(artistIds) > 0 { + primaryArtistId = artistIds[0] + } + return []any{ s.userId, t.Timestamp, @@ -327,6 +383,8 @@ func (s *trackSource) Values() ([]any, error) { t.Album, t.Played, "spotify", + primaryArtistId, + artistIds, }, nil } diff --git a/scrobble/scrobble.go b/scrobble/scrobble.go index 85c06b0..825fb36 100644 --- a/scrobble/scrobble.go +++ b/scrobble/scrobble.go @@ -6,6 +6,7 @@ import ( "encoding/hex" "fmt" "os" + "strings" "time" "muzi/db" @@ -118,33 +119,38 @@ func SaveScrobble(scrobble Scrobble) error { return fmt.Errorf("duplicate scrobble") } - artistId, _, err := db.GetOrCreateArtist(scrobble.UserId, scrobble.Artist) + artistNames := parseArtistString(scrobble.Artist) + artistIds, err := getOrCreateArtists(scrobble.UserId, artistNames) if err != nil { - fmt.Fprintf(os.Stderr, "Error getting/creating artist: %v\n", err) return err } + primaryArtistId := 0 + if len(artistIds) > 0 { + primaryArtistId = artistIds[0] + } + var albumId int if scrobble.Album != "" { - albumId, _, err = db.GetOrCreateAlbum(scrobble.UserId, scrobble.Album, artistId) + albumId, _, err = db.GetOrCreateAlbum(scrobble.UserId, scrobble.Album, primaryArtistId) if err != nil { fmt.Fprintf(os.Stderr, "Error getting/creating album: %v\n", err) return err } } - songId, _, err := db.GetOrCreateSong(scrobble.UserId, scrobble.SongName, artistId, albumId) + songId, _, err := db.GetOrCreateSong(scrobble.UserId, scrobble.SongName, primaryArtistId, albumId) if err != nil { fmt.Fprintf(os.Stderr, "Error getting/creating song: %v\n", err) return err } _, err = db.Pool.Exec(context.Background(), - `INSERT INTO history (user_id, timestamp, song_name, artist, album_name, ms_played, platform, artist_id, song_id) - VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) + `INSERT INTO history (user_id, timestamp, song_name, artist, album_name, ms_played, platform, artist_id, song_id, artist_ids) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) ON CONFLICT (user_id, song_name, artist, timestamp) DO NOTHING`, scrobble.UserId, scrobble.Timestamp, scrobble.SongName, scrobble.Artist, - scrobble.Album, scrobble.MsPlayed, scrobble.Platform, artistId, songId) + scrobble.Album, scrobble.MsPlayed, scrobble.Platform, primaryArtistId, songId, artistIds) if err != nil { fmt.Fprintf(os.Stderr, "Error saving scrobble: %v\n", err) return err @@ -152,6 +158,33 @@ func SaveScrobble(scrobble Scrobble) error { return nil } +func parseArtistString(artist string) []string { + if artist == "" { + return nil + } + var artists []string + for _, a := range strings.Split(artist, ",") { + a = strings.TrimSpace(a) + if a != "" { + artists = append(artists, a) + } + } + return artists +} + +func getOrCreateArtists(userId int, artistNames []string) ([]int, error) { + var artistIds []int + for _, name := range artistNames { + id, _, err := db.GetOrCreateArtist(userId, name) + if err != nil { + fmt.Fprintf(os.Stderr, "Error getting/creating artist: %v\n", err) + return nil, err + } + artistIds = append(artistIds, id) + } + return artistIds, nil +} + func SaveScrobbles(scrobbles []Scrobble) (int, int, error) { if len(scrobbles) == 0 { return 0, 0, nil diff --git a/static/style.css b/static/style.css index f2d23ba..da2be00 100644 --- a/static/style.css +++ b/static/style.css @@ -334,6 +334,7 @@ justify-content: center; margin: 5px; width: 30%; + white-space: pre-wrap; } tr:nth-child(even) { background-color: #111; @@ -341,6 +342,7 @@ a { color: #AFA; text-decoration: none; + white-space: pre-wrap; } a:hover { color: #FFF; diff --git a/templates/album.gohtml b/templates/album.gohtml index f95b8d9..f1c8235 100644 --- a/templates/album.gohtml +++ b/templates/album.gohtml @@ -12,8 +12,10 @@ {{end}} - {{if .Artist.Name}} -

{{.Artist.Name}}

+ {{if .ArtistNames}} +

+ {{- range $i, $name := .ArtistNames}}{{if $i}}, {{end}}{{$name}}{{end}} +

{{end}}
@@ -34,7 +36,10 @@ {{$username := .Username}} {{range .Times}} - {{.ArtistName}} + + {{- $artistNames := getArtistNames .ArtistIds}} + {{- range $i, $name := $artistNames}}{{if $i}}, {{end}}{{$name}}{{end}} + {{.SongName}} {{.AlbumName}} {{formatTimestamp .Timestamp}} diff --git a/templates/artist.gohtml b/templates/artist.gohtml index 88628cf..e3b5f5e 100644 --- a/templates/artist.gohtml +++ b/templates/artist.gohtml @@ -31,7 +31,10 @@ {{$username := .Username}} {{range .Times}} - {{.ArtistName}} + + {{- $artistNames := getArtistNames .ArtistIds}} + {{- range $i, $name := $artistNames}}{{if $i}}, {{end}}{{$name}}{{end}} + {{.SongName}} {{.AlbumName}} {{formatTimestamp .Timestamp}} diff --git a/templates/profile.gohtml b/templates/profile.gohtml index 515c3cc..9ccbb6c 100644 --- a/templates/profile.gohtml +++ b/templates/profile.gohtml @@ -28,13 +28,17 @@ Now Playing {{end}} - {{$artists := .Artists}} + {{$artistIdsList := .ArtistIdsList}} {{$times := .Times}} {{$username := .Username}} {{range $index, $title := .Titles}} - {{index $artists $index}} - {{$title}} + + {{- $artistIds := index $artistIdsList $index}} + {{- $artistNames := getArtistNames $artistIds}} + {{- range $i, $name := $artistNames}}{{if $i}}, {{end}}{{$name}}{{end}} + + {{$title}} {{formatTimestamp (index $times $index)}} {{end}} diff --git a/templates/song.gohtml b/templates/song.gohtml index df57b40..af9c483 100644 --- a/templates/song.gohtml +++ b/templates/song.gohtml @@ -7,8 +7,10 @@ {{end}} - {{if .Artist.Name}} -

{{.Artist.Name}}

+ {{if .ArtistNames}} +

+ {{- range $i, $name := .ArtistNames}}{{if $i}}, {{end}}{{$name}}{{end}} +

{{end}} {{range .Albums}}

{{.Title}}

@@ -32,7 +34,10 @@ {{$username := .Username}} {{range .Times}} - {{.ArtistName}} + + {{- $artistNames := getArtistNames .ArtistIds}} + {{- range $i, $name := $artistNames}}{{if $i}}, {{end}}{{$name}}{{end}} + {{.SongName}} {{.AlbumName}} {{formatTimestamp .Timestamp}} diff --git a/web/entity.go b/web/entity.go index 1c12eb8..e24a17c 100644 --- a/web/entity.go +++ b/web/entity.go @@ -1,6 +1,7 @@ package web import ( + "context" "crypto/sha256" "encoding/hex" "encoding/json" @@ -35,6 +36,7 @@ type SongData struct { Username string Song db.Song Artist db.Artist + ArtistNames []string Albums []db.Album ListenCount int Times []db.ScrobbleEntry @@ -48,6 +50,7 @@ type AlbumData struct { Username string Album db.Album Artist db.Artist + ArtistNames []string ListenCount int Times []db.ScrobbleEntry Page int @@ -169,8 +172,14 @@ func songPageHandler() http.HandlerFunc { var songIds []int var albums []db.Album seenAlbums := make(map[int]bool) + seenArtistIds := make(map[int]bool) + var allArtistIds []int for _, s := range songs { songIds = append(songIds, s.Id) + if s.ArtistId > 0 && !seenArtistIds[s.ArtistId] { + seenArtistIds[s.ArtistId] = true + allArtistIds = append(allArtistIds, s.ArtistId) + } if s.AlbumId > 0 && !seenAlbums[s.AlbumId] { seenAlbums[s.AlbumId] = true album, _ := db.GetAlbumById(s.AlbumId) @@ -178,6 +187,29 @@ func songPageHandler() http.HandlerFunc { } } + var artistNames []string + seenArtistIdsMap := make(map[int]bool) + rows, err := db.Pool.Query(context.Background(), + `SELECT DISTINCT artist_ids FROM history WHERE song_id = ANY($1)`, + songIds) + if err == nil { + defer rows.Close() + for rows.Next() { + var artistIds []int + if err := rows.Scan(&artistIds); err == nil { + for _, id := range artistIds { + if !seenArtistIdsMap[id] { + seenArtistIdsMap[id] = true + a, err := db.GetArtistById(id) + if err == nil { + artistNames = append(artistNames, a.Name) + } + } + } + } + } + } + pageStr := r.URL.Query().Get("page") var pageInt int if pageStr == "" { @@ -208,6 +240,7 @@ func songPageHandler() http.HandlerFunc { Username: username, Song: song, Artist: artist, + ArtistNames: artistNames, Albums: albums, ListenCount: listenCount, Times: entries, @@ -375,10 +408,25 @@ func albumPageHandler() http.HandlerFunc { return } + var artistNames []string + seenArtistIds := make(map[int]bool) + for _, e := range entries { + for _, artistId := range e.ArtistIds { + if !seenArtistIds[artistId] { + seenArtistIds[artistId] = true + a, err := db.GetArtistById(artistId) + if err == nil { + artistNames = append(artistNames, a.Name) + } + } + } + } + albumData := AlbumData{ Username: username, Album: album, Artist: artist, + ArtistNames: artistNames, ListenCount: listenCount, Times: entries, Page: pageInt, diff --git a/web/profile.go b/web/profile.go index a1a9457..83fe7dd 100644 --- a/web/profile.go +++ b/web/profile.go @@ -25,6 +25,7 @@ type ProfileData struct { TrackCount int ArtistCount int Artists []string + ArtistIdsList [][]int Titles []string Times []time.Time Page int @@ -93,7 +94,7 @@ func profilePageHandler() http.HandlerFunc { rows, err := db.Pool.Query( r.Context(), - "SELECT artist, song_name, timestamp FROM history WHERE user_id = $1 ORDER BY timestamp DESC LIMIT $2 OFFSET $3;", + "SELECT artist_id, song_name, timestamp, artist_ids FROM history WHERE user_id = $1 ORDER BY timestamp DESC LIMIT $2 OFFSET $3;", userId, lim, off, @@ -106,15 +107,27 @@ func profilePageHandler() http.HandlerFunc { defer rows.Close() for rows.Next() { - var artist, title string + var artistId int + var title string var time pgtype.Timestamptz - err = rows.Scan(&artist, &title, &time) + var artistIds []int + err = rows.Scan(&artistId, &title, &time, &artistIds) if err != nil { fmt.Fprintf(os.Stderr, "Scanning history row failed: %v\n", err) http.Error(w, err.Error(), http.StatusInternalServerError) return } - profileData.Artists = append(profileData.Artists, artist) + + var artistName string + if artistId > 0 { + artist, err := db.GetArtistById(artistId) + if err == nil { + artistName = artist.Name + } + } + + profileData.Artists = append(profileData.Artists, artistName) + profileData.ArtistIdsList = append(profileData.ArtistIdsList, artistIds) profileData.Titles = append(profileData.Titles, title) profileData.Times = append(profileData.Times, time.Time) } diff --git a/web/utils.go b/web/utils.go index 004751a..7b53a53 100644 --- a/web/utils.go +++ b/web/utils.go @@ -5,6 +5,8 @@ package web import ( "fmt" "time" + + "muzi/db" ) // Subtracts two integers @@ -56,3 +58,18 @@ func formatTimestamp(timestamp time.Time) string { func formatTimestampFull(timestamp time.Time) string { return timestamp.Format("Monday 2 Jan 2006, 3:04pm") } + +// GetArtistNames takes artist IDs and returns a slice of artist names +func GetArtistNames(artistIds []int) []string { + if artistIds == nil { + return nil + } + var names []string + for _, id := range artistIds { + artist, err := db.GetArtistById(id) + if err == nil { + names = append(names, artist.Name) + } + } + return names +} diff --git a/web/web.go b/web/web.go index a73d601..f770589 100644 --- a/web/web.go +++ b/web/web.go @@ -37,6 +37,7 @@ func init() { "formatTimestamp": formatTimestamp, "formatTimestampFull": formatTimestampFull, "urlquery": url.QueryEscape, + "getArtistNames": GetArtistNames, } templates = template.Must(template.New("").Funcs(funcMap).ParseGlob("./templates/*.gohtml")) }