From 94d88395e73c42139445afff5bebfaeeff7c75de Mon Sep 17 00:00:00 2001 From: Deluan Date: Mon, 8 Jun 2020 17:29:09 -0400 Subject: [PATCH] Add referential integrity to player and playlist tables --- Makefile | 5 + consts/consts.go | 2 +- db/db.go | 2 +- .../20200608153717_referential_integrity.go | 123 ++++++++++++++++++ persistence/player_repository.go | 13 +- reflex.conf | 2 +- 6 files changed, 142 insertions(+), 5 deletions(-) create mode 100644 db/migration/20200608153717_referential_integrity.go diff --git a/Makefile b/Makefile index a0e33a80..c9534cd9 100644 --- a/Makefile +++ b/Makefile @@ -37,6 +37,11 @@ update-snapshots: check_go_env UPDATE_SNAPSHOTS=true ginkgo ./server/subsonic/... .PHONY: update-snapshots +create-migration: + @if [ -z "${name}" ]; then echo "Usage: make create-migration name=name_of_migration_file"; exit 1; fi + goose -dir db/migration create ${name} +.PHONY: create-migration + setup: @which go-bindata || (echo "Installing BinData" && GO111MODULE=off go get -u github.com/go-bindata/go-bindata/...) go mod download diff --git a/consts/consts.go b/consts/consts.go index 5c049950..8e44280d 100644 --- a/consts/consts.go +++ b/consts/consts.go @@ -11,7 +11,7 @@ const ( AppName = "navidrome" LocalConfigFile = "./navidrome.toml" - DefaultDbPath = "navidrome.db?cache=shared&_busy_timeout=15000&_journal_mode=WAL" + DefaultDbPath = "navidrome.db?cache=shared&_busy_timeout=15000&_journal_mode=WAL&_foreign_keys=on" InitialSetupFlagKey = "InitialSetup" UIAuthorizationHeader = "X-ND-Authorization" diff --git a/db/db.go b/db/db.go index 8463e416..691eaa11 100644 --- a/db/db.go +++ b/db/db.go @@ -27,7 +27,7 @@ func Db() *sql.DB { var err error Path = conf.Server.DbPath if Path == ":memory:" { - Path = "file::memory:?cache=shared" + Path = "file::memory:?cache=shared&_foreign_keys=on" conf.Server.DbPath = Path } log.Debug("Opening DataBase", "dbPath", Path, "driver", Driver) diff --git a/db/migration/20200608153717_referential_integrity.go b/db/migration/20200608153717_referential_integrity.go new file mode 100644 index 00000000..4d54f5f1 --- /dev/null +++ b/db/migration/20200608153717_referential_integrity.go @@ -0,0 +1,123 @@ +package migration + +import ( + "database/sql" + + "github.com/pressly/goose" +) + +func init() { + goose.AddMigration(Up20200608153717, Down20200608153717) +} + +func Up20200608153717(tx *sql.Tx) error { + // First delete dangling players + _, err := tx.Exec(` +delete from player where user_name not in (select user_name from user)`) + if err != nil { + return err + } + + // Add foreign key to player table + err = updatePlayer_20200608153717(tx) + if err != nil { + return err + } + + // Add foreign key to playlist table + err = updatePlaylist_20200608153717(tx) + if err != nil { + return err + } + + // Add foreign keys to playlist_tracks table + return updatePlaylistTracks_20200608153717(tx) +} + +func updatePlayer_20200608153717(tx *sql.Tx) error { + _, err := tx.Exec(` +create table player_dg_tmp +( + id varchar(255) not null + primary key, + name varchar not null + unique, + type varchar, + user_name varchar not null + references user (user_name) + on update cascade on delete cascade, + client varchar not null, + ip_address varchar, + last_seen timestamp, + max_bit_rate int default 0, + transcoding_id varchar null +); + +insert into player_dg_tmp(id, name, type, user_name, client, ip_address, last_seen, max_bit_rate, transcoding_id) select id, name, type, user_name, client, ip_address, last_seen, max_bit_rate, transcoding_id from player; + +drop table player; + +alter table player_dg_tmp rename to player; +`) + return err +} + +func updatePlaylist_20200608153717(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, + owner varchar(255) default '' not null + constraint playlist_user_user_name_fk + references user (user_name) + on update cascade on delete cascade, + public bool default FALSE not null, + created_at datetime, + updated_at datetime +); + +insert into playlist_dg_tmp(id, name, comment, duration, song_count, owner, public, created_at, updated_at) select id, name, comment, duration, song_count, owner, public, created_at, updated_at from playlist; + +drop table playlist; + +alter table playlist_dg_tmp rename to playlist; + +create index playlist_name + on playlist (name); +`) + return err +} + +func updatePlaylistTracks_20200608153717(tx *sql.Tx) error { + _, err := tx.Exec(` +create table playlist_tracks_dg_tmp +( + id integer default 0 not null, + playlist_id varchar(255) not null + constraint playlist_tracks_playlist_id_fk + references playlist + on update cascade on delete cascade, + media_file_id varchar(255) not null +); + +insert into playlist_tracks_dg_tmp(id, playlist_id, media_file_id) select id, playlist_id, media_file_id from playlist_tracks; + +drop table playlist_tracks; + +alter table playlist_tracks_dg_tmp rename to playlist_tracks; + +create unique index playlist_tracks_pos + on playlist_tracks (playlist_id, id); + +`) + return err +} + +func Down20200608153717(tx *sql.Tx) error { + return nil +} diff --git a/persistence/player_repository.go b/persistence/player_repository.go index 85f9219c..a1e87d0f 100644 --- a/persistence/player_repository.go +++ b/persistence/player_repository.go @@ -46,11 +46,19 @@ func (r *playerRepository) FindByName(client, userName string) (*model.Player, e func (r *playerRepository) newRestSelect(options ...model.QueryOptions) SelectBuilder { s := r.newSelect(options...) + return s.Where(r.addRestriction()) +} + +func (r *playerRepository) addRestriction(sql ...Sqlizer) Sqlizer { + s := And{} + if len(sql) > 0 { + s = append(s, sql[0]) + } u := loggedUser(r.ctx) if u.IsAdmin { return s } - return s.Where(Eq{"user_name": u.UserName}) + return append(s, Eq{"user_name": u.UserName}) } func (r *playerRepository) Count(options ...rest.QueryOptions) (int64, error) { @@ -109,7 +117,8 @@ func (r *playerRepository) Update(entity interface{}, cols ...string) error { } func (r *playerRepository) Delete(id string) error { - err := r.delete(And{Eq{"id": id}, Eq{"user_name": loggedUser(r.ctx).UserName}}) + filter := r.addRestriction(And{Eq{"id": id}}) + err := r.delete(filter) if err == model.ErrNotFound { return rest.ErrNotFound } diff --git a/reflex.conf b/reflex.conf index 89c9ac18..fc43f934 100644 --- a/reflex.conf +++ b/reflex.conf @@ -1 +1 @@ --s -r "(\.go$$|navidrome.toml|resources)" -R "(Jamstash-master|^ui|^data)" -- go run . +-s -r "(\.go$$|navidrome.toml|resources)" -R "(Jamstash-master|^ui|^data|^db/migration)" -- go run .