mirror of
https://github.com/navidrome/navidrome.git
synced 2025-04-14 19:20:37 +03:00
Fix empty entry
collection in Shares
This commit is contained in:
parent
c994ed70ea
commit
e34f26588e
@ -10,7 +10,6 @@ import (
|
|||||||
gonanoid "github.com/matoous/go-nanoid/v2"
|
gonanoid "github.com/matoous/go-nanoid/v2"
|
||||||
"github.com/navidrome/navidrome/log"
|
"github.com/navidrome/navidrome/log"
|
||||||
"github.com/navidrome/navidrome/model"
|
"github.com/navidrome/navidrome/model"
|
||||||
"github.com/navidrome/navidrome/model/request"
|
|
||||||
"github.com/navidrome/navidrome/utils/slice"
|
"github.com/navidrome/navidrome/utils/slice"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -31,11 +30,10 @@ type shareService struct {
|
|||||||
|
|
||||||
func (s *shareService) Load(ctx context.Context, id string) (*model.Share, error) {
|
func (s *shareService) Load(ctx context.Context, id string) (*model.Share, error) {
|
||||||
repo := s.ds.Share(ctx)
|
repo := s.ds.Share(ctx)
|
||||||
entity, err := repo.(rest.Repository).Read(id)
|
share, err := repo.Get(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
share := entity.(*model.Share)
|
|
||||||
if !share.ExpiresAt.IsZero() && share.ExpiresAt.Before(time.Now()) {
|
if !share.ExpiresAt.IsZero() && share.ExpiresAt.Before(time.Now()) {
|
||||||
return nil, model.ErrNotAvailable
|
return nil, model.ErrNotAvailable
|
||||||
}
|
}
|
||||||
@ -46,35 +44,7 @@ func (s *shareService) Load(ctx context.Context, id string) (*model.Share, error
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn(ctx, "Could not increment visit count for share", "share", share.ID)
|
log.Warn(ctx, "Could not increment visit count for share", "share", share.ID)
|
||||||
}
|
}
|
||||||
|
return share, nil
|
||||||
idList := strings.Split(share.ResourceIDs, ",")
|
|
||||||
var mfs model.MediaFiles
|
|
||||||
switch share.ResourceType {
|
|
||||||
case "album":
|
|
||||||
mfs, err = s.loadMediafiles(ctx, squirrel.Eq{"album_id": idList}, "album")
|
|
||||||
case "playlist":
|
|
||||||
mfs, err = s.loadPlaylistTracks(ctx, share.ResourceIDs)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
share.Tracks = mfs
|
|
||||||
return entity.(*model.Share), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *shareService) loadMediafiles(ctx context.Context, filter squirrel.Eq, sort string) (model.MediaFiles, error) {
|
|
||||||
return s.ds.MediaFile(ctx).GetAll(model.QueryOptions{Filters: filter, Sort: sort})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *shareService) loadPlaylistTracks(ctx context.Context, id string) (model.MediaFiles, error) {
|
|
||||||
// Create a context with a fake admin user, to be able to access playlists
|
|
||||||
ctx = request.WithUser(ctx, model.User{IsAdmin: true})
|
|
||||||
|
|
||||||
tracks, err := s.ds.Playlist(ctx).Tracks(id, true).GetAll(model.QueryOptions{Sort: "id"})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return tracks.MediaFiles(), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *shareService) NewRepository(ctx context.Context) rest.Repository {
|
func (s *shareService) NewRepository(ctx context.Context) rest.Repository {
|
||||||
|
@ -20,11 +20,13 @@ type Share struct {
|
|||||||
CreatedAt time.Time `structs:"created_at" json:"createdAt,omitempty"`
|
CreatedAt time.Time `structs:"created_at" json:"createdAt,omitempty"`
|
||||||
UpdatedAt time.Time `structs:"updated_at" json:"updatedAt,omitempty"`
|
UpdatedAt time.Time `structs:"updated_at" json:"updatedAt,omitempty"`
|
||||||
Tracks MediaFiles `structs:"-" json:"tracks,omitempty" orm:"-"`
|
Tracks MediaFiles `structs:"-" json:"tracks,omitempty" orm:"-"`
|
||||||
|
Albums Albums `structs:"-" json:"albums,omitempty" orm:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Shares []Share
|
type Shares []Share
|
||||||
|
|
||||||
type ShareRepository interface {
|
type ShareRepository interface {
|
||||||
Exists(id string) (bool, error)
|
Exists(id string) (bool, error)
|
||||||
|
Get(id string) (*Share, error)
|
||||||
GetAll(options ...QueryOptions) (Shares, error)
|
GetAll(options ...QueryOptions) (Shares, error)
|
||||||
}
|
}
|
||||||
|
@ -3,12 +3,16 @@ package persistence
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
. "github.com/Masterminds/squirrel"
|
. "github.com/Masterminds/squirrel"
|
||||||
"github.com/beego/beego/v2/client/orm"
|
"github.com/beego/beego/v2/client/orm"
|
||||||
"github.com/deluan/rest"
|
"github.com/deluan/rest"
|
||||||
|
"github.com/navidrome/navidrome/log"
|
||||||
"github.com/navidrome/navidrome/model"
|
"github.com/navidrome/navidrome/model"
|
||||||
|
"github.com/navidrome/navidrome/model/request"
|
||||||
)
|
)
|
||||||
|
|
||||||
type shareRepository struct {
|
type shareRepository struct {
|
||||||
@ -40,13 +44,66 @@ func (r *shareRepository) selectShare(options ...model.QueryOptions) SelectBuild
|
|||||||
func (r *shareRepository) Exists(id string) (bool, error) {
|
func (r *shareRepository) Exists(id string) (bool, error) {
|
||||||
return r.exists(Select().Where(Eq{"id": id}))
|
return r.exists(Select().Where(Eq{"id": id}))
|
||||||
}
|
}
|
||||||
|
func (r *shareRepository) Get(id string) (*model.Share, error) {
|
||||||
|
sel := r.selectShare().Where(Eq{"share.id": id})
|
||||||
|
var res model.Share
|
||||||
|
err := r.queryOne(sel, &res)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = r.loadMedia(&res)
|
||||||
|
return &res, err
|
||||||
|
}
|
||||||
|
|
||||||
func (r *shareRepository) GetAll(options ...model.QueryOptions) (model.Shares, error) {
|
func (r *shareRepository) GetAll(options ...model.QueryOptions) (model.Shares, error) {
|
||||||
sq := r.selectShare(options...)
|
sq := r.selectShare(options...)
|
||||||
res := model.Shares{}
|
res := model.Shares{}
|
||||||
err := r.queryAll(sq, &res)
|
err := r.queryAll(sq, &res)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for i := range res {
|
||||||
|
err = r.loadMedia(&res[i])
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error loading media for share %s: %w", res[i].ID, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *shareRepository) loadMedia(share *model.Share) error {
|
||||||
|
var err error
|
||||||
|
ids := strings.Split(share.ResourceIDs, ",")
|
||||||
|
if len(ids) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
switch share.ResourceType {
|
||||||
|
case "album":
|
||||||
|
albumRepo := NewAlbumRepository(r.ctx, r.ormer)
|
||||||
|
share.Albums, err = albumRepo.GetAll(model.QueryOptions{Filters: Eq{"id": ids}})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
mfRepo := NewMediaFileRepository(r.ctx, r.ormer)
|
||||||
|
share.Tracks, err = mfRepo.GetAll(model.QueryOptions{Filters: Eq{"album_id": ids}, Sort: "album"})
|
||||||
|
return err
|
||||||
|
case "playlist":
|
||||||
|
// Create a context with a fake admin user, to be able to access all playlists
|
||||||
|
ctx := request.WithUser(r.ctx, model.User{IsAdmin: true})
|
||||||
|
plsRepo := NewPlaylistRepository(ctx, r.ormer)
|
||||||
|
tracks, err := plsRepo.Tracks(ids[0], true).GetAll(model.QueryOptions{Sort: "id"})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(tracks) >= 0 {
|
||||||
|
share.Tracks = tracks.MediaFiles()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
log.Warn(r.ctx, "Unsupported Share ResourceType", "share", share.ID, "resourceType", share.ResourceType)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (r *shareRepository) Update(id string, entity interface{}, cols ...string) error {
|
func (r *shareRepository) Update(id string, entity interface{}, cols ...string) error {
|
||||||
s := entity.(*model.Share)
|
s := entity.(*model.Share)
|
||||||
// TODO Validate record
|
// TODO Validate record
|
||||||
@ -92,19 +149,18 @@ func (r *shareRepository) NewInstance() interface{} {
|
|||||||
return &model.Share{}
|
return &model.Share{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *shareRepository) Get(id string) (*model.Share, error) {
|
func (r *shareRepository) Read(id string) (interface{}, error) {
|
||||||
sel := r.selectShare().Where(Eq{"share.id": id})
|
sel := r.selectShare().Where(Eq{"share.id": id})
|
||||||
var res model.Share
|
var res model.Share
|
||||||
err := r.queryOne(sel, &res)
|
err := r.queryOne(sel, &res)
|
||||||
return &res, err
|
return &res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *shareRepository) Read(id string) (interface{}, error) {
|
|
||||||
return r.Get(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *shareRepository) ReadAll(options ...rest.QueryOptions) (interface{}, error) {
|
func (r *shareRepository) ReadAll(options ...rest.QueryOptions) (interface{}, error) {
|
||||||
return r.GetAll(r.parseRestOptions(options...))
|
sq := r.selectShare(r.parseRestOptions(options...))
|
||||||
|
res := model.Shares{}
|
||||||
|
err := r.queryAll(sq, &res)
|
||||||
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ model.ShareRepository = (*shareRepository)(nil)
|
var _ model.ShareRepository = (*shareRepository)(nil)
|
||||||
|
@ -13,12 +13,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func (api *Router) GetShares(r *http.Request) (*responses.Subsonic, error) {
|
func (api *Router) GetShares(r *http.Request) (*responses.Subsonic, error) {
|
||||||
repo := api.share.NewRepository(r.Context())
|
repo := api.share.NewRepository(r.Context()).(model.ShareRepository)
|
||||||
entity, err := repo.ReadAll()
|
shares, err := repo.GetAll()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
shares := entity.(model.Shares)
|
|
||||||
|
|
||||||
response := newResponse()
|
response := newResponse()
|
||||||
response.Shares = &responses.Shares{}
|
response.Shares = &responses.Shares{}
|
||||||
@ -29,8 +28,7 @@ func (api *Router) GetShares(r *http.Request) (*responses.Subsonic, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (api *Router) buildShare(r *http.Request, share model.Share) responses.Share {
|
func (api *Router) buildShare(r *http.Request, share model.Share) responses.Share {
|
||||||
return responses.Share{
|
resp := responses.Share{
|
||||||
Entry: childrenFromMediaFiles(r.Context(), share.Tracks),
|
|
||||||
ID: share.ID,
|
ID: share.ID,
|
||||||
Url: public.ShareURL(r, share.ID),
|
Url: public.ShareURL(r, share.ID),
|
||||||
Description: share.Description,
|
Description: share.Description,
|
||||||
@ -40,6 +38,12 @@ func (api *Router) buildShare(r *http.Request, share model.Share) responses.Shar
|
|||||||
LastVisited: share.LastVisitedAt,
|
LastVisited: share.LastVisitedAt,
|
||||||
VisitCount: share.VisitCount,
|
VisitCount: share.VisitCount,
|
||||||
}
|
}
|
||||||
|
if len(share.Albums) > 0 {
|
||||||
|
resp.Entry = childrenFromAlbums(r.Context(), share.Albums)
|
||||||
|
} else {
|
||||||
|
resp.Entry = childrenFromMediaFiles(r.Context(), share.Tracks)
|
||||||
|
}
|
||||||
|
return resp
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *Router) CreateShare(r *http.Request) (*responses.Subsonic, error) {
|
func (api *Router) CreateShare(r *http.Request) (*responses.Subsonic, error) {
|
||||||
@ -63,11 +67,10 @@ func (api *Router) CreateShare(r *http.Request) (*responses.Subsonic, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
entity, err := repo.Read(id)
|
share, err = repo.(model.ShareRepository).Get(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
share = entity.(*model.Share)
|
|
||||||
|
|
||||||
response := newResponse()
|
response := newResponse()
|
||||||
response.Shares = &responses.Shares{Share: []responses.Share{api.buildShare(r, *share)}}
|
response.Shares = &responses.Shares{Share: []responses.Share{api.buildShare(r, *share)}}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user