Skip to content

Commit 95fecb4

Browse files
committed
refactor: address PR feedback for HTTP helpers
- Fix account not found to return 404 instead of 400 - Update getAccountWithRelations to return null instead of throwing - Add AccountStatus enum to replace magic strings - Add Zod validation for audit logs query parameters - Improve type safety across account and audit log handlers
1 parent 3c91d45 commit 95fecb4

File tree

5 files changed

+42
-22
lines changed

5 files changed

+42
-22
lines changed

src/handlers/accounts/accounts.handlers.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export const getAccount = asyncHandler(async (req: Request, res: Response): Prom
3838
const result = await getAccountWithRelations(req.params.id);
3939

4040
if (!result) {
41-
const response = apiResponse.error(HttpErrors.ValidationFailed("Account not found"));
41+
const response = apiResponse.error(HttpErrors.NotFound("Account"));
4242
res.status(response.code).send(response);
4343
return;
4444
}

src/handlers/accounts/accounts.methods.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ export async function getAccountById(accountId: string): Promise<AccountSelectTy
6363
return result;
6464
}
6565

66-
export async function getAccountWithRelations(accountId: string): Promise<AccountWithRelations> {
66+
export async function getAccountWithRelations(accountId: string): Promise<AccountWithRelations | null> {
6767
const validationResult = uuidSchema.safeParse({ uuid: accountId });
6868
if (!validationResult.success) {
6969
throw new Error(`Invalid account ID: ${validationResult.error.message}`);
@@ -85,9 +85,5 @@ export async function getAccountWithRelations(accountId: string): Promise<Accoun
8585
}
8686
});
8787

88-
if (!result) {
89-
throw new Error("Account not found");
90-
}
91-
92-
return result;
88+
return result || null;
9389
}

src/handlers/admin/auditLogs.handlers.ts

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,43 @@
1-
import { HttpStatusCode } from "@/helpers/Http.ts";
1+
import { HttpErrors, HttpStatusCode } from "@/helpers/Http.ts";
22
import { apiResponse } from "@/helpers/response.ts";
33
import { asyncHandler } from "@/helpers/request.ts";
44
import { accounts, auditLogs } from "@/schema.ts";
55
import { db } from "@/services/db/drizzle.ts";
66
import { and, desc, eq, gte, lte, sql } from "drizzle-orm";
77
import type { Request, Response } from "express";
8+
import { z } from "zod";
9+
10+
// Zod schema for audit logs query parameters
11+
const auditLogsQuerySchema = z.object({
12+
page: z.coerce.number().int().positive().default(1),
13+
limit: z.coerce.number().int().positive().max(100).default(50),
14+
action: z.string().optional(),
15+
entityType: z.string().optional(),
16+
actorId: z.string().uuid().optional(),
17+
entityId: z.string().uuid().optional(),
18+
workspaceId: z.string().uuid().optional(),
19+
startDate: z.string().datetime().optional(),
20+
endDate: z.string().datetime().optional()
21+
});
822

923
/**
1024
* GET /admin/audit-logs
1125
* List audit logs with filtering and pagination (SuperAdmin only)
1226
*/
1327
export const getAuditLogs = asyncHandler(async (req: Request, res: Response): Promise<void> => {
14-
const page = parseInt(req.query.page as string) || 1;
15-
const limit = parseInt(req.query.limit as string) || 50;
16-
const offset = (page - 1) * limit;
28+
// Validate query parameters
29+
const validationResult = auditLogsQuerySchema.safeParse(req.query);
30+
31+
if (!validationResult.success) {
32+
const response = apiResponse.error(
33+
HttpErrors.ValidationFailed(`Invalid query parameters: ${validationResult.error.message}`)
34+
);
35+
res.status(response.code).send(response);
36+
return;
37+
}
1738

18-
// Filter parameters
19-
const action = req.query.action as string;
20-
const entityType = req.query.entityType as string;
21-
const actorId = req.query.actorId as string;
22-
const entityId = req.query.entityId as string;
23-
const workspaceId = req.query.workspaceId as string;
24-
const startDate = req.query.startDate as string;
25-
const endDate = req.query.endDate as string;
39+
const { page, limit, action, entityType, actorId, entityId, workspaceId, startDate, endDate } = validationResult.data;
40+
const offset = (page - 1) * limit;
2641

2742
// Build conditions for filtering
2843
const conditions = [];

src/middleware/checkAccountStatus.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { logger } from "@/helpers/index.ts";
2-
import { accounts } from "@/schema.ts";
2+
import { AccountStatus, accounts } from "@/schema.ts";
33
import { db } from "@/services/db/drizzle.ts";
44
import { eq } from "drizzle-orm";
55
import type { NextFunction, Request, Response } from "express";
@@ -33,8 +33,8 @@ export const checkAccountStatus = async (req: Request, res: Response, next: Next
3333
res.status(response.code).send(response);
3434
return;
3535
}
36-
// TODO enum for account status
37-
if (account.status !== "active") {
36+
37+
if (account.status !== AccountStatus.ACTIVE) {
3838
logger.warn({ msg: `Access denied for ${account.status} account: ${accountId}` });
3939
const response = apiResponse.error(HttpErrors.Forbidden(`Account is ${account.status}`));
4040
res.status(response.code).send(response);

src/schema.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,15 @@ export const uuidSchema = z.uuid();
1010
// Validate UUID in object form (for backward compatibility)
1111
export const uuidObjectSchema = z.object({ uuid: uuidSchema });
1212

13+
// Account status enum
14+
export const AccountStatus = {
15+
ACTIVE: "active",
16+
INACTIVE: "inactive",
17+
SUSPENDED: "suspended"
18+
} as const;
19+
20+
export type AccountStatusType = (typeof AccountStatus)[keyof typeof AccountStatus];
21+
1322
export const accounts = pgTable("accounts", {
1423
uuid: uuid("uuid").defaultRandom().primaryKey(),
1524
fullName: text("full_name").notNull(),

0 commit comments

Comments
 (0)