Add owner_id to playlist

This commit is contained in:
Deluan 2021-10-29 22:55:28 -04:00 committed by Deluan Quintão
parent 84bbcdbfc2
commit 133fed344f
38 changed files with 173 additions and 100 deletions

View File

@ -136,7 +136,7 @@ func (s *playlists) parseM3U(ctx context.Context, pls *model.Playlist, baseDir s
} }
func (s *playlists) updatePlaylist(ctx context.Context, newPls *model.Playlist) error { func (s *playlists) updatePlaylist(ctx context.Context, newPls *model.Playlist) error {
owner, _ := request.UsernameFrom(ctx) owner, _ := request.UserFrom(ctx)
pls, err := s.ds.Playlist(ctx).FindByPath(newPls.Path) pls, err := s.ds.Playlist(ctx).FindByPath(newPls.Path)
if err != nil && err != model.ErrNotFound { if err != nil && err != model.ErrNotFound {
@ -152,12 +152,12 @@ func (s *playlists) updatePlaylist(ctx context.Context, newPls *model.Playlist)
newPls.ID = pls.ID newPls.ID = pls.ID
newPls.Name = pls.Name newPls.Name = pls.Name
newPls.Comment = pls.Comment newPls.Comment = pls.Comment
newPls.Owner = pls.Owner newPls.OwnerID = pls.OwnerID
newPls.Public = pls.Public newPls.Public = pls.Public
newPls.EvaluatedAt = time.Time{} newPls.EvaluatedAt = time.Time{}
} else { } else {
log.Info(ctx, "Adding synced playlist", "playlist", newPls.Name, "path", newPls.Path, "owner", owner) log.Info(ctx, "Adding synced playlist", "playlist", newPls.Name, "path", newPls.Path, "owner", owner.UserName)
newPls.Owner = owner newPls.OwnerID = owner.ID
} }
return s.ds.Playlist(ctx).Put(newPls) return s.ds.Playlist(ctx).Put(newPls)
} }

View File

@ -0,0 +1,60 @@
package migrations
import (
"database/sql"
"github.com/pressly/goose"
)
func init() {
goose.AddMigration(upAddUseridToPlaylist, downAddUseridToPlaylist)
}
func upAddUseridToPlaylist(tx *sql.Tx) error {
_, err := tx.Exec(`
create table playlist_dg_tmp
(
id varchar(255) not null
primary key,
name varchar(255) default '' not null,
comment varchar(255) default '' not null,
duration real default 0 not null,
song_count integer default 0 not null,
public bool default FALSE not null,
created_at datetime,
updated_at datetime,
path string default '' not null,
sync bool default false not null,
size integer default 0 not null,
rules varchar,
evaluated_at datetime,
owner_id varchar(255) not null
constraint playlist_user_user_id_fk
references user
on update cascade on delete cascade
);
insert into playlist_dg_tmp(id, name, comment, duration, song_count, public, created_at, updated_at, path, sync, size, rules, evaluated_at, owner_id)
select id, name, comment, duration, song_count, public, created_at, updated_at, path, sync, size, rules, evaluated_at,
(select id from user where user_name = owner) as user_id from playlist;
drop table playlist;
alter table playlist_dg_tmp rename to playlist;
create index playlist_created_at
on playlist (created_at);
create index playlist_evaluated_at
on playlist (evaluated_at);
create index playlist_name
on playlist (name);
create index playlist_size
on playlist (size);
create index playlist_updated_at
on playlist (updated_at);
`)
return err
}
func downAddUseridToPlaylist(tx *sql.Tx) error {
return nil
}

View File

