From 9601198701c5b77b3ff558fe7b80694307489974 Mon Sep 17 00:00:00 2001 From: aapark Date: Sun, 15 Mar 2026 09:56:31 +0000 Subject: [PATCH] fix(web): paginate admin table fetches to avoid 422 limits --- src/mud_server/web/static/js/api.js | 39 ++++++++++++++++++- .../web/static/js/pages/accounts.js | 4 +- .../web/static/js/pages/characters.js | 6 +-- .../web/static/js/pages/tombstones.js | 4 +- .../web/static/js/pages/users_data.js | 6 +-- 5 files changed, 47 insertions(+), 12 deletions(-) diff --git a/src/mud_server/web/static/js/api.js b/src/mud_server/web/static/js/api.js index 1fe26c9..fd034b2 100644 --- a/src/mud_server/web/static/js/api.js +++ b/src/mud_server/web/static/js/api.js @@ -138,14 +138,49 @@ class ApiClient { */ async getTableRows(sessionId, tableName, limit = 100, offset = 0) { const safeTableName = encodeURIComponent(tableName); + const requestedLimit = Number(limit); + const safeLimit = Number.isFinite(requestedLimit) + ? Math.min(Math.max(Math.trunc(requestedLimit), 1), 1000) + : 100; + const requestedOffset = Number(offset); + const safeOffset = Number.isFinite(requestedOffset) ? Math.max(Math.trunc(requestedOffset), 0) : 0; const params = new URLSearchParams({ session_id: sessionId, - limit: `${limit}`, - offset: `${offset}`, + limit: `${safeLimit}`, + offset: `${safeOffset}`, }); return this.fetcher(`/admin/database/table/${safeTableName}?${params.toString()}`); } + /** + * Fetch all rows for a table using paged requests. + */ + async getAllTableRows(sessionId, tableName, pageSize = 1000) { + const boundedPageSize = Math.min(Math.max(Math.trunc(Number(pageSize) || 1000), 1), 1000); + const allRows = []; + let columns = []; + let offset = 0; + + while (true) { + const response = await this.getTableRows(sessionId, tableName, boundedPageSize, offset); + if (!columns.length) { + columns = Array.isArray(response.columns) ? response.columns : []; + } + const pageRows = Array.isArray(response.rows) ? response.rows : []; + allRows.push(...pageRows); + if (pageRows.length < boundedPageSize) { + break; + } + offset += pageRows.length; + } + + return { + table: tableName, + columns, + rows: allRows, + }; + } + /** * Fetch world operations rows (status + active character sessions). */ diff --git a/src/mud_server/web/static/js/pages/accounts.js b/src/mud_server/web/static/js/pages/accounts.js index 07bad8b..84b1c06 100644 --- a/src/mud_server/web/static/js/pages/accounts.js +++ b/src/mud_server/web/static/js/pages/accounts.js @@ -247,9 +247,9 @@ async function renderAccountsDashboard(root, { api, session }) { const [accountsResponse, charactersResponse, worldsResponse, worldPermsResponse] = await Promise.all([ api.getPlayers(sessionId), - api.getTableRows(sessionId, 'characters', 2000), + api.getAllTableRows(sessionId, 'characters'), api.getTableRows(sessionId, 'worlds', 300), - api.getTableRows(sessionId, 'world_permissions', 2000), + api.getAllTableRows(sessionId, 'world_permissions'), ]); allAccounts = Array.isArray(accountsResponse.players) ? accountsResponse.players : []; diff --git a/src/mud_server/web/static/js/pages/characters.js b/src/mud_server/web/static/js/pages/characters.js index badc71f..b2e1360 100644 --- a/src/mud_server/web/static/js/pages/characters.js +++ b/src/mud_server/web/static/js/pages/characters.js @@ -385,10 +385,10 @@ async function renderCharactersDashboard(root, { api, session }) { */ const load = async () => { const [charactersResponse, usersResponse, worldsResponse, locationsResponse] = await Promise.all([ - api.getTableRows(sessionId, 'characters', 3000), - api.getTableRows(sessionId, 'users', 2000), + api.getAllTableRows(sessionId, 'characters'), + api.getAllTableRows(sessionId, 'users'), api.getTableRows(sessionId, 'worlds', 300), - api.getTableRows(sessionId, 'character_locations', 3000), + api.getAllTableRows(sessionId, 'character_locations'), ]); allCharacters = rowsToObjects(charactersResponse.columns, charactersResponse.rows).filter( diff --git a/src/mud_server/web/static/js/pages/tombstones.js b/src/mud_server/web/static/js/pages/tombstones.js index fe956d4..23b5c7a 100644 --- a/src/mud_server/web/static/js/pages/tombstones.js +++ b/src/mud_server/web/static/js/pages/tombstones.js @@ -132,8 +132,8 @@ async function renderTombstonesDashboard(root, { api, session }) { */ const load = async () => { const [usersResponse, charactersResponse, worldsResponse] = await Promise.all([ - api.getTableRows(sessionId, 'users', 4000), - api.getTableRows(sessionId, 'characters', 4000), + api.getAllTableRows(sessionId, 'users'), + api.getAllTableRows(sessionId, 'characters'), api.getTableRows(sessionId, 'worlds', 300), ]); diff --git a/src/mud_server/web/static/js/pages/users_data.js b/src/mud_server/web/static/js/pages/users_data.js index 1bf1b1f..acba4b2 100644 --- a/src/mud_server/web/static/js/pages/users_data.js +++ b/src/mud_server/web/static/js/pages/users_data.js @@ -53,10 +53,10 @@ async function loadUsersList({ api, sessionId }) { */ async function loadUsersMetadata({ api, sessionId }) { const [charactersResp, worldsResp, permissionsResp, locationsResp] = await Promise.all([ - api.getTableRows(sessionId, 'characters', 1000), + api.getAllTableRows(sessionId, 'characters'), api.getTableRows(sessionId, 'worlds', 200), - api.getTableRows(sessionId, 'world_permissions', 1000), - api.getTableRows(sessionId, 'character_locations', 1000), + api.getAllTableRows(sessionId, 'world_permissions'), + api.getAllTableRows(sessionId, 'character_locations'), ]); const worldRows = rowsToObjects(worldsResp.columns, worldsResp.rows);