feat: render local artist images with no foreign key
This commit is contained in:
@@ -16,15 +16,6 @@ import (
|
|||||||
"go.senan.xyz/gonic/server/scrobble/listenbrainz"
|
"go.senan.xyz/gonic/server/scrobble/listenbrainz"
|
||||||
)
|
)
|
||||||
|
|
||||||
func firstExisting(or string, strings ...string) string {
|
|
||||||
for _, s := range strings {
|
|
||||||
if s != "" {
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return or
|
|
||||||
}
|
|
||||||
|
|
||||||
func doScan(scanner *scanner.Scanner, opts scanner.ScanOptions) {
|
func doScan(scanner *scanner.Scanner, opts scanner.ScanOptions) {
|
||||||
go func() {
|
go func() {
|
||||||
if err := scanner.ScanAndClean(opts); err != nil {
|
if err := scanner.ScanAndClean(opts); err != nil {
|
||||||
@@ -48,18 +39,7 @@ func (c *Controller) ServeHome(r *http.Request) *Response {
|
|||||||
c.DB.Model(&db.Album{}).Count(&data.AlbumCount)
|
c.DB.Model(&db.Album{}).Count(&data.AlbumCount)
|
||||||
c.DB.Table("tracks").Count(&data.TrackCount)
|
c.DB.Table("tracks").Count(&data.TrackCount)
|
||||||
// lastfm box
|
// lastfm box
|
||||||
scheme := firstExisting(
|
data.RequestRoot = c.BaseURL(r)
|
||||||
"http", // fallback
|
|
||||||
r.Header.Get("X-Forwarded-Proto"),
|
|
||||||
r.Header.Get("X-Forwarded-Scheme"),
|
|
||||||
r.URL.Scheme,
|
|
||||||
)
|
|
||||||
host := firstExisting(
|
|
||||||
"localhost:4747", // fallback
|
|
||||||
r.Header.Get("X-Forwarded-Host"),
|
|
||||||
r.Host,
|
|
||||||
)
|
|
||||||
data.RequestRoot = fmt.Sprintf("%s://%s", scheme, host)
|
|
||||||
data.CurrentLastFMAPIKey, _ = c.DB.GetSetting("lastfm_api_key")
|
data.CurrentLastFMAPIKey, _ = c.DB.GetSetting("lastfm_api_key")
|
||||||
data.DefaultListenBrainzURL = listenbrainz.BaseURL
|
data.DefaultListenBrainzURL = listenbrainz.BaseURL
|
||||||
// users box
|
// users box
|
||||||
|
|||||||
@@ -55,6 +55,21 @@ func (c *Controller) Path(rel string) string {
|
|||||||
return path.Join(c.ProxyPrefix, rel)
|
return path.Join(c.ProxyPrefix, rel)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Controller) BaseURL(r *http.Request) string {
|
||||||
|
scheme := firstExisting(
|
||||||
|
"http", // fallback
|
||||||
|
r.Header.Get("X-Forwarded-Proto"),
|
||||||
|
r.Header.Get("X-Forwarded-Scheme"),
|
||||||
|
r.URL.Scheme,
|
||||||
|
)
|
||||||
|
host := firstExisting(
|
||||||
|
"localhost:4747", // fallback
|
||||||
|
r.Header.Get("X-Forwarded-Host"),
|
||||||
|
r.Host,
|
||||||
|
)
|
||||||
|
return fmt.Sprintf("%s://%s", scheme, host)
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Controller) WithLogging(next http.Handler) http.Handler {
|
func (c *Controller) WithLogging(next http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
// this is (should be) the first middleware. pass right though it
|
// this is (should be) the first middleware. pass right though it
|
||||||
@@ -87,3 +102,12 @@ func (c *Controller) WithCORS(next http.Handler) http.Handler {
|
|||||||
next.ServeHTTP(w, r)
|
next.ServeHTTP(w, r)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func firstExisting(or string, strings ...string) string {
|
||||||
|
for _, s := range strings {
|
||||||
|
if s != "" {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return or
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/jinzhu/gorm"
|
"github.com/jinzhu/gorm"
|
||||||
@@ -248,12 +250,7 @@ func (c *Controller) ServeGetArtistInfoTwo(r *http.Request) *spec.Response {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return spec.NewError(10, "please provide an `id` parameter")
|
return spec.NewError(10, "please provide an `id` parameter")
|
||||||
}
|
}
|
||||||
apiKey, _ := c.DB.GetSetting("lastfm_api_key")
|
|
||||||
if apiKey == "" {
|
|
||||||
sub := spec.NewResponse()
|
|
||||||
sub.ArtistInfoTwo = &spec.ArtistInfo{}
|
|
||||||
return sub
|
|
||||||
}
|
|
||||||
artist := &db.Artist{}
|
artist := &db.Artist{}
|
||||||
err = c.DB.
|
err = c.DB.
|
||||||
Where("id=?", id.Value).
|
Where("id=?", id.Value).
|
||||||
@@ -262,26 +259,41 @@ func (c *Controller) ServeGetArtistInfoTwo(r *http.Request) *spec.Response {
|
|||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
return spec.NewError(70, "artist with id `%s` not found", id)
|
return spec.NewError(70, "artist with id `%s` not found", id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sub := spec.NewResponse()
|
||||||
|
sub.ArtistInfoTwo = &spec.ArtistInfo{}
|
||||||
|
if artist.Cover != "" {
|
||||||
|
sub.ArtistInfoTwo.SmallImageURL = c.genArtistCoverURL(r, artist, 64)
|
||||||
|
sub.ArtistInfoTwo.MediumImageURL = c.genArtistCoverURL(r, artist, 126)
|
||||||
|
sub.ArtistInfoTwo.LargeImageURL = c.genArtistCoverURL(r, artist, 256)
|
||||||
|
}
|
||||||
|
|
||||||
|
apiKey, _ := c.DB.GetSetting("lastfm_api_key")
|
||||||
|
if apiKey == "" {
|
||||||
|
return sub
|
||||||
|
}
|
||||||
info, err := lastfm.ArtistGetInfo(apiKey, artist)
|
info, err := lastfm.ArtistGetInfo(apiKey, artist)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return spec.NewError(0, "fetching artist info: %v", err)
|
return spec.NewError(0, "fetching artist info: %v", err)
|
||||||
}
|
}
|
||||||
sub := spec.NewResponse()
|
|
||||||
sub.ArtistInfoTwo = &spec.ArtistInfo{
|
sub.ArtistInfoTwo.Biography = info.Bio.Summary
|
||||||
Biography: info.Bio.Summary,
|
sub.ArtistInfoTwo.MusicBrainzID = info.MBID
|
||||||
MusicBrainzID: info.MBID,
|
sub.ArtistInfoTwo.LastFMURL = info.URL
|
||||||
LastFMURL: info.URL,
|
|
||||||
}
|
if artist.Cover == "" {
|
||||||
for _, image := range info.Image {
|
for _, image := range info.Image {
|
||||||
switch image.Size {
|
switch image.Size {
|
||||||
case "small":
|
case "small":
|
||||||
sub.ArtistInfoTwo.SmallImageURL = image.Text
|
sub.ArtistInfoTwo.SmallImageURL = image.Text
|
||||||
case "medium":
|
case "medium":
|
||||||
sub.ArtistInfoTwo.MediumImageURL = image.Text
|
sub.ArtistInfoTwo.MediumImageURL = image.Text
|
||||||
case "large":
|
case "large":
|
||||||
sub.ArtistInfoTwo.LargeImageURL = image.Text
|
sub.ArtistInfoTwo.LargeImageURL = image.Text
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
count := params.GetOrInt("count", 20)
|
count := params.GetOrInt("count", 20)
|
||||||
inclNotPresent := params.GetOrBool("includeNotPresent", false)
|
inclNotPresent := params.GetOrBool("includeNotPresent", false)
|
||||||
for i, similarInfo := range info.Similar.Artists {
|
for i, similarInfo := range info.Similar.Artists {
|
||||||
@@ -310,6 +322,7 @@ func (c *Controller) ServeGetArtistInfoTwo(r *http.Request) *spec.Response {
|
|||||||
sub.ArtistInfoTwo.SimilarArtist = append(
|
sub.ArtistInfoTwo.SimilarArtist = append(
|
||||||
sub.ArtistInfoTwo.SimilarArtist, similar)
|
sub.ArtistInfoTwo.SimilarArtist, similar)
|
||||||
}
|
}
|
||||||
|
|
||||||
return sub
|
return sub
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -371,3 +384,15 @@ func (c *Controller) ServeGetStarredTwo(r *http.Request) *spec.Response {
|
|||||||
}
|
}
|
||||||
return sub
|
return sub
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Controller) genArtistCoverURL(r *http.Request, artist *db.Artist, size int) string {
|
||||||
|
coverURL, _ := url.Parse(c.BaseURL(r))
|
||||||
|
coverURL.Path = c.Path("/rest/getCoverArt")
|
||||||
|
|
||||||
|
query := r.URL.Query()
|
||||||
|
query.Set("id", artist.SID().String())
|
||||||
|
query.Set("size", strconv.Itoa(size))
|
||||||
|
coverURL.RawQuery = query.Encode()
|
||||||
|
|
||||||
|
return coverURL.String()
|
||||||
|
}
|
||||||
|
|||||||
@@ -78,6 +78,8 @@ func coverGetPath(dbc *db.DB, podcastPath string, id specid.ID) (string, error)
|
|||||||
switch id.Type {
|
switch id.Type {
|
||||||
case specid.Album:
|
case specid.Album:
|
||||||
return coverGetPathAlbum(dbc, id.Value)
|
return coverGetPathAlbum(dbc, id.Value)
|
||||||
|
case specid.Artist:
|
||||||
|
return coverGetPathArtist(dbc, id.Value)
|
||||||
case specid.Podcast:
|
case specid.Podcast:
|
||||||
return coverGetPathPodcast(dbc, podcastPath, id.Value)
|
return coverGetPathPodcast(dbc, podcastPath, id.Value)
|
||||||
case specid.PodcastEpisode:
|
case specid.PodcastEpisode:
|
||||||
@@ -90,7 +92,6 @@ func coverGetPath(dbc *db.DB, podcastPath string, id specid.ID) (string, error)
|
|||||||
func coverGetPathAlbum(dbc *db.DB, id int) (string, error) {
|
func coverGetPathAlbum(dbc *db.DB, id int) (string, error) {
|
||||||
folder := &db.Album{}
|
folder := &db.Album{}
|
||||||
err := dbc.DB.
|
err := dbc.DB.
|
||||||
Preload("Parent").
|
|
||||||
Select("id, root_dir, left_path, right_path, cover").
|
Select("id, root_dir, left_path, right_path, cover").
|
||||||
First(folder, id).
|
First(folder, id).
|
||||||
Error
|
Error
|
||||||
@@ -108,6 +109,28 @@ func coverGetPathAlbum(dbc *db.DB, id int) (string, error) {
|
|||||||
), nil
|
), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func coverGetPathArtist(dbc *db.DB, id int) (string, error) {
|
||||||
|
folder := &db.Album{}
|
||||||
|
err := dbc.DB.Debug().
|
||||||
|
Select("parent.id, parent.root_dir, parent.left_path, parent.right_path, parent.cover").
|
||||||
|
Joins("JOIN albums parent ON parent.id=albums.parent_id").
|
||||||
|
Where("albums.tag_artist_id=?", id).
|
||||||
|
Find(folder).
|
||||||
|
Error
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("select guessed artist folder: %w", err)
|
||||||
|
}
|
||||||
|
if folder.Cover == "" {
|
||||||
|
return "", errCoverEmpty
|
||||||
|
}
|
||||||
|
return path.Join(
|
||||||
|
folder.RootDir,
|
||||||
|
folder.LeftPath,
|
||||||
|
folder.RightPath,
|
||||||
|
folder.Cover,
|
||||||
|
), nil
|
||||||
|
}
|
||||||
|
|
||||||
func coverGetPathPodcast(dbc *db.DB, podcastPath string, id int) (string, error) {
|
func coverGetPathPodcast(dbc *db.DB, podcastPath string, id int) (string, error) {
|
||||||
podcast := &db.Podcast{}
|
podcast := &db.Podcast{}
|
||||||
err := dbc.
|
err := dbc.
|
||||||
|
|||||||
@@ -76,11 +76,15 @@ func NewArtistByFolder(f *db.Album) *Artist {
|
|||||||
// an album is also a folder. so we're constructing an artist
|
// an album is also a folder. so we're constructing an artist
|
||||||
// from an "album" where
|
// from an "album" where
|
||||||
// maybe TODO: rename the Album model to Folder
|
// maybe TODO: rename the Album model to Folder
|
||||||
return &Artist{
|
a := &Artist{
|
||||||
ID: f.SID(),
|
ID: f.SID(),
|
||||||
Name: f.RightPath,
|
Name: f.RightPath,
|
||||||
AlbumCount: f.ChildCount,
|
AlbumCount: f.ChildCount,
|
||||||
}
|
}
|
||||||
|
if f.Cover != "" {
|
||||||
|
a.CoverID = f.SID()
|
||||||
|
}
|
||||||
|
return a
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDirectoryByFolder(f *db.Album, children []*TrackChild) *Directory {
|
func NewDirectoryByFolder(f *db.Album, children []*TrackChild) *Directory {
|
||||||
|
|||||||
@@ -72,11 +72,15 @@ func NewTrackByTags(t *db.Track, album *db.Album) *TrackChild {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewArtistByTags(a *db.Artist) *Artist {
|
func NewArtistByTags(a *db.Artist) *Artist {
|
||||||
return &Artist{
|
r := &Artist{
|
||||||
ID: a.SID(),
|
ID: a.SID(),
|
||||||
Name: a.Name,
|
Name: a.Name,
|
||||||
AlbumCount: a.AlbumCount,
|
AlbumCount: a.AlbumCount,
|
||||||
}
|
}
|
||||||
|
if a.Cover != "" {
|
||||||
|
r.CoverID = a.SID()
|
||||||
|
}
|
||||||
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewGenre(g *db.Genre) *Genre {
|
func NewGenre(g *db.Genre) *Genre {
|
||||||
|
|||||||
@@ -37,6 +37,8 @@ func (db *DB) Migrate(ctx MigrationContext) error {
|
|||||||
construct(ctx, "202102191448", migratePodcastAutoDownload),
|
construct(ctx, "202102191448", migratePodcastAutoDownload),
|
||||||
construct(ctx, "202110041330", migrateAlbumCreatedAt),
|
construct(ctx, "202110041330", migrateAlbumCreatedAt),
|
||||||
construct(ctx, "202111021951", migrateAlbumRootDir),
|
construct(ctx, "202111021951", migrateAlbumRootDir),
|
||||||
|
construct(ctx, "202201042236", migrateArtistGuessedFolder),
|
||||||
|
construct(ctx, "202202092013", migrateArtistCover),
|
||||||
}
|
}
|
||||||
|
|
||||||
return gormigrate.
|
return gormigrate.
|
||||||
@@ -306,3 +308,28 @@ func migrateAlbumRootDir(tx *gorm.DB, ctx MigrationContext) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func migrateArtistGuessedFolder(tx *gorm.DB, ctx MigrationContext) error {
|
||||||
|
return tx.AutoMigrate(Artist{}).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func migrateArtistCover(tx *gorm.DB, ctx MigrationContext) error {
|
||||||
|
step := tx.AutoMigrate(
|
||||||
|
Artist{},
|
||||||
|
)
|
||||||
|
if err := step.Error; err != nil {
|
||||||
|
return fmt.Errorf("step auto migrate: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !tx.Dialect().HasColumn("artists", "guessed_folder_id") {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
step = tx.Exec(`
|
||||||
|
ALTER TABLE artists DROP COLUMN guessed_folder_id
|
||||||
|
`)
|
||||||
|
if err := step.Error; err != nil {
|
||||||
|
return fmt.Errorf("step drop column: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ type Artist struct {
|
|||||||
NameUDec string `sql:"default: null"`
|
NameUDec string `sql:"default: null"`
|
||||||
Albums []*Album `gorm:"foreignkey:TagArtistID"`
|
Albums []*Album `gorm:"foreignkey:TagArtistID"`
|
||||||
AlbumCount int `sql:"-"`
|
AlbumCount int `sql:"-"`
|
||||||
|
Cover string `sql:"default: null"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Artist) SID() *specid.ID {
|
func (a *Artist) SID() *specid.ID {
|
||||||
|
|||||||
@@ -187,7 +187,7 @@ func (s *Scanner) scanDir(tx *db.DB, c *ctx, musicDir string, absPath string) er
|
|||||||
sort.Strings(tracks)
|
sort.Strings(tracks)
|
||||||
for i, basename := range tracks {
|
for i, basename := range tracks {
|
||||||
absPath := filepath.Join(musicDir, relPath, basename)
|
absPath := filepath.Join(musicDir, relPath, basename)
|
||||||
if err := s.populateTrackAndAlbumArtists(tx, c, i, album, basename, absPath); err != nil {
|
if err := s.populateTrackAndAlbumArtists(tx, c, i, parent, album, basename, absPath); err != nil {
|
||||||
return fmt.Errorf("populate track %q: %w", basename, err)
|
return fmt.Errorf("populate track %q: %w", basename, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -195,7 +195,7 @@ func (s *Scanner) scanDir(tx *db.DB, c *ctx, musicDir string, absPath string) er
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Scanner) populateTrackAndAlbumArtists(tx *db.DB, c *ctx, i int, album *db.Album, basename string, absPath string) error {
|
func (s *Scanner) populateTrackAndAlbumArtists(tx *db.DB, c *ctx, i int, parent, album *db.Album, basename string, absPath string) error {
|
||||||
track := &db.Track{AlbumID: album.ID, Filename: filepath.Base(basename)}
|
track := &db.Track{AlbumID: album.ID, Filename: filepath.Base(basename)}
|
||||||
if err := tx.Where(track).First(track).Error; err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
if err := tx.Where(track).First(track).Error; err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
return fmt.Errorf("query track: %w", err)
|
return fmt.Errorf("query track: %w", err)
|
||||||
@@ -217,7 +217,7 @@ func (s *Scanner) populateTrackAndAlbumArtists(tx *db.DB, c *ctx, i int, album *
|
|||||||
}
|
}
|
||||||
|
|
||||||
artistName := trags.SomeAlbumArtist()
|
artistName := trags.SomeAlbumArtist()
|
||||||
albumArtist, err := s.populateAlbumArtist(tx, artistName)
|
albumArtist, err := s.populateAlbumArtist(tx, parent, artistName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("populate artist: %w", err)
|
return fmt.Errorf("populate artist: %w", err)
|
||||||
}
|
}
|
||||||
@@ -315,12 +315,15 @@ func populateTrack(tx *db.DB, album *db.Album, albumArtist *db.Artist, track *db
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Scanner) populateAlbumArtist(tx *db.DB, artistName string) (*db.Artist, error) {
|
func (s *Scanner) populateAlbumArtist(tx *db.DB, parent *db.Album, artistName string) (*db.Artist, error) {
|
||||||
var artist db.Artist
|
var artist db.Artist
|
||||||
update := db.Artist{
|
update := db.Artist{
|
||||||
Name: artistName,
|
Name: artistName,
|
||||||
NameUDec: decoded(artistName),
|
NameUDec: decoded(artistName),
|
||||||
}
|
}
|
||||||
|
if parent.Cover != "" {
|
||||||
|
update.Cover = parent.Cover
|
||||||
|
}
|
||||||
if err := tx.Where("name=?", artistName).Assign(update).FirstOrCreate(&artist).Error; err != nil {
|
if err := tx.Where("name=?", artistName).Assign(update).FirstOrCreate(&artist).Error; err != nil {
|
||||||
return nil, fmt.Errorf("find or create artist: %w", err)
|
return nil, fmt.Errorf("find or create artist: %w", err)
|
||||||
}
|
}
|
||||||
@@ -476,7 +479,8 @@ func isCover(name string) bool {
|
|||||||
"folder.png", "folder.jpg", "folder.jpeg",
|
"folder.png", "folder.jpg", "folder.jpeg",
|
||||||
"album.png", "album.jpg", "album.jpeg",
|
"album.png", "album.jpg", "album.jpeg",
|
||||||
"albumart.png", "albumart.jpg", "albumart.jpeg",
|
"albumart.png", "albumart.jpg", "albumart.jpeg",
|
||||||
"front.png", "front.jpg", "front.jpeg":
|
"front.png", "front.jpg", "front.jpeg",
|
||||||
|
"artist.png", "artist.jpg", "artist.jpeg":
|
||||||
return true
|
return true
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -441,3 +441,22 @@ func TestSymlinkedSubdiscs(t *testing.T) {
|
|||||||
is.True(!info.IsDir()) // track resolves
|
is.True(!info.IsDir()) // track resolves
|
||||||
is.True(!info.ModTime().IsZero()) // track resolves
|
is.True(!info.ModTime().IsZero()) // track resolves
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestArtistHasCover(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
is := is.New(t)
|
||||||
|
m := mockfs.New(t)
|
||||||
|
defer m.CleanUp()
|
||||||
|
|
||||||
|
m.AddItemsWithCovers()
|
||||||
|
m.AddCover("artist-2/artist.png")
|
||||||
|
m.ScanAndClean()
|
||||||
|
|
||||||
|
var artistWith db.Artist
|
||||||
|
is.NoErr(m.DB().Where("name=?", "artist-2").First(&artistWith).Error)
|
||||||
|
is.Equal(artistWith.Cover, "artist.png")
|
||||||
|
|
||||||
|
var artistWithout db.Artist
|
||||||
|
is.NoErr(m.DB().Where("name=?", "artist-0").First(&artistWithout).Error)
|
||||||
|
is.Equal(artistWithout.Cover, "")
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user