alepha
Version:
Easy-to-use modern TypeScript framework for building many kind of applications.
239 lines (227 loc) • 7.06 kB
text/typescript
import { $inject, z } from "alepha";
import { $repository } from "alepha/orm";
import { $secure } from "alepha/security";
import { $action } from "alepha/server";
import { audits } from "../entities/audits.ts";
import { auditQuerySchema } from "../schemas/auditQuerySchema.ts";
import { auditResourceSchema } from "../schemas/auditResourceSchema.ts";
import { createAuditSchema } from "../schemas/createAuditSchema.ts";
import { AuditService } from "../services/AuditService.ts";
/**
* REST API controller for audit log management.
*
* Provides endpoints for:
* - Querying audit logs with filtering
* - Creating audit entries
* - Getting audit statistics
* - Viewing registered audit types
*/
export class AdminAuditController {
protected readonly url = "/audits";
protected readonly group = "admin:audits";
protected readonly auditService = $inject(AuditService);
protected readonly repo = $repository(audits);
/**
* Find audit entries with filtering and pagination.
*/
public readonly findAudits = $action({
path: this.url,
group: this.group,
use: [$secure({ permissions: ["admin:audit:read"] })],
description: "Find audit entries with filtering and pagination",
schema: {
query: auditQuerySchema,
response: z.page(auditResourceSchema),
},
handler: ({ query }) => this.auditService.find(query),
});
/**
* Get a single audit entry by ID.
*/
public readonly getAudit = $action({
path: `${this.url}/:id`,
group: this.group,
use: [$secure({ permissions: ["admin:audit:read"] })],
description: "Get a single audit entry by ID",
schema: {
params: z.object({
id: z.text(),
}),
response: auditResourceSchema,
},
handler: ({ params }) => this.auditService.getById(params.id),
});
/**
* Delete many audit entries by id in one repository call. Use with care —
* audit logs are usually retained for compliance reasons.
*/
public readonly deleteAudits = $action({
method: "POST",
path: `${this.url}/delete`,
group: this.group,
use: [$secure({ permissions: ["admin:audit:delete"] })],
description: "Delete many audit entries",
schema: {
body: z.object({
ids: z.array(z.text()).min(1).max(1000),
}),
response: z.object({
deleted: z.array(z.text()),
}),
},
handler: async ({ body }) => {
const deleted = await this.repo.deleteMany({
id: { inArray: body.ids },
});
return { deleted: deleted.map(String) };
},
});
/**
* Create a new audit entry.
* System-only — this permission should never be assigned to human roles.
*/
public readonly createAudit = $action({
method: "POST",
path: this.url,
group: this.group,
use: [$secure({ permissions: ["admin:audit:create"] })],
description: "Create a new audit entry",
schema: {
body: createAuditSchema,
response: auditResourceSchema,
},
handler: ({ body }) => this.auditService.create(body),
});
/**
* Get audit entries for a specific user.
*/
public readonly findByUser = $action({
path: `${this.url}/user/:userId`,
group: this.group,
use: [$secure({ permissions: ["admin:audit:read"] })],
description: "Get audit entries for a specific user",
schema: {
params: z.object({
userId: z.uuid(),
}),
query: auditQuerySchema.omit({ userId: true }),
response: z.page(auditResourceSchema),
},
handler: ({ params, query }) =>
this.auditService.findByUser(params.userId, query),
});
/**
* Get audit entries for a specific resource.
*/
public readonly findByResource = $action({
path: `${this.url}/resource/:resourceType/:resourceId`,
group: this.group,
use: [$secure({ permissions: ["admin:audit:read"] })],
description: "Get audit entries for a specific resource",
schema: {
params: z.object({
resourceType: z.text(),
resourceId: z.text(),
}),
query: auditQuerySchema.omit({ resourceType: true, resourceId: true }),
response: z.page(auditResourceSchema),
},
handler: ({ params, query }) =>
this.auditService.findByResource(
params.resourceType,
params.resourceId,
query,
),
});
/**
* Get audit statistics.
*/
public readonly getAuditStats = $action({
path: `${this.url}/stats`,
group: this.group,
use: [$secure({ permissions: ["admin:audit:read"] })],
description: "Get audit statistics for a time period",
schema: {
query: z.object({
from: z.datetime().optional(),
to: z.datetime().optional(),
userRealm: z.text().optional(),
}),
response: z.object({
total: z.integer(),
byType: z.record(z.text(), z.integer()),
bySeverity: z.object({
info: z.integer(),
warning: z.integer(),
critical: z.integer(),
}),
successRate: z.number(),
recentFailures: z.array(auditResourceSchema),
}),
},
handler: ({ query }) =>
this.auditService.getStats({
from: query.from ? new Date(query.from) : undefined,
to: query.to ? new Date(query.to) : undefined,
userRealm: query.userRealm,
}),
});
/**
* List distinct action names present in the audit log (for filters).
*/
public readonly getAuditActions = $action({
path: `${this.url}/actions`,
group: this.group,
use: [$secure({ permissions: ["admin:audit:read"] })],
description: "List distinct action names present in the audit log",
schema: {
response: z.array(z.text()),
},
handler: () => this.auditService.getDistinctActions(),
});
/**
* Get registered audit types.
*/
public readonly getTypes = $action({
path: `${this.url}/types`,
group: this.group,
use: [$secure({ permissions: ["admin:audit:read"] })],
description: "Get all registered audit types",
schema: {
response: z.array(
z.object({
type: z.text(),
description: z.text().optional(),
actions: z.array(z.text()),
}),
),
},
handler: () => this.auditService.getRegisteredTypes(),
});
/**
* Get distinct values for filters.
*/
public readonly getFilterOptions = $action({
path: `${this.url}/filters`,
group: this.group,
use: [$secure({ permissions: ["admin:audit:read"] })],
description: "Get distinct values for audit filters",
schema: {
response: z.object({
types: z.array(z.text()),
actions: z.array(z.text()),
resourceTypes: z.array(z.text()),
userRealms: z.array(z.text()),
}),
},
handler: async () => {
const types = this.auditService.getRegisteredTypes();
return {
types: types.map((t) => t.type),
actions: types.flatMap((t) => t.actions),
resourceTypes: ["user", "session", "file", "order", "payment"],
userRealms: ["default"],
};
},
});
}