@ -15,7 +15,8 @@ type Playlist struct {
Duration float32 `structs:"duration" json:"duration"` Duration float32 `structs:"duration" json:"duration"`
Size int64 `structs:"size" json:"size"` Size int64 `structs:"size" json:"size"`
SongCount int `structs:"song_count" json:"songCount"` SongCount int `structs:"song_count" json:"songCount"`
Owner string `structs:"owner" json:"owner"` OwnerName string `structs:"-" json:"ownerName"`
OwnerID string `structs:"owner_id" json:"ownerId" orm:"column(owner_id)"`
Public bool `structs:"public" json:"public"` Public bool `structs:"public" json:"public"`
Tracks PlaylistTracks `structs:"-" json:"tracks,omitempty"` Tracks PlaylistTracks `structs:"-" json:"tracks,omitempty"`
Path string `structs:"path" json:"path"` Path string `structs:"path" json:"path"`

View File

@ -85,7 +85,14 @@ var _ = Describe("Initialize test DB", func() {
BeforeSuite(func() { BeforeSuite(func() {
o := orm.NewOrm() o := orm.NewOrm()
ctx := log.NewContext(context.TODO()) ctx := log.NewContext(context.TODO())
ctx = request.WithUser(ctx, model.User{ID: "userid", UserName: "userid"}) user := model.User{ID: "userid", UserName: "userid"}
ctx = request.WithUser(ctx, user)
ur := NewUserRepository(ctx, o)
err := ur.Put(&user)
if err != nil {
panic(err)
}
gr := NewGenreRepository(ctx, o) gr := NewGenreRepository(ctx, o)
for i := range testGenres { for i := range testGenres {
@ -126,12 +133,13 @@ var _ = Describe("Initialize test DB", func() {
plsBest = model.Playlist{ plsBest = model.Playlist{
Name: "Best", Name: "Best",
Comment: "No Comments", Comment: "No Comments",
Owner: "userid", OwnerID: "userid",
OwnerName: "userid",
Public: true, Public: true,
SongCount: 2, SongCount: 2,
} }
plsBest.AddTracks([]string{"1001", "1003"}) plsBest.AddTracks([]string{"1001", "1003"})
plsCool = model.Playlist{Name: "Cool", Owner: "userid"} plsCool = model.Playlist{Name: "Cool", OwnerID: "userid", OwnerName: "userid"}
plsCool.AddTracks([]string{"1004"}) plsCool.AddTracks([]string{"1004"})
testPlaylists = []*model.Playlist{&plsBest, &plsCool} testPlaylists = []*model.Playlist{&plsBest, &plsCool}

View File

@ -50,7 +50,7 @@ func (r *playlistRepository) userFilter() Sqlizer {
} }
return Or{ return Or{
Eq{"public": true}, Eq{"public": true},
Eq{"owner": user.UserName}, Eq{"owner_id": user.ID},
} }
} }
@ -70,7 +70,7 @@ func (r *playlistRepository) Delete(id string) error {
if err != nil { if err != nil {
return err return err
} }
if pls.Owner != usr.UserName { if pls.OwnerID != usr.ID {
return rest.ErrPermissionDenied return rest.ErrPermissionDenied
} }
} }
@ -117,11 +117,11 @@ func (r *playlistRepository) Put(p *model.Playlist) error {
} }
func (r *playlistRepository) Get(id string) (*model.Playlist, error) { func (r *playlistRepository) Get(id string) (*model.Playlist, error) {
return r.findBy(And{Eq{"id": id}, r.userFilter()}) return r.findBy(And{Eq{"playlist.id": id}, r.userFilter()})
} }
func (r *playlistRepository) GetWithTracks(id string) (*model.Playlist, error) { func (r *playlistRepository) GetWithTracks(id string) (*model.Playlist, error) {
pls, err := r.findBy(And{Eq{"id": id}, r.userFilter()}) pls, err := r.Get(id)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -140,7 +140,7 @@ func (r *playlistRepository) FindByPath(path string) (*model.Playlist, error) {
} }
func (r *playlistRepository) findBy(sql Sqlizer) (*model.Playlist, error) { func (r *playlistRepository) findBy(sql Sqlizer) (*model.Playlist, error) {
sel := r.newSelect().Columns("*").Where(sql) sel := r.selectPlaylist().Where(sql)
var pls []dbPlaylist var pls []dbPlaylist
err := r.queryAll(sel, &pls) err := r.queryAll(sel, &pls)
if err != nil { if err != nil {
@ -169,7 +169,7 @@ func (r *playlistRepository) toModel(pls dbPlaylist) (*model.Playlist, error) {
} }
func (r *playlistRepository) GetAll(options ...model.QueryOptions) (model.Playlists, error) { func (r *playlistRepository) GetAll(options ...model.QueryOptions) (model.Playlists, error) {
sel := r.newSelect(options...).Columns("*").Where(r.userFilter()) sel := r.selectPlaylist(options...).Where(r.userFilter())
var res []dbPlaylist var res []dbPlaylist
err := r.queryAll(sel, &res) err := r.queryAll(sel, &res)
if err != nil { if err != nil {
@ -186,6 +186,11 @@ func (r *playlistRepository) GetAll(options ...model.QueryOptions) (model.Playli
return playlists, err return playlists, err
} }
func (r *playlistRepository) selectPlaylist(options ...model.QueryOptions) SelectBuilder {
return r.newSelect(options...).Join("user on user.id = owner_id").
Columns(r.tableName+".*", "user.user_name as owner_name")
}
func (r *playlistRepository) refreshSmartPlaylist(pls *model.Playlist) bool { func (r *playlistRepository) refreshSmartPlaylist(pls *model.Playlist) bool {
// Only refresh if it is a smart playlist and was not refreshed in the last 5 seconds // Only refresh if it is a smart playlist and was not refreshed in the last 5 seconds
if !pls.IsSmartPlaylist() || time.Since(pls.EvaluatedAt) < 5*time.Second { if !pls.IsSmartPlaylist() || time.Since(pls.EvaluatedAt) < 5*time.Second {
@ -194,7 +199,7 @@ func (r *playlistRepository) refreshSmartPlaylist(pls *model.Playlist) bool {
// Never refresh other users' playlists // Never refresh other users' playlists
usr := loggedUser(r.ctx) usr := loggedUser(r.ctx)
if pls.Owner != usr.UserName { if pls.OwnerID != usr.ID {
return false return false
} }
@ -362,7 +367,8 @@ func (r *playlistRepository) NewInstance() interface{} {
func (r *playlistRepository) Save(entity interface{}) (string, error) { func (r *playlistRepository) Save(entity interface{}) (string, error) {
pls := entity.(*model.Playlist) pls := entity.(*model.Playlist)
pls.Owner = loggedUser(r.ctx).UserName pls.OwnerID = loggedUser(r.ctx).ID
pls.ID = "" // Make sure we don't override an existing playlist
err := r.Put(pls) err := r.Put(pls)
if err != nil { if err != nil {
return "", err return "", err
@ -373,7 +379,7 @@ func (r *playlistRepository) Save(entity interface{}) (string, error) {
func (r *playlistRepository) Update(entity interface{}, cols ...string) error { func (r *playlistRepository) Update(entity interface{}, cols ...string) error {
pls := entity.(*model.Playlist) pls := entity.(*model.Playlist)
usr := loggedUser(r.ctx) usr := loggedUser(r.ctx)
if !usr.IsAdmin && pls.Owner != usr.UserName { if !usr.IsAdmin && pls.OwnerID != usr.ID {
return rest.ErrPermissionDenied return rest.ErrPermissionDenied
} }
err := r.Put(pls) err := r.Put(pls)
@ -432,7 +438,7 @@ func (r *playlistRepository) isWritable(playlistId string) bool {
return true return true
} }
pls, err := r.Get(playlistId) pls, err := r.Get(playlistId)
return err == nil && pls.Owner == usr.UserName return err == nil && pls.OwnerID == usr.ID
} }
var _ model.PlaylistRepository = (*playlistRepository)(nil) var _ model.PlaylistRepository = (*playlistRepository)(nil)

View File

@ -76,7 +76,7 @@ var _ = Describe("PlaylistRepository", func() {
It("Put/Exists/Delete", func() { It("Put/Exists/Delete", func() {
By("saves the playlist to the DB") By("saves the playlist to the DB")
newPls := model.Playlist{Name: "Great!", Owner: "userid"} newPls := model.Playlist{Name: "Great!", OwnerID: "userid"}
newPls.AddTracks([]string{"1004", "1003"}) newPls.AddTracks([]string{"1004", "1003"})
By("saves the playlist to the DB") By("saves the playlist to the DB")

View File

@ -133,7 +133,7 @@
"fields": { "fields": {
"name": "Název", "name": "Název",
"duration": "Délka", "duration": "Délka",
"owner": "Vlastník", "ownerName": "Vlastník",
"public": "Veřejný", "public": "Veřejný",
"updatedAt": "Nahrán", "updatedAt": "Nahrán",
"createdAt": "Vytvořen", "createdAt": "Vytvořen",
@ -386,4 +386,4 @@
"toggle_love": "Přidat tuto skladbu do oblíbených" "toggle_love": "Přidat tuto skladbu do oblíbených"
} }
} }
} }

View File

@ -110,7 +110,7 @@
"fields": { "fields": {
"name": "Navn", "name": "Navn",
"duration": "Varighed", "duration": "Varighed",
"owner": "Ejer", "ownerName": "Ejer",
"public": "Offentlig", "public": "Offentlig",
"updatedAt": "Opdateret den", "updatedAt": "Opdateret den",
"createdAt": "Oprettet den", "createdAt": "Oprettet den",
@ -324,4 +324,4 @@
"serverUptime": "Server uptime", "serverUptime": "Server uptime",
"serverDown": "OFFLINE" "serverDown": "OFFLINE"
} }
} }

View File

@ -133,7 +133,7 @@
"fields": { "fields": {
"name": "Name", "name": "Name",
"duration": "Dauer", "duration": "Dauer",
"owner": "Inhaber", "ownerName": "Inhaber",
"public": "Öffentlich", "public": "Öffentlich",
"updatedAt": "Aktualisiert um", "updatedAt": "Aktualisiert um",
"createdAt": "Erstellt um", "createdAt": "Erstellt um",
@ -386,4 +386,4 @@
"toggle_love": "Song zu Favoriten hinzufügen" "toggle_love": "Song zu Favoriten hinzufügen"
} }
} }
} }

View File

@ -115,7 +115,7 @@
"fields": { "fields": {
"name": "Nomo", "name": "Nomo",
"duration": "Daŭro", "duration": "Daŭro",
"owner": "Posedanto", "ownerName": "Posedanto",
"public": "Publika", "public": "Publika",
"updatedAt": "Ĝisdatigita je", "updatedAt": "Ĝisdatigita je",
"createdAt": "Kreita je", "createdAt": "Kreita je",
@ -348,4 +348,4 @@
"toggle_love": "Baskuli la stelon de nuna kanto" "toggle_love": "Baskuli la stelon de nuna kanto"
} }
} }
} }

View File

@ -133,7 +133,7 @@
"fields": { "fields": {
"name": "Nombre", "name": "Nombre",
"duration": "Duración", "duration": "Duración",
"owner": "Dueño", "ownerName": "Dueño",
"public": "Público", "public": "Público",
"updatedAt": "Actualizado el", "updatedAt": "Actualizado el",
"createdAt": "Creado el", "createdAt": "Creado el",
@ -386,4 +386,4 @@
"toggle_love": "Marca esta canción como favorita" "toggle_love": "Marca esta canción como favorita"
} }
} }
} }

View File

@ -133,7 +133,7 @@
"fields": { "fields": {
"name": "نام", "name": "نام",
"duration": "مدّت زمان", "duration": "مدّت زمان",
"owner": "مالک", "ownerName": "مالک",
"public": "عمومی", "public": "عمومی",
"updatedAt": "بروزشده در", "updatedAt": "بروزشده در",
"createdAt": "ایجادشده در", "createdAt": "ایجادشده در",

View File

@ -133,7 +133,7 @@
"fields": { "fields": {
"name": "Nimi", "name": "Nimi",
"duration": "Kesto", "duration": "Kesto",
"owner": "Omistaja", "ownerName": "Omistaja",
"public": "Julkinen", "public": "Julkinen",
"updatedAt": "Päivitetty", "updatedAt": "Päivitetty",
"createdAt": "Luotu", "createdAt": "Luotu",
@ -386,4 +386,4 @@
"toggle_love": "Lisää kappale suosikkeihin" "toggle_love": "Lisää kappale suosikkeihin"
} }
} }
} }

View File

@ -133,7 +133,7 @@
"fields": { "fields": {
"name": "Nom", "name": "Nom",
"duration": "Durée", "duration": "Durée",
"owner": "Propriétaire", "ownerName": "Propriétaire",
"public": "Public", "public": "Public",
"updatedAt": "Mise à jour le", "updatedAt": "Mise à jour le",
"createdAt": "Créé le", "createdAt": "Créé le",
@ -386,4 +386,4 @@
"toggle_love": "Ajouter/Enlever le morceau des favoris" "toggle_love": "Ajouter/Enlever le morceau des favoris"
} }
} }
} }

View File

@ -133,7 +133,7 @@
"fields": { "fields": {
"name": "Nome", "name": "Nome",
"duration": "Durata", "duration": "Durata",
"owner": "Creatore", "ownerName": "Creatore",
"public": "Pubblica", "public": "Pubblica",
"updatedAt": "Ultimo aggiornamento", "updatedAt": "Ultimo aggiornamento",
"createdAt": "Data creazione", "createdAt": "Data creazione",
@ -386,4 +386,4 @@
"toggle_love": "Aggiungi questa traccia ai preferiti" "toggle_love": "Aggiungi questa traccia ai preferiti"
} }
} }
} }

View File

@ -133,7 +133,7 @@
"fields": { "fields": {
"name": "名前", "name": "名前",
"duration": "時間", "duration": "時間",
"owner": "所有者", "ownerName": "所有者",
"public": "公開", "public": "公開",
"updatedAt": "更新日", "updatedAt": "更新日",
"createdAt": "作成日", "createdAt": "作成日",
@ -386,4 +386,4 @@
"toggle_love": "星の付け外し" "toggle_love": "星の付け外し"
} }
} }
} }

