diff --git a/graphql/documents/mutations/performer.graphql b/graphql/documents/mutations/performer.graphql index d1a8489dd..a74a3a5f7 100644 --- a/graphql/documents/mutations/performer.graphql +++ b/graphql/documents/mutations/performer.graphql @@ -82,4 +82,8 @@ mutation PerformerUpdate( }) { ...PerformerData } +} + +mutation PerformerDestroy($id: ID!) { + performerDestroy(input: { id: $id }) } \ No newline at end of file diff --git a/graphql/documents/mutations/studio.graphql b/graphql/documents/mutations/studio.graphql index b9ad5cded..d0032c8e2 100644 --- a/graphql/documents/mutations/studio.graphql +++ b/graphql/documents/mutations/studio.graphql @@ -17,4 +17,8 @@ mutation StudioUpdate( studioUpdate(input: { id: $id, name: $name, url: $url, image: $image }) { ...StudioData } +} + +mutation StudioDestroy($id: ID!) { + studioDestroy(input: { id: $id }) } \ No newline at end of file diff --git a/graphql/schema/schema.graphql b/graphql/schema/schema.graphql index 80c473069..42911190c 100644 --- a/graphql/schema/schema.graphql +++ b/graphql/schema/schema.graphql @@ -80,9 +80,11 @@ type Mutation { performerCreate(input: PerformerCreateInput!): Performer performerUpdate(input: PerformerUpdateInput!): Performer + performerDestroy(input: PerformerDestroyInput!): Boolean! studioCreate(input: StudioCreateInput!): Studio studioUpdate(input: StudioUpdateInput!): Studio + studioDestroy(input: StudioDestroyInput!): Boolean! tagCreate(input: TagCreateInput!): Tag tagUpdate(input: TagUpdateInput!): Tag diff --git a/graphql/schema/types/performer.graphql b/graphql/schema/types/performer.graphql index 38f8de897..af52b704c 100644 --- a/graphql/schema/types/performer.graphql +++ b/graphql/schema/types/performer.graphql @@ -66,6 +66,10 @@ input PerformerUpdateInput { image: String } +input PerformerDestroyInput { + id: ID! +} + type FindPerformersResultType { count: Int! performers: [Performer!]! diff --git a/graphql/schema/types/studio.graphql b/graphql/schema/types/studio.graphql index 294ea53ed..bda41b914 100644 --- a/graphql/schema/types/studio.graphql +++ b/graphql/schema/types/studio.graphql @@ -23,6 +23,10 @@ input StudioUpdateInput { image: String } +input StudioDestroyInput { + id: ID! +} + type FindStudiosResultType { count: Int! studios: [Studio!]! diff --git a/pkg/api/resolver_mutation_performer.go b/pkg/api/resolver_mutation_performer.go index 37222b2d3..54a7693e4 100644 --- a/pkg/api/resolver_mutation_performer.go +++ b/pkg/api/resolver_mutation_performer.go @@ -175,3 +175,17 @@ func (r *mutationResolver) PerformerUpdate(ctx context.Context, input models.Per return performer, nil } + +func (r *mutationResolver) PerformerDestroy(ctx context.Context, input models.PerformerDestroyInput) (bool, error) { + qb := models.NewPerformerQueryBuilder() + tx := database.DB.MustBeginTx(ctx, nil) + if err := qb.Destroy(input.ID, tx); err != nil { + _ = tx.Rollback() + return false, err + } + if err := tx.Commit(); err != nil { + return false, err + } + return true, nil +} + diff --git a/pkg/api/resolver_mutation_studio.go b/pkg/api/resolver_mutation_studio.go index 52745eb0b..c79622448 100644 --- a/pkg/api/resolver_mutation_studio.go +++ b/pkg/api/resolver_mutation_studio.go @@ -3,11 +3,12 @@ package api import ( "context" "database/sql" + "strconv" + "time" + "github.com/stashapp/stash/pkg/database" "github.com/stashapp/stash/pkg/models" "github.com/stashapp/stash/pkg/utils" - "strconv" - "time" ) func (r *mutationResolver) StudioCreate(ctx context.Context, input models.StudioCreateInput) (*models.Studio, error) { @@ -85,3 +86,16 @@ func (r *mutationResolver) StudioUpdate(ctx context.Context, input models.Studio return studio, nil } + +func (r *mutationResolver) StudioDestroy(ctx context.Context, input models.StudioDestroyInput) (bool, error) { + qb := models.NewStudioQueryBuilder() + tx := database.DB.MustBeginTx(ctx, nil) + if err := qb.Destroy(input.ID, tx); err != nil { + _ = tx.Rollback() + return false, err + } + if err := tx.Commit(); err != nil { + return false, err + } + return true, nil +} diff --git a/pkg/ffmpeg/ffprobe.go b/pkg/ffmpeg/ffprobe.go index c1ba4d95c..98f2f9dc3 100644 --- a/pkg/ffmpeg/ffprobe.go +++ b/pkg/ffmpeg/ffprobe.go @@ -6,6 +6,7 @@ import ( "math" "os" "os/exec" + "path/filepath" "strconv" "strings" "time" @@ -28,6 +29,8 @@ type VideoFile struct { VideoStream *FFProbeStream Path string + Title string + Comment string Container string Duration float64 StartTime float64 @@ -82,6 +85,14 @@ func parse(filePath string, probeJSON *FFProbeJSON) (*VideoFile, error) { //} // TODO nil_or_unsupported.(video_stream) && nil_or_unsupported.(audio_stream) result.Path = filePath + result.Title = probeJSON.Format.Tags.Title + + if result.Title == "" { + // default title to filename + result.Title = filepath.Base(result.Path) + } + + result.Comment = probeJSON.Format.Tags.Comment result.Bitrate, _ = strconv.ParseInt(probeJSON.Format.BitRate, 10, 64) result.Container = probeJSON.Format.FormatName diff --git a/pkg/ffmpeg/types.go b/pkg/ffmpeg/types.go index 6f85e21c3..ed3fadf67 100644 --- a/pkg/ffmpeg/types.go +++ b/pkg/ffmpeg/types.go @@ -22,6 +22,8 @@ type FFProbeJSON struct { Encoder string `json:"encoder"` MajorBrand string `json:"major_brand"` MinorVersion string `json:"minor_version"` + Title string `json:"title"` + Comment string `json:"comment"` } `json:"tags"` } `json:"format"` Streams []FFProbeStream `json:"streams"` diff --git a/pkg/manager/manager_tasks.go b/pkg/manager/manager_tasks.go index 99f28a6f9..3d64e1c03 100644 --- a/pkg/manager/manager_tasks.go +++ b/pkg/manager/manager_tasks.go @@ -1,13 +1,14 @@ package manager import ( + "path/filepath" + "sync" + "github.com/bmatcuk/doublestar" "github.com/stashapp/stash/pkg/logger" "github.com/stashapp/stash/pkg/manager/config" "github.com/stashapp/stash/pkg/models" "github.com/stashapp/stash/pkg/utils" - "path/filepath" - "sync" ) func (s *singleton) Scan() { @@ -34,6 +35,8 @@ func (s *singleton) Scan() { go task.Start(&wg) wg.Wait() } + + logger.Info("Finished scan") }() } diff --git a/pkg/manager/task_scan.go b/pkg/manager/task_scan.go index 19a016e97..89c563a78 100644 --- a/pkg/manager/task_scan.go +++ b/pkg/manager/task_scan.go @@ -3,15 +3,16 @@ package manager import ( "context" "database/sql" + "path/filepath" + "strconv" + "sync" + "time" + "github.com/stashapp/stash/pkg/database" "github.com/stashapp/stash/pkg/ffmpeg" "github.com/stashapp/stash/pkg/logger" "github.com/stashapp/stash/pkg/models" "github.com/stashapp/stash/pkg/utils" - "path/filepath" - "strconv" - "sync" - "time" ) type ScanTask struct { @@ -46,9 +47,15 @@ func (t *ScanTask) scanGallery() { tx := database.DB.MustBeginTx(ctx, nil) gallery, _ = qb.FindByChecksum(checksum, tx) if gallery != nil { - logger.Infof("%s already exists. Updating path...", t.FilePath) - gallery.Path = t.FilePath - _, err = qb.Update(*gallery, tx) + exists, _ := utils.FileExists(t.FilePath) + if exists { + logger.Infof("%s already exists. Duplicate of %s ", t.FilePath, gallery.Path) + } else { + + logger.Infof("%s already exists. Updating path...", t.FilePath) + gallery.Path = t.FilePath + _, err = qb.Update(*gallery, tx) + } } else { logger.Infof("%s doesn't exist. Creating new item...", t.FilePath) currentTime := time.Now() @@ -95,15 +102,23 @@ func (t *ScanTask) scanScene() { ctx := context.TODO() tx := database.DB.MustBeginTx(ctx, nil) if scene != nil { - logger.Infof("%s already exists. Updating path...", t.FilePath) - scene.Path = t.FilePath - _, err = qb.Update(*scene, tx) + exists, _ := utils.FileExists(t.FilePath) + if exists { + logger.Infof("%s already exists. Duplicate of %s ", t.FilePath, scene.Path) + } else { + logger.Infof("%s already exists. Updating path...", t.FilePath) + scene.Path = t.FilePath + _, err = qb.Update(*scene, tx) + } } else { logger.Infof("%s doesn't exist. Creating new item...", t.FilePath) currentTime := time.Now() newScene := models.Scene{ Checksum: checksum, Path: t.FilePath, + Title: sql.NullString{String: videoFile.Title, Valid: true}, + Details: sql.NullString{String: videoFile.Comment, Valid: true}, + Date: models.SQLiteDate{String: videoFile.CreationTime.Format("2006-01-02")}, Duration: sql.NullFloat64{Float64: videoFile.Duration, Valid: true}, VideoCodec: sql.NullString{String: videoFile.VideoCodec, Valid: true}, AudioCodec: sql.NullString{String: videoFile.AudioCodec, Valid: true}, diff --git a/pkg/models/generated_exec.go b/pkg/models/generated_exec.go index a285c42bf..77e02b6f7 100644 --- a/pkg/models/generated_exec.go +++ b/pkg/models/generated_exec.go @@ -108,6 +108,7 @@ type ComplexityRoot struct { Mutation struct { ConfigureGeneral func(childComplexity int, input ConfigGeneralInput) int PerformerCreate func(childComplexity int, input PerformerCreateInput) int + PerformerDestroy func(childComplexity int, input PerformerDestroyInput) int PerformerUpdate func(childComplexity int, input PerformerUpdateInput) int SceneDestroy func(childComplexity int, input SceneDestroyInput) int SceneMarkerCreate func(childComplexity int, input SceneMarkerCreateInput) int @@ -115,6 +116,7 @@ type ComplexityRoot struct { SceneMarkerUpdate func(childComplexity int, input SceneMarkerUpdateInput) int SceneUpdate func(childComplexity int, input SceneUpdateInput) int StudioCreate func(childComplexity int, input StudioCreateInput) int + StudioDestroy func(childComplexity int, input StudioDestroyInput) int StudioUpdate func(childComplexity int, input StudioUpdateInput) int TagCreate func(childComplexity int, input TagCreateInput) int TagDestroy func(childComplexity int, input TagDestroyInput) int @@ -290,8 +292,10 @@ type MutationResolver interface { SceneMarkerDestroy(ctx context.Context, id string) (bool, error) PerformerCreate(ctx context.Context, input PerformerCreateInput) (*Performer, error) PerformerUpdate(ctx context.Context, input PerformerUpdateInput) (*Performer, error) + PerformerDestroy(ctx context.Context, input PerformerDestroyInput) (bool, error) StudioCreate(ctx context.Context, input StudioCreateInput) (*Studio, error) StudioUpdate(ctx context.Context, input StudioUpdateInput) (*Studio, error) + StudioDestroy(ctx context.Context, input StudioDestroyInput) (bool, error) TagCreate(ctx context.Context, input TagCreateInput) (*Tag, error) TagUpdate(ctx context.Context, input TagUpdateInput) (*Tag, error) TagDestroy(ctx context.Context, input TagDestroyInput) (bool, error) @@ -600,6 +604,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Mutation.PerformerCreate(childComplexity, args["input"].(PerformerCreateInput)), true + case "Mutation.performerDestroy": + if e.complexity.Mutation.PerformerDestroy == nil { + break + } + + args, err := ec.field_Mutation_performerDestroy_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Mutation.PerformerDestroy(childComplexity, args["input"].(PerformerDestroyInput)), true + case "Mutation.performerUpdate": if e.complexity.Mutation.PerformerUpdate == nil { break @@ -684,6 +700,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Mutation.StudioCreate(childComplexity, args["input"].(StudioCreateInput)), true + case "Mutation.studioDestroy": + if e.complexity.Mutation.StudioDestroy == nil { + break + } + + args, err := ec.field_Mutation_studioDestroy_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Mutation.StudioDestroy(childComplexity, args["input"].(StudioDestroyInput)), true + case "Mutation.studioUpdate": if e.complexity.Mutation.StudioUpdate == nil { break @@ -1855,9 +1883,11 @@ type Mutation { performerCreate(input: PerformerCreateInput!): Performer performerUpdate(input: PerformerUpdateInput!): Performer + performerDestroy(input: PerformerDestroyInput!): Boolean! studioCreate(input: StudioCreateInput!): Studio studioUpdate(input: StudioUpdateInput!): Studio + studioDestroy(input: StudioDestroyInput!): Boolean! tagCreate(input: TagCreateInput!): Tag tagUpdate(input: TagUpdateInput!): Tag @@ -2069,6 +2099,10 @@ input PerformerUpdateInput { image: String } +input PerformerDestroyInput { + id: ID! +} + type FindPerformersResultType { count: Int! performers: [Performer!]! @@ -2233,6 +2267,10 @@ input StudioUpdateInput { image: String } +input StudioDestroyInput { + id: ID! +} + type FindStudiosResultType { count: Int! studios: [Studio!]! @@ -2291,6 +2329,20 @@ func (ec *executionContext) field_Mutation_performerCreate_args(ctx context.Cont return args, nil } +func (ec *executionContext) field_Mutation_performerDestroy_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { + var err error + args := map[string]interface{}{} + var arg0 PerformerDestroyInput + if tmp, ok := rawArgs["input"]; ok { + arg0, err = ec.unmarshalNPerformerDestroyInput2githubᚗcomᚋstashappᚋstashᚋpkgᚋmodelsᚐPerformerDestroyInput(ctx, tmp) + if err != nil { + return nil, err + } + } + args["input"] = arg0 + return args, nil +} + func (ec *executionContext) field_Mutation_performerUpdate_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { var err error args := map[string]interface{}{} @@ -2389,6 +2441,20 @@ func (ec *executionContext) field_Mutation_studioCreate_args(ctx context.Context return args, nil } +func (ec *executionContext) field_Mutation_studioDestroy_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { + var err error + args := map[string]interface{}{} + var arg0 StudioDestroyInput + if tmp, ok := rawArgs["input"]; ok { + arg0, err = ec.unmarshalNStudioDestroyInput2githubᚗcomᚋstashappᚋstashᚋpkgᚋmodelsᚐStudioDestroyInput(ctx, tmp) + if err != nil { + return nil, err + } + } + args["input"] = arg0 + return args, nil +} + func (ec *executionContext) field_Mutation_studioUpdate_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { var err error args := map[string]interface{}{} @@ -3694,6 +3760,40 @@ func (ec *executionContext) _Mutation_performerUpdate(ctx context.Context, field return ec.marshalOPerformer2ᚖgithubᚗcomᚋstashappᚋstashᚋpkgᚋmodelsᚐPerformer(ctx, field.Selections, res) } +func (ec *executionContext) _Mutation_performerDestroy(ctx context.Context, field graphql.CollectedField) graphql.Marshaler { + ctx = ec.Tracer.StartFieldExecution(ctx, field) + defer func() { ec.Tracer.EndFieldExecution(ctx) }() + rctx := &graphql.ResolverContext{ + Object: "Mutation", + Field: field, + Args: nil, + IsMethod: true, + } + ctx = graphql.WithResolverContext(ctx, rctx) + rawArgs := field.ArgumentMap(ec.Variables) + args, err := ec.field_Mutation_performerDestroy_args(ctx, rawArgs) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + rctx.Args = args + ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) + resTmp := ec.FieldMiddleware(ctx, nil, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Mutation().PerformerDestroy(rctx, args["input"].(PerformerDestroyInput)) + }) + if resTmp == nil { + if !ec.HasError(rctx) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(bool) + rctx.Result = res + ctx = ec.Tracer.StartFieldChildExecution(ctx) + return ec.marshalNBoolean2bool(ctx, field.Selections, res) +} + func (ec *executionContext) _Mutation_studioCreate(ctx context.Context, field graphql.CollectedField) graphql.Marshaler { ctx = ec.Tracer.StartFieldExecution(ctx, field) defer func() { ec.Tracer.EndFieldExecution(ctx) }() @@ -3756,6 +3856,40 @@ func (ec *executionContext) _Mutation_studioUpdate(ctx context.Context, field gr return ec.marshalOStudio2ᚖgithubᚗcomᚋstashappᚋstashᚋpkgᚋmodelsᚐStudio(ctx, field.Selections, res) } +func (ec *executionContext) _Mutation_studioDestroy(ctx context.Context, field graphql.CollectedField) graphql.Marshaler { + ctx = ec.Tracer.StartFieldExecution(ctx, field) + defer func() { ec.Tracer.EndFieldExecution(ctx) }() + rctx := &graphql.ResolverContext{ + Object: "Mutation", + Field: field, + Args: nil, + IsMethod: true, + } + ctx = graphql.WithResolverContext(ctx, rctx) + rawArgs := field.ArgumentMap(ec.Variables) + args, err := ec.field_Mutation_studioDestroy_args(ctx, rawArgs) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + rctx.Args = args + ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) + resTmp := ec.FieldMiddleware(ctx, nil, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Mutation().StudioDestroy(rctx, args["input"].(StudioDestroyInput)) + }) + if resTmp == nil { + if !ec.HasError(rctx) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(bool) + rctx.Result = res + ctx = ec.Tracer.StartFieldChildExecution(ctx) + return ec.marshalNBoolean2bool(ctx, field.Selections, res) +} + func (ec *executionContext) _Mutation_tagCreate(ctx context.Context, field graphql.CollectedField) graphql.Marshaler { ctx = ec.Tracer.StartFieldExecution(ctx, field) defer func() { ec.Tracer.EndFieldExecution(ctx) }() @@ -8200,6 +8334,24 @@ func (ec *executionContext) unmarshalInputPerformerCreateInput(ctx context.Conte return it, nil } +func (ec *executionContext) unmarshalInputPerformerDestroyInput(ctx context.Context, v interface{}) (PerformerDestroyInput, error) { + var it PerformerDestroyInput + var asMap = v.(map[string]interface{}) + + for k, v := range asMap { + switch k { + case "id": + var err error + it.ID, err = ec.unmarshalNID2string(ctx, v) + if err != nil { + return it, err + } + } + } + + return it, nil +} + func (ec *executionContext) unmarshalInputPerformerFilterType(ctx context.Context, v interface{}) (PerformerFilterType, error) { var it PerformerFilterType var asMap = v.(map[string]interface{}) @@ -8656,6 +8808,24 @@ func (ec *executionContext) unmarshalInputStudioCreateInput(ctx context.Context, return it, nil } +func (ec *executionContext) unmarshalInputStudioDestroyInput(ctx context.Context, v interface{}) (StudioDestroyInput, error) { + var it StudioDestroyInput + var asMap = v.(map[string]interface{}) + + for k, v := range asMap { + switch k { + case "id": + var err error + it.ID, err = ec.unmarshalNID2string(ctx, v) + if err != nil { + return it, err + } + } + } + + return it, nil +} + func (ec *executionContext) unmarshalInputStudioUpdateInput(ctx context.Context, v interface{}) (StudioUpdateInput, error) { var it StudioUpdateInput var asMap = v.(map[string]interface{}) @@ -9149,10 +9319,20 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet) out.Values[i] = ec._Mutation_performerCreate(ctx, field) case "performerUpdate": out.Values[i] = ec._Mutation_performerUpdate(ctx, field) + case "performerDestroy": + out.Values[i] = ec._Mutation_performerDestroy(ctx, field) + if out.Values[i] == graphql.Null { + invalids++ + } case "studioCreate": out.Values[i] = ec._Mutation_studioCreate(ctx, field) case "studioUpdate": out.Values[i] = ec._Mutation_studioUpdate(ctx, field) + case "studioDestroy": + out.Values[i] = ec._Mutation_studioDestroy(ctx, field) + if out.Values[i] == graphql.Null { + invalids++ + } case "tagCreate": out.Values[i] = ec._Mutation_tagCreate(ctx, field) case "tagUpdate": @@ -11114,6 +11294,10 @@ func (ec *executionContext) unmarshalNPerformerCreateInput2githubᚗcomᚋstasha return ec.unmarshalInputPerformerCreateInput(ctx, v) } +func (ec *executionContext) unmarshalNPerformerDestroyInput2githubᚗcomᚋstashappᚋstashᚋpkgᚋmodelsᚐPerformerDestroyInput(ctx context.Context, v interface{}) (PerformerDestroyInput, error) { + return ec.unmarshalInputPerformerDestroyInput(ctx, v) +} + func (ec *executionContext) unmarshalNPerformerUpdateInput2githubᚗcomᚋstashappᚋstashᚋpkgᚋmodelsᚐPerformerUpdateInput(ctx context.Context, v interface{}) (PerformerUpdateInput, error) { return ec.unmarshalInputPerformerUpdateInput(ctx, v) } @@ -11427,6 +11611,10 @@ func (ec *executionContext) unmarshalNStudioCreateInput2githubᚗcomᚋstashapp return ec.unmarshalInputStudioCreateInput(ctx, v) } +func (ec *executionContext) unmarshalNStudioDestroyInput2githubᚗcomᚋstashappᚋstashᚋpkgᚋmodelsᚐStudioDestroyInput(ctx context.Context, v interface{}) (StudioDestroyInput, error) { + return ec.unmarshalInputStudioDestroyInput(ctx, v) +} + func (ec *executionContext) unmarshalNStudioUpdateInput2githubᚗcomᚋstashappᚋstashᚋpkgᚋmodelsᚐStudioUpdateInput(ctx context.Context, v interface{}) (StudioUpdateInput, error) { return ec.unmarshalInputStudioUpdateInput(ctx, v) } diff --git a/pkg/models/generated_models.go b/pkg/models/generated_models.go index 358921d15..9fd700071 100644 --- a/pkg/models/generated_models.go +++ b/pkg/models/generated_models.go @@ -109,6 +109,10 @@ type PerformerCreateInput struct { Image string `json:"image"` } +type PerformerDestroyInput struct { + ID string `json:"id"` +} + type PerformerFilterType struct { // Filter by favorite FilterFavorites *bool `json:"filter_favorites"` @@ -260,6 +264,10 @@ type StudioCreateInput struct { Image string `json:"image"` } +type StudioDestroyInput struct { + ID string `json:"id"` +} + type StudioUpdateInput struct { ID string `json:"id"` Name *string `json:"name"` diff --git a/pkg/models/querybuilder_performer.go b/pkg/models/querybuilder_performer.go index ebbe8604b..d19965bb2 100644 --- a/pkg/models/querybuilder_performer.go +++ b/pkg/models/querybuilder_performer.go @@ -2,6 +2,7 @@ package models import ( "database/sql" + "github.com/jmoiron/sqlx" "github.com/stashapp/stash/pkg/database" ) @@ -54,6 +55,15 @@ func (qb *PerformerQueryBuilder) Update(updatedPerformer Performer, tx *sqlx.Tx) return &updatedPerformer, nil } +func (qb *PerformerQueryBuilder) Destroy(id string, tx *sqlx.Tx) error { + _, err := tx.Exec("DELETE FROM performers_scenes WHERE performer_id = ?", id) + if err != nil { + return err + } + + return executeDeleteQuery("performers", id, tx) +} + func (qb *PerformerQueryBuilder) Find(id int) (*Performer, error) { query := "SELECT * FROM performers WHERE id = ? LIMIT 1" args := []interface{}{id} diff --git a/pkg/models/querybuilder_studio.go b/pkg/models/querybuilder_studio.go index 10071775c..1bf501a43 100644 --- a/pkg/models/querybuilder_studio.go +++ b/pkg/models/querybuilder_studio.go @@ -2,6 +2,7 @@ package models import ( "database/sql" + "github.com/jmoiron/sqlx" "github.com/stashapp/stash/pkg/database" ) @@ -50,6 +51,22 @@ func (qb *StudioQueryBuilder) Update(updatedStudio Studio, tx *sqlx.Tx) (*Studio return &updatedStudio, nil } +func (qb *StudioQueryBuilder) Destroy(id string, tx *sqlx.Tx) error { + // remove studio from scenes + _, err := tx.Exec("UPDATE scenes SET studio_id = null WHERE studio_id = ?", id) + if err != nil { + return err + } + + // remove studio from scraped items + _, err = tx.Exec("UPDATE scraped_items SET studio_id = null WHERE studio_id = ?", id) + if err != nil { + return err + } + + return executeDeleteQuery("studios", id, tx) +} + func (qb *StudioQueryBuilder) Find(id int, tx *sqlx.Tx) (*Studio, error) { query := "SELECT * FROM studios WHERE id = ? LIMIT 1" args := []interface{}{id} @@ -86,7 +103,7 @@ func (qb *StudioQueryBuilder) Query(findFilter *FindFilterType) ([]*Studio, int) var args []interface{} body := selectDistinctIDs("studios") body += ` - join scenes on studios.id = scenes.studio_id + left join scenes on studios.id = scenes.studio_id ` if q := findFilter.Q; q != nil && *q != "" { diff --git a/ui/v2/src/components/Shared/DetailsEditNavbar.tsx b/ui/v2/src/components/Shared/DetailsEditNavbar.tsx index 47555cd50..2bedc0d48 100644 --- a/ui/v2/src/components/Shared/DetailsEditNavbar.tsx +++ b/ui/v2/src/components/Shared/DetailsEditNavbar.tsx @@ -1,4 +1,5 @@ import { + Alert, Button, FileInput, Menu, @@ -8,7 +9,7 @@ import { Popover, } from "@blueprintjs/core"; import _ from "lodash"; -import React, { FunctionComponent } from "react"; +import React, { FunctionComponent, useState } from "react"; import { Link } from "react-router-dom"; import * as GQL from "../../core/generated-graphql"; import { NavigationUtils } from "../../utils/navigation"; @@ -20,6 +21,7 @@ interface IProps { isEditing: boolean; onToggleEdit: () => void; onSave: () => void; + onDelete: () => void; onImageChange: (event: React.FormEvent) => void; // TODO: only for performers. make generic @@ -27,6 +29,8 @@ interface IProps { } export const DetailsEditNavbar: FunctionComponent = (props: IProps) => { + const [isDeleteAlertOpen, setIsDeleteAlertOpen] = useState(false); + function renderEditButton() { if (props.isNew) { return; } return ( @@ -43,6 +47,11 @@ export const DetailsEditNavbar: FunctionComponent = (props: IProps) => { return