Merge branch 'master' into delete_scene

This commit is contained in:
WithoutPants 2019-08-20 15:37:58 +10:00 committed by GitHub
commit c3e4c5702a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 458 additions and 17 deletions

View file

@ -82,4 +82,8 @@ mutation PerformerUpdate(
}) { }) {
...PerformerData ...PerformerData
} }
}
mutation PerformerDestroy($id: ID!) {
performerDestroy(input: { id: $id })
} }

View file

@ -17,4 +17,8 @@ mutation StudioUpdate(
studioUpdate(input: { id: $id, name: $name, url: $url, image: $image }) { studioUpdate(input: { id: $id, name: $name, url: $url, image: $image }) {
...StudioData ...StudioData
} }
}
mutation StudioDestroy($id: ID!) {
studioDestroy(input: { id: $id })
} }

View file

@ -80,9 +80,11 @@ type Mutation {
performerCreate(input: PerformerCreateInput!): Performer performerCreate(input: PerformerCreateInput!): Performer
performerUpdate(input: PerformerUpdateInput!): Performer performerUpdate(input: PerformerUpdateInput!): Performer
performerDestroy(input: PerformerDestroyInput!): Boolean!
studioCreate(input: StudioCreateInput!): Studio studioCreate(input: StudioCreateInput!): Studio
studioUpdate(input: StudioUpdateInput!): Studio studioUpdate(input: StudioUpdateInput!): Studio
studioDestroy(input: StudioDestroyInput!): Boolean!
tagCreate(input: TagCreateInput!): Tag tagCreate(input: TagCreateInput!): Tag
tagUpdate(input: TagUpdateInput!): Tag tagUpdate(input: TagUpdateInput!): Tag

View file

@ -66,6 +66,10 @@ input PerformerUpdateInput {
image: String image: String
} }
input PerformerDestroyInput {
id: ID!
}
type FindPerformersResultType { type FindPerformersResultType {
count: Int! count: Int!
performers: [Performer!]! performers: [Performer!]!

View file

@ -23,6 +23,10 @@ input StudioUpdateInput {
image: String image: String
} }
input StudioDestroyInput {
id: ID!
}
type FindStudiosResultType { type FindStudiosResultType {
count: Int! count: Int!
studios: [Studio!]! studios: [Studio!]!

View file

@ -175,3 +175,17 @@ func (r *mutationResolver) PerformerUpdate(ctx context.Context, input models.Per
return performer, nil 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
}

View file

@ -3,11 +3,12 @@ package api
import ( import (
"context" "context"
"database/sql" "database/sql"
"strconv"
"time"
"github.com/stashapp/stash/pkg/database" "github.com/stashapp/stash/pkg/database"
"github.com/stashapp/stash/pkg/models" "github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/utils" "github.com/stashapp/stash/pkg/utils"
"strconv"
"time"
) )
func (r *mutationResolver) StudioCreate(ctx context.Context, input models.StudioCreateInput) (*models.Studio, error) { 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 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
}

View file

@ -6,6 +6,7 @@ import (
"math" "math"
"os" "os"
"os/exec" "os/exec"
"path/filepath"
"strconv" "strconv"
"strings" "strings"
"time" "time"
@ -28,6 +29,8 @@ type VideoFile struct {
VideoStream *FFProbeStream VideoStream *FFProbeStream
Path string Path string
Title string
Comment string
Container string Container string
Duration float64 Duration float64
StartTime 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) //} // TODO nil_or_unsupported.(video_stream) && nil_or_unsupported.(audio_stream)
result.Path = filePath 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.Bitrate, _ = strconv.ParseInt(probeJSON.Format.BitRate, 10, 64)
result.Container = probeJSON.Format.FormatName result.Container = probeJSON.Format.FormatName

View file

@ -22,6 +22,8 @@ type FFProbeJSON struct {
Encoder string `json:"encoder"` Encoder string `json:"encoder"`
MajorBrand string `json:"major_brand"` MajorBrand string `json:"major_brand"`
MinorVersion string `json:"minor_version"` MinorVersion string `json:"minor_version"`
Title string `json:"title"`
Comment string `json:"comment"`
} `json:"tags"` } `json:"tags"`
} `json:"format"` } `json:"format"`
Streams []FFProbeStream `json:"streams"` Streams []FFProbeStream `json:"streams"`

View file

@ -1,13 +1,14 @@
package manager package manager
import ( import (
"path/filepath"
"sync"
"github.com/bmatcuk/doublestar" "github.com/bmatcuk/doublestar"
"github.com/stashapp/stash/pkg/logger" "github.com/stashapp/stash/pkg/logger"
"github.com/stashapp/stash/pkg/manager/config" "github.com/stashapp/stash/pkg/manager/config"
"github.com/stashapp/stash/pkg/models" "github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/utils" "github.com/stashapp/stash/pkg/utils"
"path/filepath"
"sync"
) )
func (s *singleton) Scan() { func (s *singleton) Scan() {
@ -34,6 +35,8 @@ func (s *singleton) Scan() {
go task.Start(&wg) go task.Start(&wg)
wg.Wait() wg.Wait()
} }
logger.Info("Finished scan")
}() }()
} }

