feat: add audit logging to admin actions

Made-with: Cursor
This commit is contained in:
Anton
2026-03-04 15:06:22 +03:00
parent 7cfc8fb12e
commit 7bea8585c5
2 changed files with 32 additions and 7 deletions

View File

@@ -91,8 +91,9 @@ export async function adminQuestionsRoutes(app: FastifyInstance) {
preHandler: [app.authenticate, app.authenticateAdmin],
},
async (req, reply) => {
const adminId = req.user!.id;
const { questionId } = req.params as { questionId: string };
await adminQuestionService.approve(questionId);
await adminQuestionService.approve(questionId, adminId);
return reply.status(204).send();
},
);
@@ -105,8 +106,9 @@ export async function adminQuestionsRoutes(app: FastifyInstance) {
preHandler: [app.authenticate, app.authenticateAdmin],
},
async (req, reply) => {
const adminId = req.user!.id;
const { questionId } = req.params as { questionId: string };
await adminQuestionService.reject(questionId);
await adminQuestionService.reject(questionId, adminId);
return reply.status(204).send();
},
);
@@ -119,9 +121,10 @@ export async function adminQuestionsRoutes(app: FastifyInstance) {
preHandler: [app.authenticate, app.authenticateAdmin],
},
async (req, reply) => {
const adminId = req.user!.id;
const { questionId } = req.params as { questionId: string };
const body = req.body as EditQuestionInput;
const question = await adminQuestionService.edit(questionId, body);
const question = await adminQuestionService.edit(questionId, body, adminId);
return reply.send(question);
},
);

View File

@@ -1,7 +1,7 @@
import { eq, asc, count } from 'drizzle-orm';
import type { NodePgDatabase } from 'drizzle-orm/node-postgres';
import type * as schema from '../../db/schema/index.js';
import { questionBank } from '../../db/schema/index.js';
import { questionBank, auditLogs } from '../../db/schema/index.js';
import { notFound } from '../../utils/errors.js';
import type { Stack, Level, QuestionType } from '../../db/schema/enums.js';
@@ -80,7 +80,7 @@ export class AdminQuestionService {
};
}
async approve(questionId: string): Promise<void> {
async approve(questionId: string, adminId: string): Promise<void> {
const [updated] = await this.db
.update(questionBank)
.set({
@@ -93,9 +93,16 @@ export class AdminQuestionService {
if (!updated) {
throw notFound('Question not found');
}
await this.db.insert(auditLogs).values({
adminId,
action: 'question_approved',
targetType: 'question',
targetId: questionId,
});
}
async reject(questionId: string): Promise<void> {
async reject(questionId: string, adminId: string): Promise<void> {
const [updated] = await this.db
.update(questionBank)
.set({ status: 'rejected' })
@@ -105,9 +112,16 @@ export class AdminQuestionService {
if (!updated) {
throw notFound('Question not found');
}
await this.db.insert(auditLogs).values({
adminId,
action: 'question_rejected',
targetType: 'question',
targetId: questionId,
});
}
async edit(questionId: string, input: EditQuestionInput): Promise<PendingQuestion> {
async edit(questionId: string, input: EditQuestionInput, adminId: string): Promise<PendingQuestion> {
const updates: Partial<{
stack: Stack;
level: Level;
@@ -156,6 +170,14 @@ export class AdminQuestionService {
throw notFound('Question not found');
}
await this.db.insert(auditLogs).values({
adminId,
action: 'question_edited',
targetType: 'question',
targetId: questionId,
details: updates as Record<string, unknown>,
});
return {
id: updated.id,
stack: updated.stack,