View File

@ -130,7 +130,7 @@
"fields": { "fields": {
"name": "Titel", "name": "Titel",
"duration": "Lengte", "duration": "Lengte",
"owner": "Eigenaar", "ownerName": "Eigenaar",
"public": "Publiek", "public": "Publiek",
"updatedAt": "Laatst gewijzigd op", "updatedAt": "Laatst gewijzigd op",
"createdAt": "Aangemaakt op", "createdAt": "Aangemaakt op",
@ -380,4 +380,4 @@
"toggle_love": "Voeg toe aan favorieten" "toggle_love": "Voeg toe aan favorieten"
} }
} }
} }

View File

@ -130,7 +130,7 @@
"fields": { "fields": {
"name": "Nazwa", "name": "Nazwa",
"duration": "Czas trwania", "duration": "Czas trwania",
"owner": "Właściciel", "ownerName": "Właściciel",
"public": "Publiczna", "public": "Publiczna",
"updatedAt": "Zaktualizowana", "updatedAt": "Zaktualizowana",
"createdAt": "Stworzona", "createdAt": "Stworzona",
@ -380,4 +380,4 @@
"toggle_love": "Dodaj ten utwór do ulubionych" "toggle_love": "Dodaj ten utwór do ulubionych"
} }
} }
} }