View file

@ -3,15 +3,16 @@ package manager
import ( import (
"context" "context"
"database/sql" "database/sql"
"path/filepath"
"strconv"
"sync"
"time"
"github.com/stashapp/stash/pkg/database" "github.com/stashapp/stash/pkg/database"
"github.com/stashapp/stash/pkg/ffmpeg" "github.com/stashapp/stash/pkg/ffmpeg"
"github.com/stashapp/stash/pkg/logger" "github.com/stashapp/stash/pkg/logger"
"github.com/stashapp/stash/pkg/models" "github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/utils" "github.com/stashapp/stash/pkg/utils"
"path/filepath"
"strconv"
"sync"
"time"
) )
type ScanTask struct { type ScanTask struct {
@ -46,9 +47,15 @@ func (t *ScanTask) scanGallery() {
tx := database.DB.MustBeginTx(ctx, nil) tx := database.DB.MustBeginTx(ctx, nil)
gallery, _ = qb.FindByChecksum(checksum, tx) gallery, _ = qb.FindByChecksum(checksum, tx)
if gallery != nil { if gallery != nil {
logger.Infof("%s already exists. Updating path...", t.FilePath) exists, _ := utils.FileExists(t.FilePath)
gallery.Path = t.FilePath if exists {
_, err = qb.Update(*gallery, tx) 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 { } else {
logger.Infof("%s doesn't exist. Creating new item...", t.FilePath) logger.Infof("%s doesn't exist. Creating new item...", t.FilePath)
currentTime := time.Now() currentTime := time.Now()
@ -95,15 +102,23 @@ func (t *ScanTask) scanScene() {
ctx := context.TODO() ctx := context.TODO()
tx := database.DB.MustBeginTx(ctx, nil) tx := database.DB.MustBeginTx(ctx, nil)
if scene != nil { if scene != nil {
logger.Infof("%s already exists. Updating path...", t.FilePath) exists, _ := utils.FileExists(t.FilePath)
scene.Path = t.FilePath if exists {
_, err = qb.Update(*scene, tx) 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 { } else {
logger.Infof("%s doesn't exist. Creating new item...", t.FilePath) logger.Infof("%s doesn't exist. Creating new item...", t.FilePath)
currentTime := time.Now() currentTime := time.Now()
newScene := models.Scene{ newScene := models.Scene{
Checksum: checksum, Checksum: checksum,
Path: t.FilePath, 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}, Duration: sql.NullFloat64{Float64: videoFile.Duration, Valid: true},
VideoCodec: sql.NullString{String: videoFile.VideoCodec, Valid: true}, VideoCodec: sql.NullString{String: videoFile.VideoCodec, Valid: true},
AudioCodec: sql.NullString{String: videoFile.AudioCodec, Valid: true}, AudioCodec: sql.NullString{String: videoFile.AudioCodec, Valid: true},

View file

@ -108,6 +108,7 @@ type ComplexityRoot struct {
Mutation struct { Mutation struct {
ConfigureGeneral func(childComplexity int, input ConfigGeneralInput) int ConfigureGeneral func(childComplexity int, input ConfigGeneralInput) int
PerformerCreate func(childComplexity int, input PerformerCreateInput) int PerformerCreate func(childComplexity int, input PerformerCreateInput) int
PerformerDestroy func(childComplexity int, input PerformerDestroyInput) int
PerformerUpdate func(childComplexity int, input PerformerUpdateInput) int PerformerUpdate func(childComplexity int, input PerformerUpdateInput) int
SceneDestroy func(childComplexity int, input SceneDestroyInput) int SceneDestroy func(childComplexity int, input SceneDestroyInput) int
SceneMarkerCreate func(childComplexity int, input SceneMarkerCreateInput) int SceneMarkerCreate func(childComplexity int, input SceneMarkerCreateInput) int
@ -115,6 +116,7 @@ type ComplexityRoot struct {
SceneMarkerUpdate func(childComplexity int, input SceneMarkerUpdateInput) int SceneMarkerUpdate func(childComplexity int, input SceneMarkerUpdateInput) int
SceneUpdate func(childComplexity int, input SceneUpdateInput) int SceneUpdate func(childComplexity int, input SceneUpdateInput) int
StudioCreate func(childComplexity int, input StudioCreateInput) int StudioCreate func(childComplexity int, input StudioCreateInput) int
StudioDestroy func(childComplexity int, input StudioDestroyInput) int
StudioUpdate func(childComplexity int, input StudioUpdateInput) int StudioUpdate func(childComplexity int, input StudioUpdateInput) int
TagCreate func(childComplexity int, input TagCreateInput) int TagCreate func(childComplexity int, input TagCreateInput) int
TagDestroy func(childComplexity int, input TagDestroyInput) int TagDestroy func(childComplexity int, input TagDestroyInput) int
@ -290,8 +292,10 @@ type MutationResolver interface {
SceneMarkerDestroy(ctx context.Context, id string) (bool, error) SceneMarkerDestroy(ctx context.Context, id string) (bool, error)
PerformerCreate(ctx context.Context, input PerformerCreateInput) (*Performer, error) PerformerCreate(ctx context.Context, input PerformerCreateInput) (*Performer, error)
PerformerUpdate(ctx context.Context, input PerformerUpdateInput) (*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) StudioCreate(ctx context.Context, input StudioCreateInput) (*Studio, error)
StudioUpdate(ctx context.Context, input StudioUpdateInput) (*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) TagCreate(ctx context.Context, input TagCreateInput) (*Tag, error)
TagUpdate(ctx context.Context, input TagUpdateInput) (*Tag, error) TagUpdate(ctx context.Context, input TagUpdateInput) (*Tag, error)
TagDestroy(ctx context.Context, input TagDestroyInput) (bool, 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 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": case "Mutation.performerUpdate":
if e.complexity.Mutation.PerformerUpdate == nil { if e.complexity.Mutation.PerformerUpdate == nil {
break break
@ -684,6 +700,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Mutation.StudioCreate(childComplexity, args["input"].(StudioCreateInput)), true 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": case "Mutation.studioUpdate":
if e.complexity.Mutation.StudioUpdate == nil { if e.complexity.Mutation.StudioUpdate == nil {
break break
@ -1855,9 +1883,11 @@ type Mutation {
performerCreate(input: PerformerCreateInput!): Performer performerCreate(input: PerformerCreateInput!): Performer
performerUpdate(input: PerformerUpdateInput!): Performer performerUpdate(input: PerformerUpdateInput!): Performer
performerDestroy(input: PerformerDestroyInput!): Boolean!
studioCreate(input: StudioCreateInput!): Studio studioCreate(input: StudioCreateInput!): Studio
studioUpdate(input: StudioUpdateInput!): Studio studioUpdate(input: StudioUpdateInput!): Studio
studioDestroy(input: StudioDestroyInput!): Boolean!
tagCreate(input: TagCreateInput!): Tag tagCreate(input: TagCreateInput!): Tag
tagUpdate(input: TagUpdateInput!): Tag tagUpdate(input: TagUpdateInput!): Tag
@ -2069,6 +2099,10 @@ input PerformerUpdateInput {
image: String image: String
} }
input PerformerDestroyInput {
id: ID!
}
type FindPerformersResultType { type FindPerformersResultType {
count: Int! count: Int!
performers: [Performer!]! performers: [Performer!]!
@ -2233,6 +2267,10 @@ input StudioUpdateInput {
image: String image: String
} }
input StudioDestroyInput {
id: ID!
}
type FindStudiosResultType { type FindStudiosResultType {
count: Int! count: Int!
studios: [Studio!]! studios: [Studio!]!
@ -2291,6 +2329,20 @@ func (ec *executionContext) field_Mutation_performerCreate_args(ctx context.Cont
return args, nil 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) { func (ec *executionContext) field_Mutation_performerUpdate_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
var err error var err error
args := map[string]interface{}{} args := map[string]interface{}{}
@ -2389,6 +2441,20 @@ func (ec *executionContext) field_Mutation_studioCreate_args(ctx context.Context
return args, nil 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) { func (ec *executionContext) field_Mutation_studioUpdate_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
var err error var err error
args := map[string]interface{}{} 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) 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 { func (ec *executionContext) _Mutation_studioCreate(ctx context.Context, field graphql.CollectedField) graphql.Marshaler {
ctx = ec.Tracer.StartFieldExecution(ctx, field) ctx = ec.Tracer.StartFieldExecution(ctx, field)
defer func() { ec.Tracer.EndFieldExecution(ctx) }() 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) 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 { func (ec *executionContext) _Mutation_tagCreate(ctx context.Context, field graphql.CollectedField) graphql.Marshaler {
ctx = ec.Tracer.StartFieldExecution(ctx, field) ctx = ec.Tracer.StartFieldExecution(ctx, field)
defer func() { ec.Tracer.EndFieldExecution(ctx) }() defer func() { ec.Tracer.EndFieldExecution(ctx) }()
@ -8200,6 +8334,24 @@ func (ec *executionContext) unmarshalInputPerformerCreateInput(ctx context.Conte
return it, nil 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) { func (ec *executionContext) unmarshalInputPerformerFilterType(ctx context.Context, v interface{}) (PerformerFilterType, error) {
var it PerformerFilterType var it PerformerFilterType
var asMap = v.(map[string]interface{}) var asMap = v.(map[string]interface{})
@ -8656,6 +8808,24 @@ func (ec *executionContext) unmarshalInputStudioCreateInput(ctx context.Context,
return it, nil 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) { func (ec *executionContext) unmarshalInputStudioUpdateInput(ctx context.Context, v interface{}) (StudioUpdateInput, error) {
var it StudioUpdateInput var it StudioUpdateInput
var asMap = v.(map[string]interface{}) 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) out.Values[i] = ec._Mutation_performerCreate(ctx, field)
case "performerUpdate": case "performerUpdate":
out.Values[i] = ec._Mutation_performerUpdate(ctx, field) 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": case "studioCreate":
out.Values[i] = ec._Mutation_studioCreate(ctx, field) out.Values[i] = ec._Mutation_studioCreate(ctx, field)
case "studioUpdate": case "studioUpdate":
out.Values[i] = ec._Mutation_studioUpdate(ctx, field) 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": case "tagCreate":
out.Values[i] = ec._Mutation_tagCreate(ctx, field) out.Values[i] = ec._Mutation_tagCreate(ctx, field)
case "tagUpdate": case "tagUpdate":
@ -11114,6 +11294,10 @@ func (ec *executionContext) unmarshalNPerformerCreateInput2githubᚗcomᚋstasha
return ec.unmarshalInputPerformerCreateInput(ctx, v) 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) { func (ec *executionContext) unmarshalNPerformerUpdateInput2githubᚗcomᚋstashappᚋstashᚋpkgᚋmodelsᚐPerformerUpdateInput(ctx context.Context, v interface{}) (PerformerUpdateInput, error) {
return ec.unmarshalInputPerformerUpdateInput(ctx, v) return ec.unmarshalInputPerformerUpdateInput(ctx, v)
} }
@ -11427,6 +11611,10 @@ func (ec *executionContext) unmarshalNStudioCreateInput2githubᚗcomᚋstashapp
return ec.unmarshalInputStudioCreateInput(ctx, v) 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) { func (ec *executionContext) unmarshalNStudioUpdateInput2githubᚗcomᚋstashappᚋstashᚋpkgᚋmodelsᚐStudioUpdateInput(ctx context.Context, v interface{}) (StudioUpdateInput, error) {
return ec.unmarshalInputStudioUpdateInput(ctx, v) return ec.unmarshalInputStudioUpdateInput(ctx, v)
} }

View file

@ -109,6 +109,10 @@ type PerformerCreateInput struct {
Image string `json:"image"` Image string `json:"image"`
} }
type PerformerDestroyInput struct {
ID string `json:"id"`
}
type PerformerFilterType struct { type PerformerFilterType struct {
// Filter by favorite // Filter by favorite
FilterFavorites *bool `json:"filter_favorites"` FilterFavorites *bool `json:"filter_favorites"`
@ -260,6 +264,10 @@ type StudioCreateInput struct {
Image string `json:"image"` Image string `json:"image"`
} }
type StudioDestroyInput struct {
ID string `json:"id"`
}
type StudioUpdateInput struct { type StudioUpdateInput struct {
ID string `json:"id"` ID string `json:"id"`
Name *string `json:"name"` Name *string `json:"name"`

View file

@ -2,6 +2,7 @@ package models
import ( import (
"database/sql" "database/sql"
"github.com/jmoiron/sqlx" "github.com/jmoiron/sqlx"
"github.com/stashapp/stash/pkg/database" "github.com/stashapp/stash/pkg/database"
) )
@ -54,6 +55,15 @@ func (qb *PerformerQueryBuilder) Update(updatedPerformer Performer, tx *sqlx.Tx)
return &updatedPerformer, nil 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) { func (qb *PerformerQueryBuilder) Find(id int) (*Performer, error) {
query := "SELECT * FROM performers WHERE id = ? LIMIT 1" query := "SELECT * FROM performers WHERE id = ? LIMIT 1"
args := []interface{}{id} args := []interface{}{id}

View file

@ -2,6 +2,7 @@ package models
import ( import (
"database/sql" "database/sql"
"github.com/jmoiron/sqlx" "github.com/jmoiron/sqlx"
"github.com/stashapp/stash/pkg/database" "github.com/stashapp/stash/pkg/database"
) )
@ -50,6 +51,22 @@ func (qb *StudioQueryBuilder) Update(updatedStudio Studio, tx *sqlx.Tx) (*Studio
return &updatedStudio, nil 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) { func (qb *StudioQueryBuilder) Find(id int, tx *sqlx.Tx) (*Studio, error) {
query := "SELECT * FROM studios WHERE id = ? LIMIT 1" query := "SELECT * FROM studios WHERE id = ? LIMIT 1"
args := []interface{}{id} args := []interface{}{id}
@ -86,7 +103,7 @@ func (qb *StudioQueryBuilder) Query(findFilter *FindFilterType) ([]*Studio, int)
var args []interface{} var args []interface{}
body := selectDistinctIDs("studios") body := selectDistinctIDs("studios")
body += ` 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 != "" { if q := findFilter.Q; q != nil && *q != "" {

View file

@ -1,4 +1,5 @@
import { import {
Alert,
Button, Button,
FileInput, FileInput,
Menu, Menu,
@ -8,7 +9,7 @@ import {
Popover, Popover,
} from "@blueprintjs/core"; } from "@blueprintjs/core";
import _ from "lodash"; import _ from "lodash";
import React, { FunctionComponent } from "react"; import React, { FunctionComponent, useState } from "react";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import * as GQL from "../../core/generated-graphql"; import * as GQL from "../../core/generated-graphql";
import { NavigationUtils } from "../../utils/navigation"; import { NavigationUtils } from "../../utils/navigation";
@ -20,6 +21,7 @@ interface IProps {
isEditing: boolean; isEditing: boolean;
onToggleEdit: () => void; onToggleEdit: () => void;
onSave: () => void; onSave: () => void;
onDelete: () => void;
onImageChange: (event: React.FormEvent<HTMLInputElement>) => void; onImageChange: (event: React.FormEvent<HTMLInputElement>) => void;
// TODO: only for performers. make generic // TODO: only for performers. make generic
@ -27,6 +29,8 @@ interface IProps {
} }
export const DetailsEditNavbar: FunctionComponent<IProps> = (props: IProps) => { export const DetailsEditNavbar: FunctionComponent<IProps> = (props: IProps) => {
const [isDeleteAlertOpen, setIsDeleteAlertOpen] = useState<boolean>(false);
function renderEditButton() { function renderEditButton() {
if (props.isNew) { return; } if (props.isNew) { return; }
return ( return (
@ -43,6 +47,11 @@ export const DetailsEditNavbar: FunctionComponent<IProps> = (props: IProps) => {
return <Button intent="success" text="Save" onClick={() => props.onSave()} />; return <Button intent="success" text="Save" onClick={() => props.onSave()} />;
} }
function renderDeleteButton() {
if (props.isNew || props.isEditing) { return; }
return <Button intent="danger" text="Delete" onClick={() => setIsDeleteAlertOpen(true)} />;
}
function renderImageInput() { function renderImageInput() {
if (!props.isEditing) { return; } if (!props.isEditing) { return; }
return <FileInput text="Choose image..." onInputChange={props.onImageChange} inputProps={{accept: ".jpg,.jpeg"}} />; return <FileInput text="Choose image..." onInputChange={props.onImageChange} inputProps={{accept: ".jpg,.jpeg"}} />;
@ -81,7 +90,37 @@ export const DetailsEditNavbar: FunctionComponent<IProps> = (props: IProps) => {
); );
} }
function renderDeleteAlert() {
var name;
if (props.performer) {
name = props.performer.name;
}
if (props.studio) {
name = props.studio.name;
}
return (
<Alert
cancelButtonText="Cancel"
confirmButtonText="Delete"
icon="trash"
intent="danger"
isOpen={isDeleteAlertOpen}
onCancel={() => setIsDeleteAlertOpen(false)}
onConfirm={() => props.onDelete()}
>
<p>
Are you sure you want to delete {name}?
</p>
</Alert>
);
}
return ( return (
<>
{renderDeleteAlert()}
<Navbar> <Navbar>
<Navbar.Group> <Navbar.Group>
{renderEditButton()} {renderEditButton()}
@ -91,7 +130,9 @@ export const DetailsEditNavbar: FunctionComponent<IProps> = (props: IProps) => {
{renderSaveButton()} {renderSaveButton()}
{renderScenesButton()} {renderScenesButton()}
{renderDeleteButton()}
</Navbar.Group> </Navbar.Group>
</Navbar> </Navbar>
</>
); );
}; };

View file

@ -39,6 +39,7 @@ export const Studio: FunctionComponent<IProps> = (props: IProps) => {
const { data, error, loading } = StashService.useFindStudio(props.match.params.id); const { data, error, loading } = StashService.useFindStudio(props.match.params.id);
const updateStudio = StashService.useStudioUpdate(getStudioInput() as GQL.StudioUpdateInput); const updateStudio = StashService.useStudioUpdate(getStudioInput() as GQL.StudioUpdateInput);
const createStudio = StashService.useStudioCreate(getStudioInput() as GQL.StudioCreateInput); const createStudio = StashService.useStudioCreate(getStudioInput() as GQL.StudioCreateInput);
const deleteStudio = StashService.useStudioDestroy(getStudioInput() as GQL.StudioDestroyInput);
function updateStudioEditState(state: Partial<GQL.StudioDataFragment>) { function updateStudioEditState(state: Partial<GQL.StudioDataFragment>) {
setName(state.name); setName(state.name);
@ -95,6 +96,19 @@ export const Studio: FunctionComponent<IProps> = (props: IProps) => {
setIsLoading(false); setIsLoading(false);
} }
async function onDelete() {
setIsLoading(true);
try {
const result = await deleteStudio();
} catch (e) {
ErrorUtils.handle(e);
}
setIsLoading(false);
// redirect to studios page
props.history.push(`/studios`);
}
function onImageChange(event: React.FormEvent<HTMLInputElement>) { function onImageChange(event: React.FormEvent<HTMLInputElement>) {
const file: File = (event.target as any).files[0]; const file: File = (event.target as any).files[0];
const reader: FileReader = new FileReader(); const reader: FileReader = new FileReader();
@ -120,6 +134,7 @@ export const Studio: FunctionComponent<IProps> = (props: IProps) => {
isEditing={isEditing} isEditing={isEditing}
onToggleEdit={() => { setIsEditing(!isEditing); updateStudioEditState(studio); }} onToggleEdit={() => { setIsEditing(!isEditing); updateStudioEditState(studio); }}
onSave={onSave} onSave={onSave}
onDelete={onDelete}
onImageChange={onImageChange} onImageChange={onImageChange}
/> />
<h1 className="bp3-heading"> <h1 className="bp3-heading">

View file

@ -55,6 +55,7 @@ export const Performer: FunctionComponent<IPerformerProps> = (props: IPerformerP
const { data, error, loading } = StashService.useFindPerformer(props.match.params.id); const { data, error, loading } = StashService.useFindPerformer(props.match.params.id);
const updatePerformer = StashService.usePerformerUpdate(getPerformerInput() as GQL.PerformerUpdateInput); const updatePerformer = StashService.usePerformerUpdate(getPerformerInput() as GQL.PerformerUpdateInput);
const createPerformer = StashService.usePerformerCreate(getPerformerInput() as GQL.PerformerCreateInput); const createPerformer = StashService.usePerformerCreate(getPerformerInput() as GQL.PerformerCreateInput);
const deletePerformer = StashService.usePerformerDestroy(getPerformerInput() as GQL.PerformerDestroyInput);
function updatePerformerEditState(state: Partial<GQL.PerformerDataFragment | GQL.ScrapeFreeonesScrapeFreeones>) { function updatePerformerEditState(state: Partial<GQL.PerformerDataFragment | GQL.ScrapeFreeonesScrapeFreeones>) {
if ((state as GQL.PerformerDataFragment).favorite !== undefined) { if ((state as GQL.PerformerDataFragment).favorite !== undefined) {
@ -141,6 +142,19 @@ export const Performer: FunctionComponent<IPerformerProps> = (props: IPerformerP
setIsLoading(false); setIsLoading(false);
} }
async function onDelete() {
setIsLoading(true);
try {
const result = await deletePerformer();
} catch (e) {
ErrorUtils.handle(e);
}
setIsLoading(false);
// redirect to performers page
props.history.push(`/performers`);
}
function onImageChange(event: React.FormEvent<HTMLInputElement>) { function onImageChange(event: React.FormEvent<HTMLInputElement>) {
const file: File = (event.target as any).files[0]; const file: File = (event.target as any).files[0];
const reader: FileReader = new FileReader(); const reader: FileReader = new FileReader();
@ -218,6 +232,7 @@ export const Performer: FunctionComponent<IPerformerProps> = (props: IPerformerP
isEditing={isEditing} isEditing={isEditing}
onToggleEdit={() => { setIsEditing(!isEditing); updatePerformerEditState(performer); }} onToggleEdit={() => { setIsEditing(!isEditing); updatePerformerEditState(performer); }}
onSave={onSave} onSave={onSave}
onDelete={onDelete}
onImageChange={onImageChange} onImageChange={onImageChange}
onDisplayFreeOnesDialog={onDisplayFreeOnesDialog} onDisplayFreeOnesDialog={onDisplayFreeOnesDialog}
/> />

View file

@ -133,6 +133,9 @@ export class StashService {
public static usePerformerUpdate(input: GQL.PerformerUpdateInput) { public static usePerformerUpdate(input: GQL.PerformerUpdateInput) {
return GQL.usePerformerUpdate({ variables: input }); return GQL.usePerformerUpdate({ variables: input });
} }
public static usePerformerDestroy(input: GQL.PerformerDestroyInput) {
return GQL.usePerformerDestroy({ variables: input });
}
public static useSceneUpdate(input: GQL.SceneUpdateInput) { public static useSceneUpdate(input: GQL.SceneUpdateInput) {
return GQL.useSceneUpdate({ variables: input }); return GQL.useSceneUpdate({ variables: input });
@ -148,6 +151,9 @@ export class StashService {
public static useStudioUpdate(input: GQL.StudioUpdateInput) { public static useStudioUpdate(input: GQL.StudioUpdateInput) {
return GQL.useStudioUpdate({ variables: input }); return GQL.useStudioUpdate({ variables: input });
} }
public static useStudioDestroy(input: GQL.StudioDestroyInput) {
return GQL.useStudioDestroy({ variables: input });
}
public static useTagCreate(input: GQL.TagCreateInput) { public static useTagCreate(input: GQL.TagCreateInput) {
return GQL.useTagCreate({ variables: input, refetchQueries: ["AllTags"] }); return GQL.useTagCreate({ variables: input, refetchQueries: ["AllTags"] });

View file

@ -196,6 +196,10 @@ export interface PerformerUpdateInput {
image?: Maybe<string>; image?: Maybe<string>;
} }
export interface PerformerDestroyInput {
id: string;
}
export interface StudioCreateInput { export interface StudioCreateInput {
name: string; name: string;
@ -214,6 +218,10 @@ export interface StudioUpdateInput {
image?: Maybe<string>; image?: Maybe<string>;
} }
export interface StudioDestroyInput {
id: string;
}
export interface TagCreateInput { export interface TagCreateInput {
name: string; name: string;
} }
@ -334,6 +342,16 @@ export type PerformerUpdateMutation = {
export type PerformerUpdatePerformerUpdate = PerformerDataFragment; export type PerformerUpdatePerformerUpdate = PerformerDataFragment;
export type PerformerDestroyVariables = {
id: string;
};
export type PerformerDestroyMutation = {
__typename?: "Mutation";
performerDestroy: boolean;
};
export type SceneMarkerCreateVariables = { export type SceneMarkerCreateVariables = {
title: string; title: string;
seconds: number; seconds: number;
@ -439,6 +457,16 @@ export type StudioUpdateMutation = {
export type StudioUpdateStudioUpdate = StudioDataFragment; export type StudioUpdateStudioUpdate = StudioDataFragment;
export type StudioDestroyVariables = {
id: string;
};
export type StudioDestroyMutation = {
__typename?: "Mutation";
studioDestroy: boolean;
};
export type TagCreateVariables = { export type TagCreateVariables = {
name: string; name: string;
}; };
@ -1664,6 +1692,22 @@ export function usePerformerUpdate(
PerformerUpdateVariables PerformerUpdateVariables
>(PerformerUpdateDocument, baseOptions); >(PerformerUpdateDocument, baseOptions);
} }
export const PerformerDestroyDocument = gql`
mutation PerformerDestroy($id: ID!) {
performerDestroy(input: { id: $id })
}
`;
export function usePerformerDestroy(
baseOptions?: ReactApolloHooks.MutationHookOptions<
PerformerDestroyMutation,
PerformerDestroyVariables
>
) {
return ReactApolloHooks.useMutation<
PerformerDestroyMutation,
PerformerDestroyVariables
>(PerformerDestroyDocument, baseOptions);
}
export const SceneMarkerCreateDocument = gql` export const SceneMarkerCreateDocument = gql`
mutation SceneMarkerCreate( mutation SceneMarkerCreate(
$title: String! $title: String!
@ -1860,6 +1904,22 @@ export function useStudioUpdate(
StudioUpdateVariables StudioUpdateVariables
>(StudioUpdateDocument, baseOptions); >(StudioUpdateDocument, baseOptions);
} }
export const StudioDestroyDocument = gql`
mutation StudioDestroy($id: ID!) {
studioDestroy(input: { id: $id })
}
`;
export function useStudioDestroy(
baseOptions?: ReactApolloHooks.MutationHookOptions<
StudioDestroyMutation,
StudioDestroyVariables
>
) {
return ReactApolloHooks.useMutation<
StudioDestroyMutation,
StudioDestroyVariables
>(StudioDestroyDocument, baseOptions);
}
export const TagCreateDocument = gql` export const TagCreateDocument = gql`
mutation TagCreate($name: String!) { mutation TagCreate($name: String!) {
tagCreate(input: { name: $name }) { tagCreate(input: { name: $name }) {

View file

@ -186,10 +186,14 @@ span.block {
& .tag-list-row { & .tag-list-row {
margin: 10px; margin: 10px;
cursor: pointer;
& .bp3-button { & .bp3-button {
margin: 0 10px; margin: 0 10px;
} }
} }
}
& .tag-list-row:hover {
text-decoration: underline;
}
}