Every settings update answered with a bare success, so the console could not tell a change that took effect at once from one written to the file that the running program would keep ignoring until it was restarted — the listener address, the storage paths, the Komari dashboard URL. Classify the patch instead. config.RestartRequiredKeys expands a patch into the dot-separated paths of its leaves and intersects them with the settings main reads before it starts serving, and the settings endpoint returns that list as data.restartRequired. It is a pure function of the patch, so UpdateSettings keeps its signature and nothing else has to change; the write succeeds either way, and the field only says which edits are not live. Matching is by overlap rather than equality, so a patch that names a section — replacing it, or deleting it with a null — reports the startup-only keys inside it, while a sibling subtree like webhook.endpoints is not mistaken for webhook.enabled. The result is never nil, so an update with nothing to report serializes as [] rather than null and a client can iterate it without a guard. The console reads the field in ConfigSection and warns instead of confirming, naming the keys that are pending; Settings.vue's hints for the Komari URL, the listen address and the storage paths now say which of their fields is affected rather than labelling the whole card.
94 lines
3.1 KiB
Go
94 lines
3.1 KiB
Go
package handler
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
|
|
"nukumizu-backend/config"
|
|
"nukumizu-backend/postLog"
|
|
"nukumizu-backend/utils"
|
|
)
|
|
|
|
// SettingsGetHandler handles GET /api/settings/get?type=xxx.
|
|
// type selects which config file to return and may be one of
|
|
// "global", "bot_user_config" or "bot_node_config"; the returned data.config
|
|
// object has the same layout as the source JSON file.
|
|
func SettingsGetHandler(w http.ResponseWriter, r *http.Request) {
|
|
if !utils.Auth(w, r, "GET", "admin") {
|
|
return
|
|
}
|
|
|
|
settingsType := r.URL.Query().Get("type")
|
|
if settingsType == "" {
|
|
utils.SendErrorResponse(w, http.StatusBadRequest, "missing type parameter")
|
|
return
|
|
}
|
|
if !config.IsValidSettingsType(settingsType) {
|
|
utils.SendErrorResponse(w, http.StatusBadRequest, "invalid type parameter: "+settingsType)
|
|
return
|
|
}
|
|
|
|
data, err := config.GetSettings(settingsType)
|
|
if err != nil {
|
|
utils.SendErrorResponse(w, http.StatusInternalServerError, "failed to get settings: "+err.Error())
|
|
return
|
|
}
|
|
|
|
utils.SendSuccessResponse(w, "", map[string]interface{}{
|
|
"config": json.RawMessage(data),
|
|
})
|
|
}
|
|
|
|
// SettingsSetHandler handles POST /api/settings/set?type=xxx.
|
|
// type selects which config file to update and may be one of "global",
|
|
// "bot_user_config" or "bot_node_config". The JSON body is a partial config
|
|
// object whose keys map directly to config entries, e.g.
|
|
//
|
|
// {"system": {"debugMode": true}}
|
|
//
|
|
// Multiple entries may be given at once; only the provided keys are changed.
|
|
//
|
|
// The response carries data.restartRequired: the keys the update changed that
|
|
// are only read at startup, so the caller can say which edits are not live yet.
|
|
// It is empty for an update that took effect in full.
|
|
func SettingsSetHandler(w http.ResponseWriter, r *http.Request) {
|
|
if !utils.Auth(w, r, "POST", "admin") {
|
|
return
|
|
}
|
|
|
|
settingsType := r.URL.Query().Get("type")
|
|
if settingsType == "" {
|
|
utils.SendErrorResponse(w, http.StatusBadRequest, "missing type parameter")
|
|
return
|
|
}
|
|
if !config.IsValidSettingsType(settingsType) {
|
|
utils.SendErrorResponse(w, http.StatusBadRequest, "invalid type parameter: "+settingsType)
|
|
return
|
|
}
|
|
|
|
dec := json.NewDecoder(r.Body)
|
|
dec.UseNumber() // Keep numeric values verbatim (e.g. QQ IDs) instead of float64.
|
|
var patch map[string]interface{}
|
|
if err := dec.Decode(&patch); err != nil {
|
|
utils.SendErrorResponse(w, http.StatusBadRequest, "invalid request body: expected a JSON object")
|
|
return
|
|
}
|
|
if patch == nil {
|
|
utils.SendErrorResponse(w, http.StatusBadRequest, "request body must be a JSON object")
|
|
return
|
|
}
|
|
|
|
if err := config.UpdateSettings(settingsType, patch); err != nil {
|
|
postLog.Error("Failed to update " + settingsType + " settings: " + err.Error())
|
|
utils.SendErrorResponse(w, http.StatusInternalServerError, "failed to update settings: "+err.Error())
|
|
return
|
|
}
|
|
|
|
// The write landed either way. This only tells the caller which of the keys
|
|
// it changed will not be live until the program is restarted.
|
|
utils.SendSuccessResponse(w, "settings updated successfully", map[string]interface{}{
|
|
"type": settingsType,
|
|
"restartRequired": config.RestartRequiredKeys(settingsType, patch),
|
|
})
|
|
}
|