View File

@ -138,7 +138,7 @@
"fields": { "fields": {
"name": "Nome", "name": "Nome",
"duration": "Duração", "duration": "Duração",
"owner": "Dono", "ownerName": "Dono",
"public": "Pública", "public": "Pública",
"updatedAt": "Últ. Atualização", "updatedAt": "Últ. Atualização",
"createdAt": "Data de Criação ", "createdAt": "Data de Criação ",

View File

@ -133,7 +133,7 @@
"fields": { "fields": {
"name": "Название", "name": "Название",
"duration": "Длительность", "duration": "Длительность",
"owner": "Владелец", "ownerName": "Владелец",
"public": "Публичный", "public": "Публичный",
"updatedAt": "Обновлен", "updatedAt": "Обновлен",
"createdAt": "Создан", "createdAt": "Создан",
@ -386,4 +386,4 @@
"toggle_love": "Добавить / удалить песню из избранного" "toggle_love": "Добавить / удалить песню из избранного"
} }
} }
} }

View File

@ -133,7 +133,7 @@
"fields": { "fields": {
"name": "Ime", "name": "Ime",
"duration": "Dolžina", "duration": "Dolžina",
"owner": "Lastnik", "ownerName": "Lastnik",
"public": "Javno", "public": "Javno",
"updatedAt": "Posodobljen", "updatedAt": "Posodobljen",
"createdAt": "Ustvarjen", "createdAt": "Ustvarjen",
@ -386,4 +386,4 @@
"toggle_love": "Dodaj med priljubljene" "toggle_love": "Dodaj med priljubljene"
} }
} }
} }

