From 6e65993f89c281bec6bcd031045c6b1a16116915 Mon Sep 17 00:00:00 2001 From: Wang Zhuoxuan Date: Sat, 11 Apr 2026 12:56:40 +0800 Subject: [PATCH] feat: add pagination support to admin categories endpoint - Add page/limit query parameters with Zod validation (max 50) - Update listCategories service to return paginated results - Response format includes pagination metadata (total, page, limit) - Matches existing pattern from questions/feedback endpoints Co-Authored-By: Claude Opus 4.6 --- .claude/settings.json | 7 +++++++ .claude/settings.local.json | 15 +++++++++++++ src/routes/admin/categories.ts | 28 ++++++++++++++++++++++--- src/services/admin/category-service.ts | 29 ++++++++++++++++++++++++-- 4 files changed, 74 insertions(+), 5 deletions(-) create mode 100644 .claude/settings.json create mode 100644 .claude/settings.local.json diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000..3d82207 --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,7 @@ +{ + "enabledPlugins": { + "typescript-lsp@claude-plugins-official": true, + "claude-md-management@claude-plugins-official": true, + "glm-plan-usage@zai-coding-plugins": false + } +} diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..62e2c93 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,15 @@ +{ + "permissions": { + "allow": [ + "mcp__plugin_ecc_context7__resolve-library-id", + "mcp__plugin_ecc_context7__query-docs", + "Bash(bun install:*)", + "Bash(bun add:*)", + "Bash(bunx tsc:*)", + "Bash(git status:*)", + "Bash(git add:*)", + "Bash(git commit:*)", + "Bash(bun run:*)" + ] + } +} diff --git a/src/routes/admin/categories.ts b/src/routes/admin/categories.ts index 5d52c41..5a51c00 100644 --- a/src/routes/admin/categories.ts +++ b/src/routes/admin/categories.ts @@ -18,10 +18,32 @@ const updateCategorySchema = z.object({ status: z.enum(['active', 'inactive']).optional(), }); +const listCategoriesQuerySchema = z.object({ + page: z.coerce.number().int().positive().optional().default(1), + limit: z.coerce.number().int().positive().max(50).optional().default(20), +}); + export async function adminCategoriesRoutes(app: FastifyInstance): Promise { - app.get('/', async () => { - const data = await categoryService.listCategories(); - return { success: true, data, error: null }; + app.get('/', async (request) => { + const parsed = listCategoriesQuerySchema.safeParse(request.query); + if (!parsed.success) { + return { + success: false, + data: null, + error: { + code: 'VALIDATION_ERROR', + message: parsed.error.issues[0]?.message ?? 'Invalid query parameters' + } + }; + } + + const result = await categoryService.listCategories(parsed.data); + return { + success: true, + data: result.items, + pagination: result.pagination, + error: null + }; }); app.post('/', async (request) => { diff --git a/src/services/admin/category-service.ts b/src/services/admin/category-service.ts index e730f6f..c8cc4db 100644 --- a/src/services/admin/category-service.ts +++ b/src/services/admin/category-service.ts @@ -2,8 +2,33 @@ import { db } from '../../db/client.js'; import { categories, questions } from '../../db/schema.js'; import { eq, and, sql } from 'drizzle-orm'; -export async function listCategories() { - return db.select().from(categories).orderBy(categories.sortOrder); +interface ListOptions { + page?: number; + limit?: number; +} + +export async function listCategories({ page = 1, limit = 20 }: ListOptions = {}) { + const offset = (page - 1) * limit; + + // Get total count + const [countResult] = await db + .select({ total: sql`COUNT(*)` }) + .from(categories); + + const total = Number(countResult?.total ?? 0); + + // Get paginated items + const items = await db + .select() + .from(categories) + .orderBy(categories.sortOrder) + .limit(limit) + .offset(offset); + + return { + items, + pagination: { total, page, limit } + }; } export async function createCategory(data: { id: string; name: string; slug: string; parentId?: string; sortOrder?: number }) {