package playlists import ( "context" "errors" "github.com/deluan/rest" "github.com/navidrome/navidrome/model" "github.com/navidrome/navidrome/model/request" ) // --- REST adapter (follows Share/Library pattern) --- func (s *playlists) NewRepository(ctx context.Context) rest.Repository { return &playlistRepositoryWrapper{ ctx: ctx, PlaylistRepository: s.ds.Playlist(ctx), service: s, } } // playlistRepositoryWrapper wraps the playlist repository as a thin REST-to-service adapter. // It satisfies rest.Repository through the embedded PlaylistRepository (via ResourceRepository), // and rest.Persistable by delegating to service methods for all mutations. type playlistRepositoryWrapper struct { model.PlaylistRepository ctx context.Context service *playlists } func (r *playlistRepositoryWrapper) Save(entity any) (string, error) { return r.service.savePlaylist(r.ctx, entity.(*model.Playlist)) } func (r *playlistRepositoryWrapper) Update(id string, entity any, cols ...string) error { return r.service.updatePlaylistEntity(r.ctx, id, entity.(*model.Playlist), cols...) } func (r *playlistRepositoryWrapper) Delete(id string) error { err := r.service.Delete(r.ctx, id) switch { case errors.Is(err, model.ErrNotFound): return rest.ErrNotFound case errors.Is(err, model.ErrNotAuthorized): return rest.ErrPermissionDenied default: return err } } func (s *playlists) TracksRepository(ctx context.Context, playlistId string, refreshSmartPlaylist bool) rest.Repository { repo := s.ds.Playlist(ctx) tracks := repo.Tracks(playlistId, refreshSmartPlaylist) if tracks == nil { return nil } return tracks.(rest.Repository) } // savePlaylist creates a new playlist, assigning the owner from context. // Only Name, Comment, Public, and Rules are user-settable via the REST API. func (s *playlists) savePlaylist(ctx context.Context, pls *model.Playlist) (string, error) { usr, _ := request.UserFrom(ctx) pls.OwnerID = usr.ID pls.ID = "" // Force new creation pls.Path = "" // Server-managed (M3U file path) pls.Sync = false // Server-managed (M3U sync flag) pls.UploadedImage = "" // Managed by image upload endpoint pls.ExternalImageURL = "" // Managed by M3U import / plugins only pls.EvaluatedAt = nil // Server-managed err := s.ds.Playlist(ctx).Put(pls) if err != nil { return "", err } return pls.ID, nil } // updatePlaylistEntity updates playlist metadata with permission checks. // Used by the REST API wrapper. func (s *playlists) updatePlaylistEntity(ctx context.Context, id string, entity *model.Playlist, cols ...string) error { current, err := s.checkWritable(ctx, id) if err != nil { switch { case errors.Is(err, model.ErrNotFound): return rest.ErrNotFound case errors.Is(err, model.ErrNotAuthorized): return rest.ErrPermissionDenied default: return err } } usr, _ := request.UserFrom(ctx) if !usr.IsAdmin && entity.OwnerID != "" && entity.OwnerID != current.OwnerID { return rest.ErrPermissionDenied } // Apply ownership change (admin only) if entity.OwnerID != "" { current.OwnerID = entity.OwnerID } // Apply smart playlist rules update current.Rules = entity.Rules return s.updateMetadata(ctx, s.ds, current, &entity.Name, &entity.Comment, &entity.Public) }