lyrics

lyrics
git clone git://brookjeynes.dev/bjeynes/lyrics.git
Log | Files | Refs

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 }