From bc7eb9dfdd5b98c0264aa6b5970cf0adb106aeb1 Mon Sep 17 00:00:00 2001 From: NanamiAdmin Date: Wed, 9 Sep 2026 16:15:33 +0800 Subject: [PATCH] feat: update API response structure to nest payloads under a single "data" key --- README.md | 16 ++++++++-------- frontend/src/api/index.js | 3 ++- frontend/src/views/Login.vue | 20 +++++++++++--------- frontend/src/views/Overview.vue | 11 ++++++----- frontend/src/views/Settings.vue | 2 +- frontend/src/views/Trusted.vue | 2 +- handler/settings.go | 2 +- utils/auth.go | 9 ++++++--- 8 files changed, 36 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 8913c3a..fe75d49 100644 --- a/README.md +++ b/README.md @@ -257,7 +257,7 @@ Admins and trusted groups are defined **per bot channel** and map a member ID to ## API -All responses follow the envelope `{"success": true|false, "message": "...", ...data}`. `message` is empty on success unless noted. +Success responses follow the envelope `{"success": true, "message": "...", "data": {...}}`, with the payload nested under a single `data` key. Error responses use `{"success": false, "message": "..."}`. `message` may be omitted on success when there is nothing to report. ### Authentication @@ -274,15 +274,15 @@ Tokens idle for more than 1 hour are expired (cleaned every 10 minutes); any aut | Endpoint | Method | Permission | Description | |---|---|---|---| -| `/api/user/login` | POST | None | Log in. Body `{username, password}`. Returns `{token, userID, username, level, registerDate}`. | -| `/api/user/register` | POST | None | Register the first user. Body `{username, password}`. Only allowed while no user exists; otherwise `403`. Returns `{token, userID, username, level}`. | +| `/api/user/login` | POST | None | Log in. Body `{username, password}`. Returns `data: {token, userID, username, level, registerDate}`. | +| `/api/user/register` | POST | None | Register the first user. Body `{username, password}`. Only allowed while no user exists; otherwise `403`. Returns `data: {token, userID, username, level}`. | | `/api/server/list` | GET | bot / admin | List all monitored servers. | -| `/api/server/getInfo` | GET | admin | Static server info (mirrors the Bot's `/info`). Query `?uuid=` (or `all`). Returns `{success, : {uuid, name, info}}` — one entry per requested server. `404` for an unknown single uuid. | -| `/api/server/getStatus` | GET | admin | Live server status (mirrors the Bot's `/status`). Query `?uuid=` (or `all`). Returns `{success, : {uuid, name, online, report}}`; `report` is `null` when the node has not reported yet. `404` for an unknown single uuid. | -| `/api/server/exec` | POST | bot / admin | Execute a command. Body `{uuid: [...], command}`. Dispatches a Komari task and polls until completion (or timeout). Returns `{taskID, results}`. | -| `/api/settings/get` | GET | admin | `?type=global\|bot_user_config\|bot_node_config` | Returns `{success, config}`, where `config` is the selected config file's content (same layout as the JSON file). | +| `/api/server/getInfo` | GET | admin | Static server info (mirrors the Bot's `/info`). Query `?uuid=` (or `all`). Returns `data: {: {uuid, name, info}}` — one entry per requested server. `404` for an unknown single uuid. | +| `/api/server/getStatus` | GET | admin | Live server status (mirrors the Bot's `/status`). Query `?uuid=` (or `all`). Returns `data: {: {uuid, name, online, report}}`; `report` is `null` when the node has not reported yet. `404` for an unknown single uuid. | +| `/api/server/exec` | POST | bot / admin | Execute a command. Body `{uuid: [...], command}`. Dispatches a Komari task and polls until completion (or timeout). Returns `data: {taskID, results}`. | +| `/api/settings/get` | GET | admin | `?type=global\|bot_user_config\|bot_node_config` | Returns `data: {config}`, where `config` is the selected config file's content (same layout as the JSON file). | | `/api/settings/set` | POST | admin | `?type=` + JSON body of partial updates, e.g. `{"system":{"debugMode":true}}` | Deep-merges the body into the selected config file, persists it, and reloads it in memory. Only the given keys change; arrays replace. | -| `/health` | GET | None | Health check. Returns `{status, database}`. | +| `/health` | GET | None | Health check. Returns `data: {status, database}`. | | `/api/system/getLogs` | WebSocket | None | Streams logs. Sends the last 100 buffered entries, then live `{level, content, timestamp}` events. | Middleware applied to the whole server: diff --git a/frontend/src/api/index.js b/frontend/src/api/index.js index ec26120..c058db9 100644 --- a/frontend/src/api/index.js +++ b/frontend/src/api/index.js @@ -5,13 +5,14 @@ export const authApi = { register: (username, password) => http.post('/user/register', { username, password }) }; -// /api/server/getStatus?uuid=all → { success, "": { uuid, name, online, report } } +// /api/server/getStatus?uuid=all → { success, message, data: { "": { uuid, name, online, report } } } export const serverApi = { statusAll: () => http.get('/server/getStatus?uuid=all'), infoAll: () => http.get('/server/getInfo?uuid=all') }; // /api/settings/get?type=… / /api/settings/set?type=… +// get → { success, message, data: { config } }. // `type` is one of global | bot_user_config | bot_node_config. // For set, pass a partial object; a JSON null value removes that key. export const settingsApi = { diff --git a/frontend/src/views/Login.vue b/frontend/src/views/Login.vue index e9d60ae..947df88 100644 --- a/frontend/src/views/Login.vue +++ b/frontend/src/views/Login.vue @@ -34,18 +34,20 @@ async function submit() { try { if (mode.value === 'login') { const res = await authApi.login(form.username.trim(), form.password); - afterLogin(res.token, { - userID: res.userID, - username: res.username, - level: res.level, - registerDate: res.registerDate + const d = res.data; + afterLogin(d.token, { + userID: d.userID, + username: d.username, + level: d.level, + registerDate: d.registerDate }); } else { const res = await authApi.register(form.username.trim(), form.password); - afterLogin(res.token, { - userID: res.userID, - username: res.username, - level: res.level + const d = res.data; + afterLogin(d.token, { + userID: d.userID, + username: d.username, + level: d.level }); toast.success('Admin created — welcome to Nukumizu'); } diff --git a/frontend/src/views/Overview.vue b/frontend/src/views/Overview.vue index cf0c21c..d5d74d8 100644 --- a/frontend/src/views/Overview.vue +++ b/frontend/src/views/Overview.vue @@ -43,12 +43,13 @@ async function load() { serverApi.infoAll(), settingsApi.get('bot_node_config') ]); - const nodeConf = (nodes && nodes.config) || {}; + const statusData = (status && status.data) || {}; + const infoData = (info && info.data) || {}; + const nodeConf = (nodes && nodes.data && nodes.data.config) || {}; const list = []; - for (const key of Object.keys(status)) { - if (key === 'success') continue; - const s = status[key] || {}; - const infoEntry = info[key] || {}; + for (const key of Object.keys(statusData)) { + const s = statusData[key] || {}; + const infoEntry = infoData[key] || {}; const conf = nodeConf[key] || {}; list.push({ uuid: s.uuid || key, diff --git a/frontend/src/views/Settings.vue b/frontend/src/views/Settings.vue index 5df4a02..cbc1b80 100644 --- a/frontend/src/views/Settings.vue +++ b/frontend/src/views/Settings.vue @@ -168,7 +168,7 @@ function defaults(f) { async function load() { try { const res = await settingsApi.get('global'); - const cfg = (res && res.config) || {}; + const cfg = (res && res.data && res.data.config) || {}; for (const s of sections) { const obj = {}; for (const f of s.fields) { diff --git a/frontend/src/views/Trusted.vue b/frontend/src/views/Trusted.vue index 5790e2c..28de198 100644 --- a/frontend/src/views/Trusted.vue +++ b/frontend/src/views/Trusted.vue @@ -50,7 +50,7 @@ const defaultOpts = () => ({ async function load() { try { const res = await settingsApi.get('bot_user_config'); - const cfg = (res && res.config) || {}; + const cfg = (res && res.data && res.data.config) || {}; const flat = []; for (const c of CHANNELS) { const chData = cfg[c.key] || {}; diff --git a/handler/settings.go b/handler/settings.go index d48a9fe..9200b80 100644 --- a/handler/settings.go +++ b/handler/settings.go @@ -11,7 +11,7 @@ import ( // 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 "config" +// "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") { diff --git a/utils/auth.go b/utils/auth.go index 57f4add..91e4bfe 100644 --- a/utils/auth.go +++ b/utils/auth.go @@ -203,7 +203,9 @@ func StartTokenCleaner() { }() } -// SendSuccessResponse sends a standardized JSON success response. +// SendSuccessResponse sends a standardized JSON success response. Every payload +// is nested under a single "data" key, so success responses use the envelope +// {"success": true, "message": "...", "data": {...}}. func SendSuccessResponse(w http.ResponseWriter, message string, data map[string]interface{}) { w.Header().Set("Content-Type", "application/json") resp := map[string]interface{}{ @@ -212,9 +214,10 @@ func SendSuccessResponse(w http.ResponseWriter, message string, data map[string] if message != "" { resp["message"] = message } - for k, v := range data { - resp[k] = v + if data == nil { + data = map[string]interface{}{} } + resp["data"] = data json.NewEncoder(w).Encode(resp) }