View File

@ -127,7 +127,7 @@
"fields": { "fields": {
"name": "Namn", "name": "Namn",
"duration": "Längd", "duration": "Längd",
"owner": "Ägare", "ownerName": "Ägare",
"public": "Offentlig", "public": "Offentlig",
"updatedAt": "Uppdaterad", "updatedAt": "Uppdaterad",
"createdAt": "Skapad", "createdAt": "Skapad",

View File

@ -133,7 +133,7 @@
"fields": { "fields": {
"name": "ชื่อ", "name": "ชื่อ",
"duration": "เวลา", "duration": "เวลา",
"owner": "เจ้าของ", "ownerName": "เจ้าของ",
"public": "สาธารณะ", "public": "สาธารณะ",
"updatedAt": "อัปเดตเมื่อ", "updatedAt": "อัปเดตเมื่อ",
"createdAt": "สร้างขึ้นเมื่อ", "createdAt": "สร้างขึ้นเมื่อ",
@ -386,4 +386,4 @@
"toggle_love": "เพิ่มเพลงนี้ไปยังรายการโปรด" "toggle_love": "เพิ่มเพลงนี้ไปยังรายการโปรด"
} }
} }
} }

View File

@ -110,7 +110,7 @@
"fields": { "fields": {
"name": "Isim", "name": "Isim",
"duration": "Süre", "duration": "Süre",
"owner": "Sahibi", "ownerName": "Sahibi",
"public": "Görülebilir", "public": "Görülebilir",
"updatedAt": "Güncelleme tarihi:", "updatedAt": "Güncelleme tarihi:",
"createdAt": "Oluşturma tarihi:", "createdAt": "Oluşturma tarihi:",
@ -324,4 +324,4 @@
"serverUptime": "", "serverUptime": "",
"serverDown": "" "serverDown": ""
} }
} }

View File

@ -130,7 +130,7 @@
"fields": { "fields": {
"name": "Назва", "name": "Назва",
"duration": "Тривалість", "duration": "Тривалість",
"owner": "Власник", "ownerName": "Власник",
"public": "Публічний", "public": "Публічний",
"updatedAt": "Оновлено", "updatedAt": "Оновлено",
"createdAt": "Створено", "createdAt": "Створено",
@ -380,4 +380,4 @@
"toggle_love": "Відмітити поточні пісні" "toggle_love": "Відмітити поточні пісні"
} }
} }
} }

View File

