mirror of
https://github.com/navidrome/navidrome.git
synced 2025-04-03 20:47:35 +03:00
Add nested resource playlist/{id}/tracks
This commit is contained in:
parent
a56e588c8e
commit
aebee651ac
4 changed files with 121 additions and 7 deletions
|
@ -1,6 +1,10 @@
|
|||
package model
|
||||
|
||||
import "time"
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/deluan/rest"
|
||||
)
|
||||
|
||||
type Playlist struct {
|
||||
ID string `json:"id" orm:"column(id)"`
|
||||
|
@ -22,6 +26,18 @@ type PlaylistRepository interface {
|
|||
Get(id string) (*Playlist, error)
|
||||
GetAll(options ...QueryOptions) (Playlists, error)
|
||||
Delete(id string) error
|
||||
Tracks(playlistId string) PlaylistTracksRepository
|
||||
}
|
||||
|
||||
type PlaylistTracks struct {
|
||||
ID string `json:"id" orm:"column(id)"`
|
||||
MediaFileID string `json:"mediaFileId" orm:"column(media_file_id)"`
|
||||
MediaFile
|
||||
}
|
||||
|
||||
type PlaylistTracksRepository interface {
|
||||
rest.Repository
|
||||
//rest.Persistable
|
||||
}
|
||||
|
||||
type Playlists []Playlist
|
||||
|
|
|
@ -77,10 +77,20 @@ func (r *playlistRepository) GetAll(options ...model.QueryOptions) (model.Playli
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// TODO Maybe the tracks are not required when retrieving all playlists?
|
||||
err = r.loadAllTracks(res)
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (r *playlistRepository) Tracks(playlistId string) model.PlaylistTracksRepository {
|
||||
p := &playlistTracksRepository{}
|
||||
p.playlistId = playlistId
|
||||
p.ctx = r.ctx
|
||||
p.ormer = r.ormer
|
||||
p.tableName = "playlist_tracks"
|
||||
return p
|
||||
}
|
||||
|
||||
func (r *playlistRepository) updateTracks(id string, tracks model.MediaFiles) error {
|
||||
// Remove old tracks
|
||||
del := Delete("playlist_tracks").Where(Eq{"playlist_id": id})
|
||||
|
@ -128,9 +138,14 @@ func (r *playlistRepository) loadAllTracks(all model.Playlists) error {
|
|||
}
|
||||
|
||||
func (r *playlistRepository) loadTracks(pls *model.Playlist) (err error) {
|
||||
tracksQuery := Select("media_file.*").From("media_file").
|
||||
Join("playlist_tracks f on f.media_file_id = media_file.id").
|
||||
Where(Eq{"f.playlist_id": pls.ID}).OrderBy("f.id")
|
||||
tracksQuery := Select().From("playlist_tracks").
|
||||
LeftJoin("annotation on ("+
|
||||
"annotation.item_id = media_file_id"+
|
||||
" AND annotation.item_type = 'media_file'"+
|
||||
" AND annotation.user_id = '"+userId(r.ctx)+"')").
|
||||
Columns("starred", "starred_at", "play_count", "play_date", "rating", "f.*").
|
||||
Join("media_file f on f.id = media_file_id").
|
||||
Where(Eq{"playlist_id": pls.ID}).OrderBy("playlist_tracks.id")
|
||||
err = r.queryAll(tracksQuery, &pls.Tracks)
|
||||
if err != nil {
|
||||
log.Error("Error loading playlist tracks", "playlist", pls.Name, "id", pls.ID)
|
||||
|
|
56
persistence/playlist_tracks_repository.go
Normal file
56
persistence/playlist_tracks_repository.go
Normal file
|
@ -0,0 +1,56 @@
|
|||
package persistence
|
||||
|
||||
import (
|
||||
. "github.com/Masterminds/squirrel"
|
||||
"github.com/deluan/navidrome/model"
|
||||
"github.com/deluan/rest"
|
||||
)
|
||||
|
||||
type playlistTracksRepository struct {
|
||||
sqlRepository
|
||||
sqlRestful
|
||||
playlistId string
|
||||
}
|
||||
|
||||
func (r *playlistTracksRepository) Count(options ...rest.QueryOptions) (int64, error) {
|
||||
return r.count(Select().Where(Eq{"playlist_id": r.playlistId}), r.parseRestOptions(options...))
|
||||
}
|
||||
|
||||
func (r *playlistTracksRepository) Read(id string) (interface{}, error) {
|
||||
sel := r.newSelect().
|
||||
LeftJoin("annotation on ("+
|
||||
"annotation.item_id = media_file_id"+
|
||||
" AND annotation.item_type = 'media_file'"+
|
||||
" AND annotation.user_id = '"+userId(r.ctx)+"')").
|
||||
Columns("starred", "starred_at", "play_count", "play_date", "rating", "f.*", "playlist_tracks.*").
|
||||
Join("media_file f on f.id = media_file_id").
|
||||
Where(And{Eq{"playlist_id": r.playlistId}, Eq{"id": id}})
|
||||
var trk model.PlaylistTracks
|
||||
err := r.queryOne(sel, &trk)
|
||||
return &trk, err
|
||||
}
|
||||
|
||||
func (r *playlistTracksRepository) ReadAll(options ...rest.QueryOptions) (interface{}, error) {
|
||||
sel := r.newSelect(r.parseRestOptions(options...)).
|
||||
LeftJoin("annotation on ("+
|
||||
"annotation.item_id = media_file_id"+
|
||||
" AND annotation.item_type = 'media_file'"+
|
||||
" AND annotation.user_id = '"+userId(r.ctx)+"')").
|
||||
Columns("starred", "starred_at", "play_count", "play_date", "rating", "f.*", "playlist_tracks.*").
|
||||
Join("media_file f on f.id = media_file_id").
|
||||
Where(Eq{"playlist_id": r.playlistId})
|
||||
var res []model.PlaylistTracks
|
||||
err := r.queryAll(sel, &res)
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (r *playlistTracksRepository) EntityName() string {
|
||||
return "playlist_tracks"
|
||||
}
|
||||
|
||||
func (r *playlistTracksRepository) NewInstance() interface{} {
|
||||
return &model.PlaylistTracks{}
|
||||
}
|
||||
|
||||
var _ model.PlaylistTracksRepository = (*playlistTracksRepository)(nil)
|
||||
var _ model.ResourceRepository = (*playlistTracksRepository)(nil)
|
|
@ -49,7 +49,9 @@ func (app *Router) routes(path string) http.Handler {
|
|||
app.R(r, "/player", model.Player{}, true)
|
||||
app.R(r, "/playlist", model.Playlist{}, true)
|
||||
app.R(r, "/transcoding", model.Transcoding{}, conf.Server.EnableTranscodingConfig)
|
||||
app.addResource(r, "/translation", newTranslationRepository, false)
|
||||
app.RX(r, "/translation", newTranslationRepository, false)
|
||||
|
||||
app.addPlaylistTracksRoute(r)
|
||||
|
||||
// Keepalive endpoint to be used to keep the session valid (ex: while playing songs)
|
||||
r.Get("/keepalive/*", func(w http.ResponseWriter, r *http.Request) { _, _ = w.Write([]byte(`{"response":"ok"}`)) })
|
||||
|
@ -66,10 +68,10 @@ func (app *Router) R(r chi.Router, pathPrefix string, model interface{}, persist
|
|||
constructor := func(ctx context.Context) rest.Repository {
|
||||
return app.ds.Resource(ctx, model)
|
||||
}
|
||||
app.addResource(r, pathPrefix, constructor, persistable)
|
||||
app.RX(r, pathPrefix, constructor, persistable)
|
||||
}
|
||||
|
||||
func (app *Router) addResource(r chi.Router, pathPrefix string, constructor rest.RepositoryConstructor, persistable bool) {
|
||||
func (app *Router) RX(r chi.Router, pathPrefix string, constructor rest.RepositoryConstructor, persistable bool) {
|
||||
r.Route(pathPrefix, func(r chi.Router) {
|
||||
r.Get("/", rest.GetAll(constructor))
|
||||
if persistable {
|
||||
|
@ -86,6 +88,31 @@ func (app *Router) addResource(r chi.Router, pathPrefix string, constructor rest
|
|||
})
|
||||
}
|
||||
|
||||
type restHandler = func(rest.RepositoryConstructor, ...rest.Logger) http.HandlerFunc
|
||||
|
||||
func (app *Router) addPlaylistTracksRoute(r chi.Router) {
|
||||
// Add a middleware to capture the playlisId
|
||||
wrapper := func(f restHandler) http.HandlerFunc {
|
||||
return func(res http.ResponseWriter, req *http.Request) {
|
||||
c := func(ctx context.Context) rest.Repository {
|
||||
plsRepo := app.ds.Resource(ctx, model.Playlist{})
|
||||
plsId := chi.URLParam(req, "playlistId")
|
||||
return plsRepo.(model.PlaylistRepository).Tracks(plsId)
|
||||
}
|
||||
|
||||
f(c).ServeHTTP(res, req)
|
||||
}
|
||||
}
|
||||
|
||||
r.Route("/playlist/{playlistId}/tracks", func(r chi.Router) {
|
||||
r.Get("/", wrapper(rest.GetAll))
|
||||
r.Route("/{id}", func(r chi.Router) {
|
||||
r.Use(UrlParams)
|
||||
r.Get("/", wrapper(rest.Get))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// Middleware to convert Chi URL params (from Context) to query params, as expected by our REST package
|
||||
func UrlParams(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue