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 862da0847..e756cd285 100644 --- a/graphql/schema/schema.graphql +++ b/graphql/schema/schema.graphql @@ -79,9 +79,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/models/generated_exec.go b/pkg/models/generated_exec.go index 3964dd1c9..48627edc4 100644 --- a/pkg/models/generated_exec.go +++ b/pkg/models/generated_exec.go @@ -108,12 +108,14 @@ 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 SceneMarkerCreate func(childComplexity int, input SceneMarkerCreateInput) int SceneMarkerDestroy func(childComplexity int, id string) int 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 @@ -288,8 +290,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) @@ -598,6 +602,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 @@ -670,6 +686,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 @@ -1840,9 +1868,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 @@ -2054,6 +2084,10 @@ input PerformerUpdateInput { image: String } +input PerformerDestroyInput { + id: ID! +} + type FindPerformersResultType { count: Int! performers: [Performer!]! @@ -2212,6 +2246,10 @@ input StudioUpdateInput { image: String } +input StudioDestroyInput { + id: ID! +} + type FindStudiosResultType { count: Int! studios: [Studio!]! @@ -2270,6 +2308,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{}{} @@ -2354,6 +2406,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{}{} @@ -3625,6 +3691,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) }() @@ -3687,6 +3787,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) }() @@ -8131,6 +8265,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{}) @@ -8557,6 +8709,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{}) @@ -9045,10 +9215,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": @@ -11010,6 +11190,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) } @@ -11319,6 +11503,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 4c1a780a8..351270d16 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"` @@ -254,6 +258,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..c7a46afb4 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} 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