Add delete scene button

This commit is contained in:
WithoutPants
2019-08-15 17:32:57 +10:00
parent 39bdede110
commit cfe2636837
12 changed files with 317 additions and 6 deletions

View File

@@ -24,4 +24,8 @@ mutation SceneUpdate(
}) {
...SceneData
}
}
mutation SceneDestroy($id: ID!, $delete_file: Boolean) {
sceneDestroy(input: {id: $id, delete_file: $delete_file})
}

View File

@@ -72,6 +72,7 @@ type Query {
type Mutation {
sceneUpdate(input: SceneUpdateInput!): Scene
sceneDestroy(input: SceneDestroyInput!): Boolean!
sceneMarkerCreate(input: SceneMarkerCreateInput!): SceneMarker
sceneMarkerUpdate(input: SceneMarkerUpdateInput!): SceneMarker

View File

@@ -53,6 +53,11 @@ input SceneUpdateInput {
tag_ids: [ID!]
}
input SceneDestroyInput {
id: ID!
delete_file: Boolean
}
type FindScenesResultType {
count: Int!
scenes: [Scene!]!

View File

@@ -3,10 +3,13 @@ package api
import (
"context"
"database/sql"
"github.com/stashapp/stash/pkg/database"
"github.com/stashapp/stash/pkg/models"
"os"
"strconv"
"time"
"github.com/stashapp/stash/pkg/database"
"github.com/stashapp/stash/pkg/logger"
"github.com/stashapp/stash/pkg/models"
)
func (r *mutationResolver) SceneUpdate(ctx context.Context, input models.SceneUpdateInput) (*models.Scene, error) {
@@ -101,6 +104,59 @@ func (r *mutationResolver) SceneUpdate(ctx context.Context, input models.SceneUp
return scene, nil
}
func (r *mutationResolver) SceneDestroy(ctx context.Context, input models.SceneDestroyInput) (bool, error) {
qb := models.NewSceneQueryBuilder()
jqb := models.NewJoinsQueryBuilder()
tx := database.DB.MustBeginTx(ctx, nil)
sceneID, _ := strconv.Atoi(input.ID)
scene, err := qb.Find(sceneID)
if err != nil {
_ = tx.Rollback()
return false, err
}
if err := jqb.DestroyScenesTags(sceneID, tx); err != nil {
_ = tx.Rollback()
return false, err
}
if err := jqb.DestroyPerformersScenes(sceneID, tx); err != nil {
_ = tx.Rollback()
return false, err
}
if err := jqb.DestroyScenesMarkers(sceneID, tx); err != nil {
_ = tx.Rollback()
return false, err
}
if err := jqb.DestroyScenesGalleries(sceneID, tx); err != nil {
_ = tx.Rollback()
return false, err
}
if err := qb.Destroy(input.ID, tx); err != nil {
_ = tx.Rollback()
return false, err
}
if err := tx.Commit(); err != nil {
return false, err
}
// if delete file is true, then delete the file as well
// if it fails, just log a message
if input.DeleteFile != nil && *input.DeleteFile {
err = os.Remove(scene.Path)
if err != nil {
logger.Warnf("Could not delete file %s: %s", scene.Path, err.Error())
}
}
return true, nil
}
func (r *mutationResolver) SceneMarkerCreate(ctx context.Context, input models.SceneMarkerCreateInput) (*models.SceneMarker, error) {
primaryTagID, _ := strconv.Atoi(input.PrimaryTagID)
sceneID, _ := strconv.Atoi(input.SceneID)

View File

@@ -109,6 +109,7 @@ type ComplexityRoot struct {
ConfigureGeneral func(childComplexity int, input ConfigGeneralInput) int
PerformerCreate func(childComplexity int, input PerformerCreateInput) int
PerformerUpdate func(childComplexity int, input PerformerUpdateInput) int
SceneDestroy func(childComplexity int, input SceneDestroyInput) int
SceneMarkerCreate func(childComplexity int, input SceneMarkerCreateInput) int
SceneMarkerDestroy func(childComplexity int, id string) int
SceneMarkerUpdate func(childComplexity int, input SceneMarkerUpdateInput) int
@@ -283,6 +284,7 @@ type GalleryResolver interface {
}
type MutationResolver interface {
SceneUpdate(ctx context.Context, input SceneUpdateInput) (*Scene, error)
SceneDestroy(ctx context.Context, input SceneDestroyInput) (bool, error)
SceneMarkerCreate(ctx context.Context, input SceneMarkerCreateInput) (*SceneMarker, error)
SceneMarkerUpdate(ctx context.Context, input SceneMarkerUpdateInput) (*SceneMarker, error)
SceneMarkerDestroy(ctx context.Context, id string) (bool, error)
@@ -610,6 +612,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Mutation.PerformerUpdate(childComplexity, args["input"].(PerformerUpdateInput)), true
case "Mutation.sceneDestroy":
if e.complexity.Mutation.SceneDestroy == nil {
break
}
args, err := ec.field_Mutation_sceneDestroy_args(context.TODO(), rawArgs)
if err != nil {
return 0, false
}
return e.complexity.Mutation.SceneDestroy(childComplexity, args["input"].(SceneDestroyInput)), true
case "Mutation.sceneMarkerCreate":
if e.complexity.Mutation.SceneMarkerCreate == nil {
break
@@ -1833,6 +1847,7 @@ type Query {
type Mutation {
sceneUpdate(input: SceneUpdateInput!): Scene
sceneDestroy(input: SceneDestroyInput!): Boolean!
sceneMarkerCreate(input: SceneMarkerCreateInput!): SceneMarker
sceneMarkerUpdate(input: SceneMarkerUpdateInput!): SceneMarker
@@ -2158,6 +2173,11 @@ input SceneUpdateInput {
tag_ids: [ID!]
}
input SceneDestroyInput {
id: ID!
delete_file: Boolean
}
type FindScenesResultType {
count: Int!
scenes: [Scene!]!
@@ -2284,6 +2304,20 @@ func (ec *executionContext) field_Mutation_performerUpdate_args(ctx context.Cont
return args, nil
}
func (ec *executionContext) field_Mutation_sceneDestroy_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
var err error
args := map[string]interface{}{}
var arg0 SceneDestroyInput
if tmp, ok := rawArgs["input"]; ok {
arg0, err = ec.unmarshalNSceneDestroyInput2githubᚗcomᚋstashappᚋstashᚋpkgᚋmodelsᚐSceneDestroyInput(ctx, tmp)
if err != nil {
return nil, err
}
}
args["input"] = arg0
return args, nil
}
func (ec *executionContext) field_Mutation_sceneMarkerCreate_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
var err error
args := map[string]interface{}{}
@@ -3467,6 +3501,40 @@ func (ec *executionContext) _Mutation_sceneUpdate(ctx context.Context, field gra
return ec.marshalOScene2ᚖgithubᚗcomᚋstashappᚋstashᚋpkgᚋmodelsᚐScene(ctx, field.Selections, res)
}
func (ec *executionContext) _Mutation_sceneDestroy(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_sceneDestroy_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().SceneDestroy(rctx, args["input"].(SceneDestroyInput))
})
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_sceneMarkerCreate(ctx context.Context, field graphql.CollectedField) graphql.Marshaler {
ctx = ec.Tracer.StartFieldExecution(ctx, field)
defer func() { ec.Tracer.EndFieldExecution(ctx) }()
@@ -8269,6 +8337,30 @@ func (ec *executionContext) unmarshalInputPerformerUpdateInput(ctx context.Conte
return it, nil
}
func (ec *executionContext) unmarshalInputSceneDestroyInput(ctx context.Context, v interface{}) (SceneDestroyInput, error) {
var it SceneDestroyInput
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
}
case "delete_file":
var err error
it.DeleteFile, err = ec.unmarshalOBoolean2ᚖbool(ctx, v)
if err != nil {
return it, err
}
}
}
return it, nil
}
func (ec *executionContext) unmarshalInputSceneFilterType(ctx context.Context, v interface{}) (SceneFilterType, error) {
var it SceneFilterType
var asMap = v.(map[string]interface{})
@@ -9032,6 +9124,11 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet)
out.Values[i] = graphql.MarshalString("Mutation")
case "sceneUpdate":
out.Values[i] = ec._Mutation_sceneUpdate(ctx, field)
case "sceneDestroy":
out.Values[i] = ec._Mutation_sceneDestroy(ctx, field)
if out.Values[i] == graphql.Null {
invalids++
}
case "sceneMarkerCreate":
out.Values[i] = ec._Mutation_sceneMarkerCreate(ctx, field)
case "sceneMarkerUpdate":
@@ -11065,6 +11162,10 @@ func (ec *executionContext) marshalNScene2ᚖgithubᚗcomᚋstashappᚋstashᚋp
return ec._Scene(ctx, sel, v)
}
func (ec *executionContext) unmarshalNSceneDestroyInput2githubᚗcomᚋstashappᚋstashᚋpkgᚋmodelsᚐSceneDestroyInput(ctx context.Context, v interface{}) (SceneDestroyInput, error) {
return ec.unmarshalInputSceneDestroyInput(ctx, v)
}
func (ec *executionContext) marshalNSceneFileType2githubᚗcomᚋstashappᚋstashᚋpkgᚋmodelsᚐSceneFileType(ctx context.Context, sel ast.SelectionSet, v SceneFileType) graphql.Marshaler {
return ec._SceneFileType(ctx, sel, &v)
}

View File

@@ -136,6 +136,11 @@ type PerformerUpdateInput struct {
Image *string `json:"image"`
}
type SceneDestroyInput struct {
ID string `json:"id"`
DeleteFile *bool `json:"delete_file"`
}
type SceneFileType struct {
Size *string `json:"size"`
Duration *float64 `json:"duration"`

View File

@@ -33,6 +33,14 @@ func (qb *JoinsQueryBuilder) UpdatePerformersScenes(sceneID int, updatedJoins []
return qb.CreatePerformersScenes(updatedJoins, tx)
}
func (qb *JoinsQueryBuilder) DestroyPerformersScenes(sceneID int, tx *sqlx.Tx) error {
ensureTx(tx)
// Delete the existing joins
_, err := tx.Exec("DELETE FROM performers_scenes WHERE scene_id = ?", sceneID)
return err
}
func (qb *JoinsQueryBuilder) CreateScenesTags(newJoins []ScenesTags, tx *sqlx.Tx) error {
ensureTx(tx)
for _, join := range newJoins {
@@ -58,6 +66,15 @@ func (qb *JoinsQueryBuilder) UpdateScenesTags(sceneID int, updatedJoins []Scenes
return qb.CreateScenesTags(updatedJoins, tx)
}
func (qb *JoinsQueryBuilder) DestroyScenesTags(sceneID int, tx *sqlx.Tx) error {
ensureTx(tx)
// Delete the existing joins
_, err := tx.Exec("DELETE FROM scenes_tags WHERE scene_id = ?", sceneID)
return err
}
func (qb *JoinsQueryBuilder) CreateSceneMarkersTags(newJoins []SceneMarkersTags, tx *sqlx.Tx) error {
ensureTx(tx)
for _, join := range newJoins {
@@ -82,3 +99,32 @@ func (qb *JoinsQueryBuilder) UpdateSceneMarkersTags(sceneMarkerID int, updatedJo
}
return qb.CreateSceneMarkersTags(updatedJoins, tx)
}
func (qb *JoinsQueryBuilder) DestroySceneMarkersTags(sceneMarkerID int, updatedJoins []SceneMarkersTags, tx *sqlx.Tx) error {
ensureTx(tx)
// Delete the existing joins
_, err := tx.Exec("DELETE FROM scene_markers_tags WHERE scene_marker_id = ?", sceneMarkerID)
return err
}
func (qb *JoinsQueryBuilder) DestroyScenesGalleries(sceneID int, tx *sqlx.Tx) error {
ensureTx(tx)
// Unset the existing scene id from galleries
_, err := tx.Exec("UPDATE galleries SET scene_id = null WHERE scene_id = ?", sceneID)
return err
}
func (qb *JoinsQueryBuilder) DestroyScenesMarkers(sceneID int, tx *sqlx.Tx) error {
ensureTx(tx)
// Delete the scene marker tags
_, err := tx.Exec("DELETE t FROM scene_markers_tags t join scene_markers m on t.scene_marker_id = m.id WHERE m.scene_id = ?", sceneID)
// Delete the existing joins
_, err = tx.Exec("DELETE FROM scene_markers WHERE scene_id = ?", sceneID)
return err
}

View File

@@ -2,10 +2,11 @@ package models
import (
"database/sql"
"github.com/jmoiron/sqlx"
"github.com/stashapp/stash/pkg/database"
"strconv"
"strings"
"github.com/jmoiron/sqlx"
"github.com/stashapp/stash/pkg/database"
)
const scenesForPerformerQuery = `
@@ -76,6 +77,9 @@ func (qb *SceneQueryBuilder) Update(updatedScene Scene, tx *sqlx.Tx) (*Scene, er
return &updatedScene, nil
}
func (qb *SceneQueryBuilder) Destroy(id string, tx *sqlx.Tx) error {
return executeDeleteQuery("scenes", id, tx)
}
func (qb *SceneQueryBuilder) Find(id int) (*Scene, error) {
query := "SELECT * FROM scenes WHERE id = ? LIMIT 1"
args := []interface{}{id}

View File

@@ -82,7 +82,12 @@ export const Scene: FunctionComponent<ISceneProps> = (props: ISceneProps) => {
<Tab
id="scene-edit-panel"
title="Edit"
panel={<SceneEditPanel scene={modifiedScene} onUpdate={(newScene) => setScene(newScene)} />}
panel={
<SceneEditPanel
scene={modifiedScene}
onUpdate={(newScene) => setScene(newScene)}
onDelete={() => props.history.push("/scenes")}
/>}
/>
</Tabs>
</Card>

View File

@@ -1,4 +1,5 @@
import {
Alert,
Button,
FormGroup,
HTMLSelect,
@@ -19,6 +20,7 @@ import { ValidGalleriesSelect } from "../../select/ValidGalleriesSelect";
interface IProps {
scene: GQL.SceneDataFragment;
onUpdate: (scene: GQL.SceneDataFragment) => void;
onDelete: () => void;
}
export const SceneEditPanel: FunctionComponent<IProps> = (props: IProps) => {
@@ -33,10 +35,14 @@ export const SceneEditPanel: FunctionComponent<IProps> = (props: IProps) => {
const [performerIds, setPerformerIds] = useState<string[] | undefined>(undefined);
const [tagIds, setTagIds] = useState<string[] | undefined>(undefined);
const [isDeleteAlertOpen, setIsDeleteAlertOpen] = useState<boolean>(false);
const [deleteFile, setDeleteFile] = useState<boolean>(false);
// Network state
const [isLoading, setIsLoading] = useState(false);
const updateScene = StashService.useSceneUpdate(getSceneInput());
const deleteScene = StashService.useSceneDestroy(getSceneDeleteInput());
function updateSceneEditState(state: Partial<GQL.SceneDataFragment>) {
const perfIds = !!state.performers ? state.performers.map((performer) => performer.id) : undefined;
@@ -89,6 +95,27 @@ export const SceneEditPanel: FunctionComponent<IProps> = (props: IProps) => {
setIsLoading(false);
}
function getSceneDeleteInput(): GQL.SceneDestroyInput {
return {
id: props.scene.id,
delete_file: deleteFile
};
}
async function onDelete() {
setIsDeleteAlertOpen(false);
setIsLoading(true);
try {
await deleteScene();
ToastUtils.success("Deleted scene");
} catch (e) {
ErrorUtils.handle(e);
}
setIsLoading(false);
props.onDelete();
}
function renderMultiSelect(type: "performers" | "tags", initialIds: string[] | undefined) {
return (
<FilterMultiSelect
@@ -105,8 +132,27 @@ export const SceneEditPanel: FunctionComponent<IProps> = (props: IProps) => {
);
}
function renderDeleteAlert() {
return (
<Alert
cancelButtonText="Cancel"
confirmButtonText="Delete"
icon="trash"
intent="danger"
isOpen={isDeleteAlertOpen}
onCancel={() => setIsDeleteAlertOpen(false)}
onConfirm={() => onDelete()}
>
<p>
Are you sure you want to delete this scene? Unless the file is also deleted, this scene will be re-added when scan is performed.
</p>
</Alert>
);
}
return (
<>
{renderDeleteAlert()}
{isLoading ? <Spinner size={Spinner.SIZE_LARGE} /> : undefined}
<div className="form-container " style={{width: "50%"}}>
<FormGroup label="Title">
@@ -171,6 +217,7 @@ export const SceneEditPanel: FunctionComponent<IProps> = (props: IProps) => {
</FormGroup>
</div>
<Button text="Save" intent="primary" onClick={() => onSave()}/>
<Button text="Delete" intent="danger" onClick={() => setIsDeleteAlertOpen(true)}/>
</>
);
};

View File

@@ -138,6 +138,10 @@ export class StashService {
return GQL.useSceneUpdate({ variables: input });
}
public static useSceneDestroy(input: GQL.SceneDestroyInput) {
return GQL.useSceneDestroy({ variables: input });
}
public static useStudioCreate(input: GQL.StudioCreateInput) {
return GQL.useStudioCreate({ variables: input });
}

View File

@@ -1,6 +1,6 @@
/* tslint:disable */
/* eslint-disable */
// Generated in 2019-05-27T11:23:10-07:00
// Generated in 2019-08-15T13:55:54+10:00
export type Maybe<T> = T | undefined;
export interface SceneFilterType {
@@ -88,6 +88,12 @@ export interface SceneUpdateInput {
tag_ids?: Maybe<string[]>;
}
export interface SceneDestroyInput {
id: string;
delete_file?: Maybe<boolean>;
}
export interface SceneMarkerCreateInput {
title: string;
@@ -390,6 +396,17 @@ export type SceneUpdateMutation = {
export type SceneUpdateSceneUpdate = SceneDataFragment;
export type SceneDestroyVariables = {
id: string;
delete_file?: Maybe<boolean>;
};
export type SceneDestroyMutation = {
__typename?: "Mutation";
sceneDestroy: boolean;
};
export type StudioCreateVariables = {
name: string;
url?: Maybe<string>;
@@ -1774,6 +1791,22 @@ export function useSceneUpdate(
SceneUpdateVariables
>(SceneUpdateDocument, baseOptions);
}
export const SceneDestroyDocument = gql`
mutation SceneDestroy($id: ID!, $delete_file: Boolean) {
sceneDestroy(input: { id: $id, delete_file: $delete_file })
}
`;
export function useSceneDestroy(
baseOptions?: ReactApolloHooks.MutationHookOptions<
SceneDestroyMutation,
SceneDestroyVariables
>
) {
return ReactApolloHooks.useMutation<
SceneDestroyMutation,
SceneDestroyVariables
>(SceneDestroyDocument, baseOptions);
}
export const StudioCreateDocument = gql`
mutation StudioCreate($name: String!, $url: String, $image: String!) {
studioCreate(input: { name: $name, url: $url, image: $image }) {