mirror of
https://github.com/dreamhunter2333/cloudflare_temp_email.git
synced 2026-06-13 11:30:29 +08:00
feat(admin): add column sorting and reset pagination on search (#927)
* feat(admin): add column sorting and reset pagination on search (#918) - Add server-side column sorting for admin address list (ID, name, created_at, updated_at, mail_count, send_count) - Reset pagination to page 1 when searching or changing sort order - Add optional orderBy parameter to handleListQuery with whitelist validation Closes #918 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: add JSDoc warning for orderBy parameter in handleListQuery Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: address code review findings - Fix count not resetting to 0 when search returns empty results - Add source_meta column sorting support - Use Object.hasOwn to prevent prototype pollution in sort column lookup Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -22,7 +22,19 @@ import e2e_test_api from './e2e_test_api'
|
||||
export const api = new Hono<HonoCustomType>()
|
||||
|
||||
api.get('/admin/address', async (c) => {
|
||||
const { limit, offset, query } = c.req.query();
|
||||
const { limit, offset, query, sort_by, sort_order } = c.req.query();
|
||||
const allowedSortColumns: Record<string, string> = {
|
||||
'id': 'a.id',
|
||||
'name': 'a.name',
|
||||
'created_at': 'a.created_at',
|
||||
'updated_at': 'a.updated_at',
|
||||
'source_meta': 'a.source_meta',
|
||||
'mail_count': 'mail_count',
|
||||
'send_count': 'send_count',
|
||||
};
|
||||
const sortColumn = Object.hasOwn(allowedSortColumns, sort_by) ? allowedSortColumns[sort_by] : 'a.id';
|
||||
const sortDirection = sort_order === 'ascend' ? 'asc' : 'desc';
|
||||
const orderBy = `${sortColumn} ${sortDirection}`;
|
||||
if (query) {
|
||||
return await handleListQuery(c,
|
||||
`SELECT a.*,`
|
||||
@@ -31,7 +43,7 @@ api.get('/admin/address', async (c) => {
|
||||
+ ` FROM address a`
|
||||
+ ` where name like ?`,
|
||||
`SELECT count(*) as count FROM address where name like ?`,
|
||||
[`%${query}%`], limit, offset
|
||||
[`%${query}%`], limit, offset, orderBy
|
||||
);
|
||||
}
|
||||
return await handleListQuery(c,
|
||||
@@ -40,7 +52,7 @@ api.get('/admin/address', async (c) => {
|
||||
+ ` (SELECT COUNT(*) FROM sendbox WHERE address = a.name) AS send_count`
|
||||
+ ` FROM address a`,
|
||||
`SELECT count(*) as count FROM address`,
|
||||
[], limit, offset
|
||||
[], limit, offset, orderBy
|
||||
);
|
||||
})
|
||||
|
||||
|
||||
@@ -483,7 +483,9 @@ export const handleListQuery = async (
|
||||
c: Context<HonoCustomType>,
|
||||
query: string, countQuery: string, params: string[],
|
||||
limit: string | number | undefined | null,
|
||||
offset: string | number | undefined | null
|
||||
offset: string | number | undefined | null,
|
||||
/** Must be pre-validated (e.g. whitelist), NOT raw user input. Interpolated directly into SQL. */
|
||||
orderBy?: string
|
||||
): Promise<Response> => {
|
||||
const msgs = i18n.getMessagesbyContext(c);
|
||||
if (typeof limit === "string") {
|
||||
@@ -498,7 +500,8 @@ export const handleListQuery = async (
|
||||
if (offset == null || offset == undefined || offset < 0) {
|
||||
return c.text(msgs.InvalidOffsetMsg, 400)
|
||||
}
|
||||
const resultsQuery = `${query} order by id desc limit ? offset ?`;
|
||||
const orderClause = orderBy || 'id desc';
|
||||
const resultsQuery = `${query} order by ${orderClause} limit ? offset ?`;
|
||||
const { results } = await c.env.DB.prepare(resultsQuery).bind(
|
||||
...params, limit, offset
|
||||
).all();
|
||||
|
||||
Reference in New Issue
Block a user