Add support for removing custom field keys (#6362)

This commit is contained in:
WithoutPants
2025-12-04 07:28:06 +11:00
committed by GitHub
parent ee61fc879b
commit 0bc4faef2a
6 changed files with 98 additions and 11 deletions

View File

@@ -9,6 +9,8 @@ type CustomFieldsInput struct {
Full map[string]interface{} `json:"full"`
// If populated, only the keys in this map will be updated
Partial map[string]interface{} `json:"partial"`
// Remove any keys in this list
Remove []string `json:"remove"`
}
type CustomFieldsReader interface {

View File

@@ -41,18 +41,31 @@ func (s *customFieldsStore) SetCustomFields(ctx context.Context, id int, values
case values.Partial != nil:
partial = true
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 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
// no leading or trailing whitespace, no empty strings
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
}
@@ -130,6 +150,22 @@ func (s *customFieldsStore) setCustomFields(ctx context.Context, id int, values
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) {
q := dialect.Select("field", "value").From(s.table).Where(s.fk.Eq(id))

View File

@@ -64,6 +64,18 @@ func TestSetCustomFields(t *testing.T) {
}),
false,
},
{
"valid remove",
models.CustomFieldsInput{
Remove: []string{"real"},
},
func() map[string]interface{} {
m := getPerformerCustomFields(performerIdx)
delete(m, "real")
return m
}(),
false,
},
{
"leading space full",
models.CustomFieldsInput{
@@ -144,16 +156,38 @@ func TestSetCustomFields(t *testing.T) {
nil,
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
store := db.Performer
id := performerIDs[performerIdx]
assert := assert.New(t)
for _, tt := range tests {
runWithRollbackTxn(t, tt.name, func(t *testing.T, ctx context.Context) {
assert := assert.New(t)
err := store.SetCustomFields(ctx, id, tt.input)
if (err != nil) != tt.wantErr {
t.Errorf("SetCustomFields() error = %v, wantErr %v", err, tt.wantErr)