From 292e87951573b7bd355200d356b4f683698752e8 Mon Sep 17 00:00:00 2001 From: NanamiAdmin Date: Tue, 8 Sep 2026 23:10:26 +0800 Subject: [PATCH] feat(config): make resolver support `null` value to delete a key. --- config/settings.go | 8 +++++++- config/settings_test.go | 43 +++++++++++++++++++++++++++++++++++++++++ database/user.go | 2 +- 3 files changed, 51 insertions(+), 2 deletions(-) diff --git a/config/settings.go b/config/settings.go index 4295607..46ed373 100644 --- a/config/settings.go +++ b/config/settings.go @@ -121,9 +121,15 @@ func UpdateSettings(settingsType string, patch map[string]interface{}) error { // deepMergeSettings recursively overlays src onto dst. Object values merge // 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{}) { for key, srcVal := range src { + if srcVal == nil { + delete(dst, key) + continue + } srcObj, srcIsObj := srcVal.(map[string]interface{}) if srcIsObj { if dstObj, ok := dst[key].(map[string]interface{}); ok { diff --git a/config/settings_test.go b/config/settings_test.go index 028d4d4..e53ddc0 100644 --- a/config/settings_test.go +++ b/config/settings_test.go @@ -176,3 +176,46 @@ func TestGetSettingsBotNodeMissingFile(t *testing.T) { 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) + } + } +} diff --git a/database/user.go b/database/user.go index bdaeec5..8870ed1 100644 --- a/database/user.go +++ b/database/user.go @@ -108,7 +108,7 @@ func RegisterFirstUser(username, password, level string) (int64, error) { return 0, fmt.Errorf("failed to check existing users: %w", err) } if count > 0 { - return 0, errors.New("registration closed: users already exist") + return 0, ErrUsersExist } result, err := tx.Exec(