artist.go (6375B)
1 package db 2 3 import ( 4 "fmt" 5 "lyrics/internal/domain" 6 "strings" 7 "time" 8 ) 9 10 func Artist(e Execer, id domain.ArtistID) (domain.Artist, []domain.Release, []domain.Song, []domain.Song, error) { 11 var artist domain.Artist 12 13 err := e.QueryRow(` 14 select id, musicbrainz_artist_id, name, latin_name, disambiguation, bio 15 from artists 16 where id = ? 17 `, id).Scan(&artist.ID, &artist.MusicBrainzArtistID, &artist.Name, &artist.LatinName, &artist.Disambiguation, &artist.Bio) 18 if err != nil { 19 return domain.Artist{}, nil, nil, nil, fmt.Errorf("failed to get artist: %w", err) 20 } 21 22 var releaseIDs []domain.ReleaseID 23 { 24 // fetch releases where artist is primary, ordered by release_date 25 rows, err := e.Query(` 26 select release_id 27 from release_artists 28 where artist_id = ? 29 and role = ? 30 order by (select release_date from releases where id = release_id) 31 `, id, domain.RolePrimary) 32 if err != nil { 33 return domain.Artist{}, nil, nil, nil, fmt.Errorf("failed to query artist releases: %w", err) 34 } 35 36 for rows.Next() { 37 var releaseID domain.ReleaseID 38 if err := rows.Scan(&releaseID); err != nil { 39 _ = rows.Close() 40 return domain.Artist{}, nil, nil, nil, fmt.Errorf("failed to scan release id: %w", err) 41 } 42 releaseIDs = append(releaseIDs, releaseID) 43 } 44 if err := rows.Err(); err != nil { 45 _ = rows.Close() 46 return domain.Artist{}, nil, nil, nil, fmt.Errorf("failed to iterate artist releases: %w", err) 47 } 48 _ = rows.Close() 49 } 50 51 releases := make([]domain.Release, 0, len(releaseIDs)) 52 for _, releaseID := range releaseIDs { 53 release, err := Release(e, releaseID) 54 if err != nil { 55 return domain.Artist{}, nil, nil, nil, fmt.Errorf("failed to get release %d: %w", releaseID, err) 56 } 57 releases = append(releases, release) 58 } 59 60 var singleIDs []domain.SongID 61 { 62 // fetch primary singles (primary role, no release) ordered by release_date 63 rows, err := e.Query(` 64 select sa.song_id 65 from song_artists sa 66 join songs s on s.id = sa.song_id 67 where sa.artist_id = ? 68 and sa.role = ? 69 and not exists (select 1 from song_releases where song_id = sa.song_id) 70 order by s.release_date 71 `, id, domain.RolePrimary) 72 if err != nil { 73 return domain.Artist{}, nil, nil, nil, fmt.Errorf("failed to query artist singles: %w", err) 74 } 75 76 for rows.Next() { 77 var songID domain.SongID 78 if err := rows.Scan(&songID); err != nil { 79 _ = rows.Close() 80 return domain.Artist{}, nil, nil, nil, fmt.Errorf("failed to scan single id: %w", err) 81 } 82 singleIDs = append(singleIDs, songID) 83 } 84 if err := rows.Err(); err != nil { 85 _ = rows.Close() 86 return domain.Artist{}, nil, nil, nil, fmt.Errorf("failed to iterate artist singles: %w", err) 87 } 88 _ = rows.Close() 89 } 90 91 singles := make([]domain.Song, 0, len(singleIDs)) 92 for _, songID := range singleIDs { 93 song, err := Song(e, songID) 94 if err != nil { 95 return domain.Artist{}, nil, nil, nil, fmt.Errorf("failed to get single %d: %w", songID, err) 96 } 97 singles = append(singles, song) 98 } 99 100 var featuredIDs []domain.SongID 101 { 102 // fetch featured songs (featured role, regardless of release) ordered by release_date 103 rows, err := e.Query(` 104 select sa.song_id 105 from song_artists sa 106 join songs s on s.id = sa.song_id 107 where sa.artist_id = ? 108 and sa.role = ? 109 order by s.release_date 110 `, id, domain.RoleFeatured) 111 if err != nil { 112 return domain.Artist{}, nil, nil, nil, fmt.Errorf("failed to query featured songs: %w", err) 113 } 114 115 for rows.Next() { 116 var songID domain.SongID 117 if err := rows.Scan(&songID); err != nil { 118 _ = rows.Close() 119 return domain.Artist{}, nil, nil, nil, fmt.Errorf("failed to scan featured song id: %w", err) 120 } 121 featuredIDs = append(featuredIDs, songID) 122 } 123 if err := rows.Err(); err != nil { 124 _ = rows.Close() 125 return domain.Artist{}, nil, nil, nil, fmt.Errorf("failed to iterate featured songs: %w", err) 126 } 127 _ = rows.Close() 128 } 129 130 featured := make([]domain.Song, 0, len(featuredIDs)) 131 for _, songID := range featuredIDs { 132 song, err := Song(e, songID) 133 if err != nil { 134 return domain.Artist{}, nil, nil, nil, fmt.Errorf("failed to get featured song %d: %w", songID, err) 135 } 136 featured = append(featured, song) 137 } 138 139 return artist, releases, singles, featured, nil 140 } 141 142 func InsertArtist(e Execer, artist domain.Artist) error { 143 _, err := e.Exec(` 144 insert into artists ( 145 musicbrainz_artist_id, 146 name, 147 latin_name, 148 disambiguation, 149 bio, 150 created_at 151 ) 152 values (?, ?, ?, ?, ?, ?) 153 `, artist.MusicBrainzArtistID, artist.Name, artist.LatinName, artist.Disambiguation, artist.Bio, time.Now().Format(time.RFC3339)) 154 if err != nil { 155 return fmt.Errorf("failed to insert artist: %w", err) 156 } 157 158 return nil 159 } 160 161 func UpdateArtist(e Execer, artist domain.Artist) error { 162 _, err := e.Exec(` 163 update artists 164 set 165 musicbrainz_artist_id = ?, 166 name = ?, 167 latin_name = ?, 168 disambiguation = ?, 169 bio = ? 170 where id = ? 171 `, artist.MusicBrainzArtistID, artist.Name, artist.LatinName, artist.Disambiguation, artist.Bio, artist.ID) 172 if err != nil { 173 return fmt.Errorf("failed to update artist: %w", err) 174 } 175 176 return nil 177 } 178 179 func SearchArtist(e Execer, artistName string, limit, offset int64) (int, []domain.Artist, error) { 180 artistName = strings.TrimSpace(artistName) 181 if artistName == "" { 182 return 0, nil, nil 183 } 184 185 wildcard := "%" + artistName + "%" 186 187 var totalRows int 188 err := e.QueryRow(` 189 select count(*) 190 from artists 191 where name like ? collate nocase 192 or latin_name like ? collate nocase 193 `, wildcard, wildcard).Scan(&totalRows) 194 if err != nil { 195 return 0, nil, fmt.Errorf("failed to count total artists: %w", err) 196 } 197 198 rows, err := e.Query(` 199 select id, musicbrainz_artist_id, name, latin_name, disambiguation, bio 200 from artists 201 where name like ? collate nocase 202 or latin_name like ? collate nocase 203 order by name 204 limit ? offset ? 205 `, wildcard, wildcard, limit, offset) 206 if err != nil { 207 return 0, nil, fmt.Errorf("failed to query artists: %w", err) 208 } 209 defer func() { 210 _ = rows.Close() 211 }() 212 213 var results []domain.Artist 214 for rows.Next() { 215 var artist domain.Artist 216 if err := rows.Scan(&artist.ID, &artist.MusicBrainzArtistID, &artist.Name, &artist.LatinName, &artist.Disambiguation, &artist.Bio); err != nil { 217 return 0, nil, fmt.Errorf("failed to scan artist row: %w", err) 218 } 219 results = append(results, artist) 220 } 221 222 return totalRows, results, rows.Err() 223 }