feat(config): make resolver support null value to delete a

key.
This commit is contained in:
2026-09-08 23:10:26 +08:00
parent 2a1aa3d4ab
commit 292e879515
3 changed files with 51 additions and 2 deletions
+7 -1
View File
@@ -121,9 +121,15 @@ func UpdateSettings(settingsType string, patch map[string]interface{}) error {
// deepMergeSettings recursively overlays src onto dst. Object values merge // deepMergeSettings recursively overlays src onto dst. Object values merge
// key-by-key so partial updates keep sibling keys untouched; arrays and scalars // key-by-key so partial updates keep sibling keys untouched; arrays and scalars
// always replace the destination value. // always replace the destination value. A JSON null in the payload removes that
// key from dst, giving clients a way to delete entries (members, nodes, map
// rows) through /api/settings/set.
func deepMergeSettings(dst, src map[string]interface{}) { func deepMergeSettings(dst, src map[string]interface{}) {
for key, srcVal := range src { for key, srcVal := range src {
if srcVal == nil {
delete(dst, key)
continue
}
srcObj, srcIsObj := srcVal.(map[string]interface{}) srcObj, srcIsObj := srcVal.(map[string]interface{})
if srcIsObj { if srcIsObj {
if dstObj, ok := dst[key].(map[string]interface{}); ok { if dstObj, ok := dst[key].(map[string]interface{}); ok {
+43
View File
@@ -176,3 +176,46 @@ func TestGetSettingsBotNodeMissingFile(t *testing.T) {
t.Errorf("expected empty object for missing bot_node_config, got %s", data) t.Errorf("expected empty object for missing bot_node_config, got %s", data)
} }
} }
func TestUpdateSettingsRemovesKeysWithNull(t *testing.T) {
writeTempConfig(t, &global.ConfigPath.BotUserConfig, `{
"qq(napcat)": {
"admins": {
"3526453517": { "event_status_notify": true },
"740724778": { "event_status_notify": false }
},
"trustedGroups": {
"999": { "event_bot_started": true }
}
},
"telegram": {
"admins": {}
}
}`)
// null removes a nested member and keeps its siblings; an empty section stays.
patch := map[string]interface{}{
"qq(napcat)": map[string]interface{}{
"admins": map[string]interface{}{
"3526453517": nil,
},
},
}
if err := UpdateSettings(SettingBotUserConfig, patch); err != nil {
t.Fatalf("UpdateSettings: %v", err)
}
data, err := GetSettings(SettingBotUserConfig)
if err != nil {
t.Fatalf("GetSettings: %v", err)
}
got := string(data)
if strings.Contains(got, "3526453517") {
t.Errorf("deleted member still present:\n%s", got)
}
for _, want := range []string{"740724778", `"trustedGroups"`, `"telegram"`} {
if !strings.Contains(got, want) {
t.Errorf("unrelated content missing %q:\n%s", want, got)
}
}
}
+1 -1
View File
@@ -108,7 +108,7 @@ func RegisterFirstUser(username, password, level string) (int64, error) {
return 0, fmt.Errorf("failed to check existing users: %w", err) return 0, fmt.Errorf("failed to check existing users: %w", err)
} }
if count > 0 { if count > 0 {
return 0, errors.New("registration closed: users already exist") return 0, ErrUsersExist
} }
result, err := tx.Exec( result, err := tx.Exec(