@ -130,7 +130,7 @@
"fields": { "fields": {
"name": "名称", "name": "名称",
"duration": "时长", "duration": "时长",
"owner": "所有者", "ownerName": "所有者",
"public": "公开", "public": "公开",
"updatedAt": "更新于", "updatedAt": "更新于",
"createdAt": "创建于", "createdAt": "创建于",
@ -380,4 +380,4 @@
"toggle_love": "添加/移除星标" "toggle_love": "添加/移除星标"
} }
} }
} }

View File

@ -133,7 +133,7 @@
"fields": { "fields": {
"name": "名稱", "name": "名稱",
"duration": "長度", "duration": "長度",
"owner": "擁有者", "ownerName": "擁有者",
"public": "公開", "public": "公開",
"updatedAt": "更新於", "updatedAt": "更新於",
"createdAt": "創建於", "createdAt": "創建於",
@ -386,4 +386,4 @@
"toggle_love": "添加或移除星標" "toggle_love": "添加或移除星標"
} }
} }
} }

View File

@ -64,12 +64,12 @@ func (e subError) Error() string {
return msg return msg
} }
func getUser(ctx context.Context) string { func getUser(ctx context.Context) model.User {
user, ok := request.UserFrom(ctx) user, ok := request.UserFrom(ctx)
if ok { if ok {
return user.UserName return user
} }
return "" return model.User{}
} }
func toArtists(ctx context.Context, artists model.Artists) []responses.Artist { func toArtists(ctx context.Context, artists model.Artists) []responses.Artist {

View File

@ -74,14 +74,12 @@ func (c *PlaylistsController) create(ctx context.Context, playlistId, name strin
if err != nil { if err != nil {
return err return err
} }
if owner != pls.Owner { if owner.ID != pls.OwnerID {
return model.ErrNotAuthorized return model.ErrNotAuthorized
} }
} else { } else {
pls = &model.Playlist{ pls = &model.Playlist{Name: name}
Name: name, pls.OwnerID = owner.ID
Owner: owner,
}
} }
pls.Tracks = nil pls.Tracks = nil
pls.AddTracks(ids) pls.AddTracks(ids)
@ -178,7 +176,7 @@ func (c *PlaylistsController) buildPlaylist(p model.Playlist) *responses.Playlis
pls.Name = p.Name pls.Name = p.Name
pls.Comment = p.Comment pls.Comment = p.Comment
pls.SongCount = p.SongCount pls.SongCount = p.SongCount
pls.Owner = p.Owner pls.Owner = p.OwnerName
pls.Duration = int(p.Duration) pls.Duration = int(p.Duration)
pls.Public = p.Public pls.Public = p.Public
pls.Created = p.CreatedAt pls.Created = p.CreatedAt

View File

@ -1,19 +1,19 @@
import { cloneElement, Children, isValidElement } from 'react' import { cloneElement, Children, isValidElement } from 'react'
export const isWritable = (owner) => { export const isWritable = (ownerId) => {
return ( return (
localStorage.getItem('username') === owner || localStorage.getItem('userId') === ownerId ||
localStorage.getItem('role') === 'admin' localStorage.getItem('role') === 'admin'
) )
} }
export const isReadOnly = (owner) => { export const isReadOnly = (ownerId) => {
return !isWritable(owner) return !isWritable(ownerId)
} }
export const Writable = (props) => { export const Writable = (props) => {
const { record = {}, children } = props const { record = {}, children } = props
if (isWritable(record.owner)) { if (isWritable(record.ownerId)) {
return Children.map(children, (child) => return Children.map(children, (child) =>
isValidElement(child) ? cloneElement(child, props) : child isValidElement(child) ? cloneElement(child, props) : child
) )
@ -24,4 +24,4 @@ export const Writable = (props) => {
export const isSmartPlaylist = (pls) => !!pls.rules export const isSmartPlaylist = (pls) => !!pls.rules
export const canChangeTracks = (pls) => export const canChangeTracks = (pls) =>
isWritable(pls.owner) && !isSmartPlaylist(pls) isWritable(pls.ownerId) && !isSmartPlaylist(pls)

View File

@ -22,7 +22,8 @@ export const useSelectedFields = ({
useEffect(() => { useEffect(() => {
if ( if (
!resourceFields || !resourceFields ||
Object.keys(resourceFields).length !== Object.keys(columns).length Object.keys(resourceFields).length !== Object.keys(columns).length ||
!Object.keys(columns).every((c) => c in resourceFields)
) { ) {
const obj = {} const obj = {}
for (const key of Object.keys(columns)) { for (const key of Object.keys(columns)) {

View File

@ -5,22 +5,23 @@ import { cleanup, fireEvent, render, waitFor } from '@testing-library/react'
import { AddToPlaylistDialog } from './AddToPlaylistDialog' import { AddToPlaylistDialog } from './AddToPlaylistDialog'
describe('AddToPlaylistDialog', () => { describe('AddToPlaylistDialog', () => {
beforeAll(() => localStorage.setItem('userId', 'admin'))
afterEach(cleanup) afterEach(cleanup)
const mockData = [ const mockData = [
{ id: 'sample-id1', name: 'sample playlist 1', owner: 'admin' }, { id: 'sample-id1', name: 'sample playlist 1', ownerId: 'admin' },
{ id: 'sample-id2', name: 'sample playlist 2', owner: 'admin' }, { id: 'sample-id2', name: 'sample playlist 2', ownerId: 'admin' },
] ]
const mockIndexedData = { const mockIndexedData = {
'sample-id1': { 'sample-id1': {
id: 'sample-id1', id: 'sample-id1',
name: 'sample playlist 1', name: 'sample playlist 1',
owner: 'admin', ownerId: 'admin',
}, },
'sample-id2': { 'sample-id2': {
id: 'sample-id2', id: 'sample-id2',
name: 'sample playlist 2', name: 'sample playlist 2',
owner: 'admin', ownerId: 'admin',
}, },
} }
const selectedIds = ['song-1', 'song-2'] const selectedIds = ['song-1', 'song-2']

View File

@ -30,7 +30,7 @@ export const SelectPlaylistInput = ({ onChange }) => {
const options = const options =
ids && ids &&
ids.map((id) => data[id]).filter((option) => isWritable(option.owner)) ids.map((id) => data[id]).filter((option) => isWritable(option.ownerId))
const handleOnChange = (event, newValue) => { const handleOnChange = (event, newValue) => {
let newState = [] let newState = []

View File

@ -5,24 +5,25 @@ import { cleanup, fireEvent, render, waitFor } from '@testing-library/react'
import { SelectPlaylistInput } from './SelectPlaylistInput' import { SelectPlaylistInput } from './SelectPlaylistInput'
describe('SelectPlaylistInput', () => { describe('SelectPlaylistInput', () => {
beforeAll(() => localStorage.setItem('userId', 'admin'))
afterEach(cleanup) afterEach(cleanup)
const onChangeHandler = jest.fn() const onChangeHandler = jest.fn()
it('should call the handler with the selections', async () => { it('should call the handler with the selections', async () => {
const mockData = [ const mockData = [
{ id: 'sample-id1', name: 'sample playlist 1', owner: 'admin' }, { id: 'sample-id1', name: 'sample playlist 1', ownerId: 'admin' },
{ id: 'sample-id2', name: 'sample playlist 2', owner: 'admin' }, { id: 'sample-id2', name: 'sample playlist 2', ownerId: 'admin' },
] ]
const mockIndexedData = { const mockIndexedData = {
'sample-id1': { 'sample-id1': {
id: 'sample-id1', id: 'sample-id1',
name: 'sample playlist 1', name: 'sample playlist 1',
owner: 'admin', ownerId: 'admin',
}, },
'sample-id2': { 'sample-id2': {
id: 'sample-id2', id: 'sample-id2',
name: 'sample playlist 2', name: 'sample playlist 2',
owner: 'admin', ownerId: 'admin',
}, },
} }
@ -74,7 +75,7 @@ describe('SelectPlaylistInput', () => {
fireEvent.keyDown(document.activeElement, { key: 'Enter' }) fireEvent.keyDown(document.activeElement, { key: 'Enter' })
await waitFor(() => { await waitFor(() => {
expect(onChangeHandler).toHaveBeenCalledWith([ expect(onChangeHandler).toHaveBeenCalledWith([
{ id: 'sample-id1', name: 'sample playlist 1', owner: 'admin' }, { id: 'sample-id1', name: 'sample playlist 1', ownerId: 'admin' },
]) ])
}) })
@ -82,8 +83,8 @@ describe('SelectPlaylistInput', () => {
fireEvent.keyDown(document.activeElement, { key: 'Enter' }) fireEvent.keyDown(document.activeElement, { key: 'Enter' })
await waitFor(() => { await waitFor(() => {
expect(onChangeHandler).toHaveBeenCalledWith([ expect(onChangeHandler).toHaveBeenCalledWith([
{ id: 'sample-id1', name: 'sample playlist 1', owner: 'admin' }, { id: 'sample-id1', name: 'sample playlist 1', ownerId: 'admin' },
{ id: 'sample-id2', name: 'sample playlist 2', owner: 'admin' }, { id: 'sample-id2', name: 'sample playlist 2', ownerId: 'admin' },
]) ])
}) })
@ -94,8 +95,8 @@ describe('SelectPlaylistInput', () => {
fireEvent.keyDown(document.activeElement, { key: 'Enter' }) fireEvent.keyDown(document.activeElement, { key: 'Enter' })
await waitFor(() => { await waitFor(() => {
expect(onChangeHandler).toHaveBeenCalledWith([ expect(onChangeHandler).toHaveBeenCalledWith([
{ id: 'sample-id1', name: 'sample playlist 1', owner: 'admin' }, { id: 'sample-id1', name: 'sample playlist 1', ownerId: 'admin' },
{ id: 'sample-id2', name: 'sample playlist 2', owner: 'admin' }, { id: 'sample-id2', name: 'sample playlist 2', ownerId: 'admin' },
{ name: 'new playlist' }, { name: 'new playlist' },
]) ])
}) })
@ -106,8 +107,8 @@ describe('SelectPlaylistInput', () => {
fireEvent.keyDown(document.activeElement, { key: 'Enter' }) fireEvent.keyDown(document.activeElement, { key: 'Enter' })
await waitFor(() => { await waitFor(() => {
expect(onChangeHandler).toHaveBeenCalledWith([ expect(onChangeHandler).toHaveBeenCalledWith([
{ id: 'sample-id1', name: 'sample playlist 1', owner: 'admin' }, { id: 'sample-id1', name: 'sample playlist 1', ownerId: 'admin' },
{ id: 'sample-id2', name: 'sample playlist 2', owner: 'admin' }, { id: 'sample-id2', name: 'sample playlist 2', ownerId: 'admin' },
{ name: 'new playlist' }, { name: 'new playlist' },
{ name: 'another new playlist' }, { name: 'another new playlist' },
]) ])

View File

@ -138,7 +138,7 @@
"fields": { "fields": {
"name": "Name", "name": "Name",
"duration": "Duration", "duration": "Duration",
"owner": "Owner", "ownerName": "Owner",
"public": "Public", "public": "Public",
"updatedAt": "Updated at", "updatedAt": "Updated at",
"createdAt": "Created at", "createdAt": "Created at",

View File

@ -74,7 +74,7 @@ const PlaylistsSubMenu = ({ state, setState, sidebarIsOpen, dense }) => {
/> />
) )
const user = localStorage.getItem('username') const userId = localStorage.getItem('userId')
const myPlaylists = [] const myPlaylists = []
const sharedPlaylists = [] const sharedPlaylists = []
@ -82,7 +82,7 @@ const PlaylistsSubMenu = ({ state, setState, sidebarIsOpen, dense }) => {
const allPlaylists = Object.keys(data).map((id) => data[id]) const allPlaylists = Object.keys(data).map((id) => data[id])
allPlaylists.forEach((pls) => { allPlaylists.forEach((pls) => {
if (user === pls.owner) { if (userId === pls.ownerId) {
myPlaylists.push(pls) myPlaylists.push(pls)
} else { } else {
sharedPlaylists.push(pls) sharedPlaylists.push(pls)

View File

@ -35,7 +35,7 @@ const PlaylistEditForm = (props) => {
<TextInput multiline source="comment" /> <TextInput multiline source="comment" />
<BooleanInput <BooleanInput
source="public" source="public"
disabled={!isWritable(record.owner) || isSmartPlaylist(record)} disabled={!isWritable(record.ownerId) || isSmartPlaylist(record)}
/> />
<FormDataConsumer> <FormDataConsumer>
{(formDataProps) => <SyncFragment {...formDataProps} />} {(formDataProps) => <SyncFragment {...formDataProps} />}

View File

@ -58,7 +58,7 @@ const TogglePublicInput = ({ resource, source }) => {
<Switch <Switch
checked={record[source]} checked={record[source]}
onClick={handleClick} onClick={handleClick}
disabled={!isWritable(record.owner) || isSmartPlaylist(record)} disabled={!isWritable(record.ownerId) || isSmartPlaylist(record)}
/> />
) )
} }
@ -70,7 +70,7 @@ const PlaylistList = (props) => {
const toggleableFields = useMemo(() => { const toggleableFields = useMemo(() => {
return { return {
owner: <TextField source="owner" />, ownerName: <TextField source="ownerName" />,
songCount: isDesktop && <NumberField source="songCount" />, songCount: isDesktop && <NumberField source="songCount" />,
duration: isDesktop && <DurationField source="duration" />, duration: isDesktop && <DurationField source="duration" />,
updatedAt: isDesktop && ( updatedAt: isDesktop && (
@ -94,10 +94,7 @@ const PlaylistList = (props) => {
filters={<PlaylistFilter />} filters={<PlaylistFilter />}
actions={<PlaylistListActions />} actions={<PlaylistListActions />}
> >
<Datagrid <Datagrid rowClick="show" isRowSelectable={(r) => isWritable(r?.ownerId)}>
rowClick="show"
isRowSelectable={(r) => isWritable(r && r.owner)}
>
<TextField source="name" /> <TextField source="name" />
{columns} {columns}
<Writable> <Writable>