mirror of
https://github.com/stashapp/stash.git
synced 2025-12-06 08:26:00 +01:00
Add support for removing custom field keys
This commit is contained in:
parent
e213fde0cc
commit
2c86639c71
6 changed files with 98 additions and 11 deletions
|
|
@ -344,4 +344,6 @@ input CustomFieldsInput {
|
||||||
full: Map
|
full: Map
|
||||||
"If populated, only the keys in this map will be updated"
|
"If populated, only the keys in this map will be updated"
|
||||||
partial: Map
|
partial: Map
|
||||||
|
"Remove any keys in this list"
|
||||||
|
remove: [String!]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
12
internal/api/custom_fields.go
Normal file
12
internal/api/custom_fields.go
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import "github.com/stashapp/stash/pkg/models"
|
||||||
|
|
||||||
|
func handleUpdateCustomFields(input models.CustomFieldsInput) models.CustomFieldsInput {
|
||||||
|
ret := input
|
||||||
|
// convert json.Numbers to int/float
|
||||||
|
ret.Full = convertMapJSONNumbers(ret.Full)
|
||||||
|
ret.Partial = convertMapJSONNumbers(ret.Partial)
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
@ -297,10 +297,7 @@ func (r *mutationResolver) PerformerUpdate(ctx context.Context, input models.Per
|
||||||
return nil, fmt.Errorf("converting tag ids: %w", err)
|
return nil, fmt.Errorf("converting tag ids: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
updatedPerformer.CustomFields = input.CustomFields
|
updatedPerformer.CustomFields = handleUpdateCustomFields(input.CustomFields)
|
||||||
// convert json.Numbers to int/float
|
|
||||||
updatedPerformer.CustomFields.Full = convertMapJSONNumbers(updatedPerformer.CustomFields.Full)
|
|
||||||
updatedPerformer.CustomFields.Partial = convertMapJSONNumbers(updatedPerformer.CustomFields.Partial)
|
|
||||||
|
|
||||||
var imageData []byte
|
var imageData []byte
|
||||||
imageIncluded := translator.hasField("image")
|
imageIncluded := translator.hasField("image")
|
||||||
|
|
@ -417,6 +414,10 @@ func (r *mutationResolver) BulkPerformerUpdate(ctx context.Context, input BulkPe
|
||||||
return nil, fmt.Errorf("converting tag ids: %w", err)
|
return nil, fmt.Errorf("converting tag ids: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if input.CustomFields != nil {
|
||||||
|
updatedPerformer.CustomFields = handleUpdateCustomFields(*input.CustomFields)
|
||||||
|
}
|
||||||
|
|
||||||
ret := []*models.Performer{}
|
ret := []*models.Performer{}
|
||||||
|
|
||||||
// Start the transaction and save the performers
|
// Start the transaction and save the performers
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,8 @@ type CustomFieldsInput struct {
|
||||||
Full map[string]interface{} `json:"full"`
|
Full map[string]interface{} `json:"full"`
|
||||||
// If populated, only the keys in this map will be updated
|
// If populated, only the keys in this map will be updated
|
||||||
Partial map[string]interface{} `json:"partial"`
|
Partial map[string]interface{} `json:"partial"`
|
||||||
|
// Remove any keys in this list
|
||||||
|
Remove []string `json:"remove"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type CustomFieldsReader interface {
|
type CustomFieldsReader interface {
|
||||||
|
|
|
||||||
|
|
@ -41,18 +41,31 @@ func (s *customFieldsStore) SetCustomFields(ctx context.Context, id int, values
|
||||||
case values.Partial != nil:
|
case values.Partial != nil:
|
||||||
partial = true
|
partial = true
|
||||||
valMap = values.Partial
|
valMap = values.Partial
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.validateCustomFields(valMap); err != nil {
|
if valMap != nil {
|
||||||
|
if err := s.validateCustomFields(valMap, values.Remove); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.setCustomFields(ctx, id, valMap, partial); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.deleteCustomFields(ctx, id, values.Remove); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return s.setCustomFields(ctx, id, valMap, partial)
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *customFieldsStore) validateCustomFields(values map[string]interface{}) error {
|
func (s *customFieldsStore) validateCustomFields(values map[string]interface{}, deleteKeys []string) error {
|
||||||
|
// if values is nil, nothing to validate
|
||||||
|
if values == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// ensure that custom field names are valid
|
// ensure that custom field names are valid
|
||||||
// no leading or trailing whitespace, no empty strings
|
// no leading or trailing whitespace, no empty strings
|
||||||
for k := range values {
|
for k := range values {
|
||||||
|
|
@ -61,6 +74,13 @@ func (s *customFieldsStore) validateCustomFields(values map[string]interface{})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ensure delete keys are not also in values
|
||||||
|
for _, k := range deleteKeys {
|
||||||
|
if _, ok := values[k]; ok {
|
||||||
|
return fmt.Errorf("custom field name %q cannot be in both values and delete keys", k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -130,6 +150,22 @@ func (s *customFieldsStore) setCustomFields(ctx context.Context, id int, values
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *customFieldsStore) deleteCustomFields(ctx context.Context, id int, keys []string) error {
|
||||||
|
if len(keys) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
q := dialect.Delete(s.table).
|
||||||
|
Where(s.fk.Eq(id)).
|
||||||
|
Where(goqu.I("field").In(keys))
|
||||||
|
|
||||||
|
if _, err := exec(ctx, q); err != nil {
|
||||||
|
return fmt.Errorf("deleting custom fields: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *customFieldsStore) GetCustomFields(ctx context.Context, id int) (map[string]interface{}, error) {
|
func (s *customFieldsStore) GetCustomFields(ctx context.Context, id int) (map[string]interface{}, error) {
|
||||||
q := dialect.Select("field", "value").From(s.table).Where(s.fk.Eq(id))
|
q := dialect.Select("field", "value").From(s.table).Where(s.fk.Eq(id))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -64,6 +64,18 @@ func TestSetCustomFields(t *testing.T) {
|
||||||
}),
|
}),
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"valid remove",
|
||||||
|
models.CustomFieldsInput{
|
||||||
|
Remove: []string{"real"},
|
||||||
|
},
|
||||||
|
func() map[string]interface{} {
|
||||||
|
m := getPerformerCustomFields(performerIdx)
|
||||||
|
delete(m, "real")
|
||||||
|
return m
|
||||||
|
}(),
|
||||||
|
false,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"leading space full",
|
"leading space full",
|
||||||
models.CustomFieldsInput{
|
models.CustomFieldsInput{
|
||||||
|
|
@ -144,16 +156,38 @@ func TestSetCustomFields(t *testing.T) {
|
||||||
nil,
|
nil,
|
||||||
true,
|
true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"invalid remove full",
|
||||||
|
models.CustomFieldsInput{
|
||||||
|
Full: map[string]interface{}{
|
||||||
|
"key": "value",
|
||||||
|
},
|
||||||
|
Remove: []string{"key"},
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"invalid remove partial",
|
||||||
|
models.CustomFieldsInput{
|
||||||
|
Partial: map[string]interface{}{
|
||||||
|
"real": float64(4.56),
|
||||||
|
},
|
||||||
|
Remove: []string{"real"},
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
true,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// use performer custom fields store
|
// use performer custom fields store
|
||||||
store := db.Performer
|
store := db.Performer
|
||||||
id := performerIDs[performerIdx]
|
id := performerIDs[performerIdx]
|
||||||
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
runWithRollbackTxn(t, tt.name, func(t *testing.T, ctx context.Context) {
|
runWithRollbackTxn(t, tt.name, func(t *testing.T, ctx context.Context) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
err := store.SetCustomFields(ctx, id, tt.input)
|
err := store.SetCustomFields(ctx, id, tt.input)
|
||||||
if (err != nil) != tt.wantErr {
|
if (err != nil) != tt.wantErr {
|
||||||
t.Errorf("SetCustomFields() error = %v, wantErr %v", err, tt.wantErr)
|
t.Errorf("SetCustomFields() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue