From e136433e7a7385a113b15bba199c45d1f74cf238 Mon Sep 17 00:00:00 2001 From: alphaeusmote <41258468-alphaeusmote@users.noreply.replit.com> Date: Wed, 9 Apr 2025 17:53:08 +0000 Subject: [PATCH] Add pagination to audit log retrieval. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 705f2157-ef97-4fbd-89e4-8c7f2ecaea90 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/7ed01c5f-a82d-405a-b728-b2e3d127c60c/1081a452-f77b-4ab9-a9b8-4f930a0a540d.jpg --- server/routes.ts | 98 ++++++++++++++++++++++++++++++++++++++++------- server/storage.ts | 61 +++++++++++++++++++++-------- 2 files changed, 130 insertions(+), 29 deletions(-) diff --git a/server/routes.ts b/server/routes.ts index 1db8a0b..da686ae 100644 --- a/server/routes.ts +++ b/server/routes.ts @@ -3585,22 +3585,58 @@ export async function registerRoutes(app: Express): Promise { * tags: [Audit Logs] * security: * - cookieAuth: [] + * parameters: + * - name: page + * in: query + * required: false + * schema: + * type: integer + * default: 1 + * description: Page number + * - name: pageSize + * in: query + * required: false + * schema: + * type: integer + * default: 10 + * description: Number of items per page * responses: * 200: - * description: List of all audit logs + * description: Paginated list of audit logs * content: * application/json: * schema: - * type: array - * items: - * $ref: '#/components/schemas/AuditLog' + * type: object + * properties: + * data: + * type: array + * items: + * $ref: '#/components/schemas/AuditLog' + * metadata: + * type: object + * properties: + * currentPage: + * type: integer + * totalPages: + * type: integer + * totalRecords: + * type: integer + * nextPage: + * type: integer + * nullable: true + * prevPage: + * type: integer + * nullable: true * 401: * $ref: '#/components/responses/UnauthorizedError' */ - app.get("/api/audit-logs", requireAuth, async (req: Request, res: Response, next: NextFunction) => { + app.get("/api/audit-logs", requireAuth, async (req, res, next) => { try { - const logs = await storage.getAuditLogs(); - res.json(logs); + const page = req.query.page ? parseInt(req.query.page as string) : 1; + const pageSize = req.query.pageSize ? parseInt(req.query.pageSize as string) : 10; + + const result = await storage.getAuditLogs(undefined, undefined, page, pageSize); + res.json(result); } catch (error) { next(error); } @@ -3620,23 +3656,57 @@ export async function registerRoutes(app: Express): Promise { * required: true * schema: * type: integer + * - name: page + * in: query + * required: false + * schema: + * type: integer + * default: 1 + * description: Page number + * - name: pageSize + * in: query + * required: false + * schema: + * type: integer + * default: 10 + * description: Number of items per page * responses: * 200: - * description: List of audit logs for the specified connection + * description: Paginated list of audit logs for the specified connection * content: * application/json: * schema: - * type: array - * items: - * $ref: '#/components/schemas/AuditLog' + * type: object + * properties: + * data: + * type: array + * items: + * $ref: '#/components/schemas/AuditLog' + * metadata: + * type: object + * properties: + * currentPage: + * type: integer + * totalPages: + * type: integer + * totalRecords: + * type: integer + * nextPage: + * type: integer + * nullable: true + * prevPage: + * type: integer + * nullable: true * 401: * $ref: '#/components/responses/UnauthorizedError' * 404: * $ref: '#/components/responses/NotFoundError' */ - app.get("/api/connections/:connectionId/audit-logs", requireAuth, async (req: Request, res: Response, next: NextFunction) => { + app.get("/api/connections/:connectionId/audit-logs", requireAuth, async (req, res, next) => { try { const connectionId = parseInt(req.params.connectionId, 10); + const page = req.query.page ? parseInt(req.query.page as string) : 1; + const pageSize = req.query.pageSize ? parseInt(req.query.pageSize as string) : 10; // Verify the connection exists const connection = await storage.getLdapConnection(connectionId); @@ -3644,8 +3714,8 @@ export async function registerRoutes(app: Express): Promise { return res.status(404).json({ message: "Connection not found" }); } - const logs = await storage.getAuditLogs(connectionId); - res.json(logs); + const result = await storage.getAuditLogs(connectionId, undefined, page, pageSize); + res.json(result); } catch (error) { next(error); } diff --git a/server/storage.ts b/server/storage.ts index f727ef4..2123456 100644 --- a/server/storage.ts +++ b/server/storage.ts @@ -13,7 +13,7 @@ import session from "express-session"; import createMemoryStore from "memorystore"; import crypto from "crypto"; import { db } from "./db"; -import { eq, and, type SQL, desc } from "drizzle-orm"; +import { eq, and, type SQL, desc, sql } from "drizzle-orm"; import connectPg from "connect-pg-simple"; import { Pool } from "@neondatabase/serverless"; import { applyQueryOptions } from "./query-parser"; @@ -123,7 +123,7 @@ export interface IStorage { // Audit logging createAuditLogEntry(entry: InsertAuditLog): Promise; - getAuditLogs(connectionId?: number, userId?: number): Promise; + getAuditLogs(connectionId?: number, userId?: number, page?: number, pageSize?: number): Promise<{ data: AuditLog[], metadata: { currentPage: number, totalPages: number, totalRecords: number, nextPage: number | null, prevPage: number | null } }>; // Session store sessionStore: any; @@ -978,28 +978,59 @@ export class DatabaseStorage implements IStorage { return result[0]; } - async getAuditLogs(connectionId?: number, userId?: number): Promise { - debug(`Getting audit logs: connectionId=${connectionId}, userId=${userId}`); + async getAuditLogs(connectionId?: number, userId?: number, page: number = 1, pageSize: number = 10): Promise<{ data: AuditLog[], metadata: { currentPage: number, totalPages: number, totalRecords: number, nextPage: number | null, prevPage: number | null } }> { + debug(`Getting audit logs: connectionId=${connectionId}, userId=${userId}, page=${page}, pageSize=${pageSize}`); - // Define the base query - let query = db.select().from(auditLogs); + // Define the base query for counting total records + let countQuery = db.select({ count: sql`count(*)` }).from(auditLogs); + let dataQuery = db.select().from(auditLogs); // Apply filters if provided if (connectionId !== undefined && userId !== undefined) { - query = query.where( - and( - eq(auditLogs.connectionId, connectionId), - eq(auditLogs.userId, userId) - ) + const filter = and( + eq(auditLogs.connectionId, connectionId), + eq(auditLogs.userId, userId) ); + countQuery = countQuery.where(filter); + dataQuery = dataQuery.where(filter); } else if (connectionId !== undefined) { - query = query.where(eq(auditLogs.connectionId, connectionId)); + const filter = eq(auditLogs.connectionId, connectionId); + countQuery = countQuery.where(filter); + dataQuery = dataQuery.where(filter); } else if (userId !== undefined) { - query = query.where(eq(auditLogs.userId, userId)); + const filter = eq(auditLogs.userId, userId); + countQuery = countQuery.where(filter); + dataQuery = dataQuery.where(filter); } - // Sort by most recent first - return await query.orderBy(desc(auditLogs.timestamp)); + // Get total count of records + const countResult = await countQuery; + const totalRecords = parseInt(countResult[0].count.toString()); + + // Calculate pagination values + const totalPages = Math.ceil(totalRecords / pageSize); + const currentPage = Math.max(1, Math.min(page, totalPages)); // Ensure page is within bounds + const offset = (currentPage - 1) * pageSize; + + // Apply pagination and sorting to data query + dataQuery = dataQuery + .orderBy(desc(auditLogs.timestamp)) + .limit(pageSize) + .offset(offset); + + // Execute data query + const data = await dataQuery; + + // Create pagination metadata + const metadata = { + currentPage, + totalPages, + totalRecords, + nextPage: currentPage < totalPages ? currentPage + 1 : null, + prevPage: currentPage > 1 ? currentPage - 1 : null + }; + + return { data, metadata }; } }