Files
SnapOtter/apps/api/src/routes/audit-log.ts
T

87 lines
2.6 KiB
TypeScript

import { and, desc, eq, gte, lte, sql } from "drizzle-orm";
import type { FastifyInstance, FastifyReply, FastifyRequest } from "fastify";
import { db, schema } from "../db/index.js";
import { requirePermission } from "../permissions.js";
export async function auditLogRoutes(app: FastifyInstance): Promise<void> {
app.get(
"/api/v1/audit-log",
async (
request: FastifyRequest<{
Querystring: {
page?: string;
limit?: string;
action?: string;
ip?: string;
from?: string;
to?: string;
};
}>,
reply: FastifyReply,
) => {
const user = await requirePermission("audit:read")(request, reply);
if (!user) return;
const page = Math.max(1, parseInt(request.query.page ?? "1", 10) || 1);
const limit = Math.min(100, Math.max(1, parseInt(request.query.limit ?? "50", 10) || 50));
const offset = (page - 1) * limit;
const conditions = [];
if (request.query.action) {
conditions.push(eq(schema.auditLog.action, request.query.action));
}
if (request.query.ip) {
conditions.push(eq(schema.auditLog.ipAddress, request.query.ip));
}
if (request.query.from) {
const fromDate = new Date(request.query.from);
if (!Number.isNaN(fromDate.getTime())) {
conditions.push(gte(schema.auditLog.createdAt, fromDate));
}
}
if (request.query.to) {
const toDate = new Date(request.query.to);
if (!Number.isNaN(toDate.getTime())) {
conditions.push(lte(schema.auditLog.createdAt, toDate));
}
}
const where = conditions.length > 0 ? and(...conditions) : undefined;
const entries = await db
.select()
.from(schema.auditLog)
.where(where)
.orderBy(desc(schema.auditLog.createdAt))
.limit(limit)
.offset(offset);
const [countResult] = await db
.select({ count: sql<number>`count(*)::int` })
.from(schema.auditLog)
.where(where);
return reply.send({
entries: entries.map((e) => ({
id: e.id,
actorId: e.actorId,
actorUsername: e.actorUsername,
action: e.action,
targetType: e.targetType,
targetId: e.targetId,
details: e.details ?? null,
ipAddress: e.ipAddress,
requestId: e.requestId ?? null,
createdAt: e.createdAt.toISOString(),
})),
total: countResult?.count ?? 0,
page,
limit,
});
},
);
app.log.info("Audit log routes registered");
}