alepha 0.13.6 → 0.13.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api-audits/index.browser.js +116 -0
- package/dist/api-audits/index.browser.js.map +1 -0
- package/dist/api-audits/index.d.ts +1194 -0
- package/dist/api-audits/index.js +674 -0
- package/dist/api-audits/index.js.map +1 -0
- package/dist/api-notifications/index.d.ts +147 -147
- package/dist/api-parameters/index.browser.js +36 -5
- package/dist/api-parameters/index.browser.js.map +1 -1
- package/dist/api-parameters/index.d.ts +711 -33
- package/dist/api-parameters/index.js +831 -17
- package/dist/api-parameters/index.js.map +1 -1
- package/dist/api-users/index.d.ts +793 -780
- package/dist/api-users/index.js +699 -19
- package/dist/api-users/index.js.map +1 -1
- package/dist/api-verifications/index.js +2 -1
- package/dist/api-verifications/index.js.map +1 -1
- package/dist/bin/index.js +1 -0
- package/dist/bin/index.js.map +1 -1
- package/dist/cli/index.d.ts +85 -31
- package/dist/cli/index.js +205 -33
- package/dist/cli/index.js.map +1 -1
- package/dist/command/index.d.ts +67 -6
- package/dist/command/index.js +30 -3
- package/dist/command/index.js.map +1 -1
- package/dist/core/index.browser.js +241 -61
- package/dist/core/index.browser.js.map +1 -1
- package/dist/core/index.d.ts +170 -90
- package/dist/core/index.js +264 -67
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.native.js +248 -65
- package/dist/core/index.native.js.map +1 -1
- package/dist/email/index.js +15 -10554
- package/dist/email/index.js.map +1 -1
- package/dist/logger/index.d.ts +4 -4
- package/dist/logger/index.js +77 -72
- package/dist/logger/index.js.map +1 -1
- package/dist/orm/index.d.ts +5 -1
- package/dist/orm/index.js +24 -7
- package/dist/orm/index.js.map +1 -1
- package/dist/queue/index.d.ts +4 -4
- package/dist/scheduler/index.d.ts +6 -6
- package/dist/server/index.d.ts +10 -1
- package/dist/server/index.js +20 -6
- package/dist/server/index.js.map +1 -1
- package/dist/server-auth/index.d.ts +163 -152
- package/dist/server-auth/index.js +40 -10
- package/dist/server-auth/index.js.map +1 -1
- package/dist/server-cookies/index.js +5 -1
- package/dist/server-cookies/index.js.map +1 -1
- package/dist/server-links/index.d.ts +33 -33
- package/dist/server-security/index.d.ts +9 -9
- package/dist/thread/index.js +2 -2
- package/dist/thread/index.js.map +1 -1
- package/dist/vite/index.d.ts +2 -2
- package/dist/vite/index.js +102 -45
- package/dist/vite/index.js.map +1 -1
- package/dist/websocket/index.browser.js +3 -3
- package/dist/websocket/index.browser.js.map +1 -1
- package/dist/websocket/index.js +4 -4
- package/dist/websocket/index.js.map +1 -1
- package/package.json +14 -9
- package/src/api-audits/controllers/AuditController.ts +186 -0
- package/src/api-audits/entities/audits.ts +132 -0
- package/src/api-audits/index.browser.ts +18 -0
- package/src/api-audits/index.ts +58 -0
- package/src/api-audits/primitives/$audit.ts +159 -0
- package/src/api-audits/schemas/auditQuerySchema.ts +23 -0
- package/src/api-audits/schemas/auditResourceSchema.ts +9 -0
- package/src/api-audits/schemas/createAuditSchema.ts +27 -0
- package/src/api-audits/services/AuditService.ts +412 -0
- package/src/api-parameters/controllers/ConfigController.ts +324 -0
- package/src/api-parameters/entities/parameters.ts +93 -10
- package/src/api-parameters/index.ts +43 -4
- package/src/api-parameters/primitives/$config.ts +291 -19
- package/src/api-parameters/schedulers/ConfigActivationScheduler.ts +30 -0
- package/src/api-parameters/services/ConfigStore.ts +491 -0
- package/src/api-users/atoms/realmAuthSettingsAtom.ts +19 -0
- package/src/api-users/controllers/UserRealmController.ts +0 -2
- package/src/api-users/index.ts +2 -0
- package/src/api-users/primitives/$userRealm.ts +18 -3
- package/src/api-users/providers/UserRealmProvider.ts +6 -3
- package/src/api-users/services/RegistrationService.ts +2 -1
- package/src/api-users/services/SessionService.ts +4 -0
- package/src/api-users/services/UserService.ts +3 -0
- package/src/api-verifications/index.ts +7 -1
- package/src/bin/index.ts +1 -0
- package/src/cli/assets/biomeJson.ts +1 -1
- package/src/cli/assets/dummySpecTs.ts +7 -0
- package/src/cli/assets/editorconfig.ts +13 -0
- package/src/cli/assets/mainTs.ts +14 -0
- package/src/cli/commands/BiomeCommands.ts +2 -0
- package/src/cli/commands/CoreCommands.ts +28 -9
- package/src/cli/commands/VerifyCommands.ts +2 -1
- package/src/cli/commands/ViteCommands.ts +8 -9
- package/src/cli/services/AlephaCliUtils.ts +214 -23
- package/src/command/helpers/Asker.ts +0 -1
- package/src/command/primitives/$command.ts +67 -0
- package/src/command/providers/CliProvider.ts +39 -8
- package/src/core/Alepha.ts +40 -30
- package/src/core/helpers/jsonSchemaToTypeBox.ts +307 -0
- package/src/core/index.shared.ts +1 -0
- package/src/core/index.ts +30 -3
- package/src/core/providers/EventManager.ts +1 -1
- package/src/core/providers/StateManager.ts +23 -12
- package/src/core/providers/TypeProvider.ts +26 -34
- package/src/logger/index.ts +8 -6
- package/src/logger/primitives/$logger.ts +1 -1
- package/src/logger/providers/{SimpleFormatterProvider.ts → PrettyFormatterProvider.ts} +10 -1
- package/src/orm/index.ts +6 -0
- package/src/orm/services/PgRelationManager.ts +2 -2
- package/src/orm/services/PostgresModelBuilder.ts +11 -7
- package/src/orm/services/Repository.ts +16 -7
- package/src/orm/services/SqliteModelBuilder.ts +10 -0
- package/src/server/index.ts +6 -0
- package/src/server/primitives/$action.ts +10 -1
- package/src/server/providers/ServerBodyParserProvider.ts +11 -5
- package/src/server/providers/ServerRouterProvider.ts +13 -7
- package/src/server-auth/primitives/$auth.ts +7 -0
- package/src/server-auth/providers/ServerAuthProvider.ts +51 -8
- package/src/server-cookies/index.ts +2 -1
- package/src/thread/primitives/$thread.ts +2 -2
- package/src/vite/index.ts +0 -2
- package/src/vite/tasks/buildServer.ts +3 -4
- package/src/vite/tasks/generateCloudflare.ts +35 -19
- package/src/vite/tasks/generateDocker.ts +18 -4
- package/src/vite/tasks/generateSitemap.ts +5 -7
- package/src/vite/tasks/generateVercel.ts +76 -41
- package/src/vite/tasks/runAlepha.ts +16 -1
- package/src/websocket/providers/NodeWebSocketServerProvider.ts +3 -11
- package/src/websocket/services/WebSocketClient.ts +3 -3
- package/dist/cli/dist-BlfFtOk2.js +0 -2770
- package/dist/cli/dist-BlfFtOk2.js.map +0 -1
- package/src/api-parameters/controllers/ParameterController.ts +0 -45
- package/src/api-parameters/services/ParameterStore.ts +0 -23
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
import { $inject, Alepha } from "alepha";
|
|
2
|
+
import { $logger } from "alepha/logger";
|
|
3
|
+
import { $repository, type Page } from "alepha/orm";
|
|
4
|
+
import type { ServerRequest } from "alepha/server";
|
|
5
|
+
import {
|
|
6
|
+
type AuditEntity,
|
|
7
|
+
type AuditSeverity,
|
|
8
|
+
audits,
|
|
9
|
+
} from "../entities/audits.ts";
|
|
10
|
+
import type { AuditQuery } from "../schemas/auditQuerySchema.ts";
|
|
11
|
+
import type { CreateAudit } from "../schemas/createAuditSchema.ts";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Registered audit type definition.
|
|
15
|
+
*/
|
|
16
|
+
export interface AuditTypeDefinition {
|
|
17
|
+
type: string;
|
|
18
|
+
description?: string;
|
|
19
|
+
actions: string[];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Service for managing audit logs.
|
|
24
|
+
*
|
|
25
|
+
* Provides methods for:
|
|
26
|
+
* - Creating audit entries
|
|
27
|
+
* - Querying audit history
|
|
28
|
+
* - Aggregating audit statistics
|
|
29
|
+
* - Managing registered audit types
|
|
30
|
+
*/
|
|
31
|
+
export class AuditService {
|
|
32
|
+
protected readonly alepha = $inject(Alepha);
|
|
33
|
+
protected readonly log = $logger();
|
|
34
|
+
protected readonly repo = $repository(audits);
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Registry of audit types and their allowed actions.
|
|
38
|
+
*/
|
|
39
|
+
protected readonly auditTypes = new Map<string, AuditTypeDefinition>();
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Register an audit type with its allowed actions.
|
|
43
|
+
*/
|
|
44
|
+
public registerType(definition: AuditTypeDefinition): void {
|
|
45
|
+
this.auditTypes.set(definition.type, definition);
|
|
46
|
+
this.log.debug("Audit type registered", {
|
|
47
|
+
type: definition.type,
|
|
48
|
+
actions: definition.actions,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Get all registered audit types.
|
|
54
|
+
*/
|
|
55
|
+
public getRegisteredTypes(): AuditTypeDefinition[] {
|
|
56
|
+
return Array.from(this.auditTypes.values());
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Get current request context if available.
|
|
61
|
+
*/
|
|
62
|
+
protected getRequestContext(): ServerRequest | undefined {
|
|
63
|
+
return this.alepha.context.get<ServerRequest>("request");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Create a new audit log entry.
|
|
68
|
+
* Automatically populates ipAddress, userAgent, and requestId from the current request context.
|
|
69
|
+
*/
|
|
70
|
+
public async create(data: CreateAudit): Promise<AuditEntity> {
|
|
71
|
+
const request = this.getRequestContext();
|
|
72
|
+
|
|
73
|
+
// Auto-populate from request context if not provided
|
|
74
|
+
const contextData: Partial<CreateAudit> = {};
|
|
75
|
+
|
|
76
|
+
if (request) {
|
|
77
|
+
if (!data.ipAddress && request.ip) {
|
|
78
|
+
contextData.ipAddress = request.ip;
|
|
79
|
+
}
|
|
80
|
+
if (!data.userAgent && request.headers["user-agent"]) {
|
|
81
|
+
contextData.userAgent = request.headers["user-agent"];
|
|
82
|
+
}
|
|
83
|
+
if (!data.requestId && request.requestId) {
|
|
84
|
+
contextData.requestId = request.requestId;
|
|
85
|
+
}
|
|
86
|
+
// Check for session in metadata
|
|
87
|
+
if (!data.sessionId && request.metadata?.sessionId) {
|
|
88
|
+
contextData.sessionId = request.metadata.sessionId;
|
|
89
|
+
}
|
|
90
|
+
// Extract user from request.user (set by ServerSecurityProvider)
|
|
91
|
+
const user = request.user;
|
|
92
|
+
if (user) {
|
|
93
|
+
if (!data.userId && user.id) {
|
|
94
|
+
contextData.userId = user.id;
|
|
95
|
+
}
|
|
96
|
+
if (!data.userEmail && user.email) {
|
|
97
|
+
contextData.userEmail = user.email;
|
|
98
|
+
}
|
|
99
|
+
if (!data.userRealm && user.realm) {
|
|
100
|
+
contextData.userRealm = user.realm;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
this.log.trace("Creating audit entry", {
|
|
106
|
+
type: data.type,
|
|
107
|
+
action: data.action,
|
|
108
|
+
userId: data.userId ?? contextData.userId,
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
const entry = await this.repo.create({
|
|
112
|
+
...contextData,
|
|
113
|
+
...data,
|
|
114
|
+
severity: data.severity ?? "info",
|
|
115
|
+
success: data.success ?? true,
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
this.log.debug("Audit entry created", {
|
|
119
|
+
id: entry.id,
|
|
120
|
+
type: data.type,
|
|
121
|
+
action: data.action,
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
return entry;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Record an audit event (convenience method).
|
|
129
|
+
*/
|
|
130
|
+
public async record(
|
|
131
|
+
type: string,
|
|
132
|
+
action: string,
|
|
133
|
+
options: Omit<CreateAudit, "type" | "action"> = {},
|
|
134
|
+
): Promise<AuditEntity> {
|
|
135
|
+
return this.create({ type, action, ...options });
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Record an authentication event.
|
|
140
|
+
*/
|
|
141
|
+
public async recordAuth(
|
|
142
|
+
action:
|
|
143
|
+
| "login"
|
|
144
|
+
| "logout"
|
|
145
|
+
| "login_failed"
|
|
146
|
+
| "token_refresh"
|
|
147
|
+
| "mfa_setup"
|
|
148
|
+
| "mfa_verify",
|
|
149
|
+
options: Omit<CreateAudit, "type" | "action"> = {},
|
|
150
|
+
): Promise<AuditEntity> {
|
|
151
|
+
return this.create({
|
|
152
|
+
type: "auth",
|
|
153
|
+
action,
|
|
154
|
+
severity: action === "login_failed" ? "warning" : "info",
|
|
155
|
+
...options,
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Record a user management event.
|
|
161
|
+
*/
|
|
162
|
+
public async recordUser(
|
|
163
|
+
action:
|
|
164
|
+
| "create"
|
|
165
|
+
| "update"
|
|
166
|
+
| "delete"
|
|
167
|
+
| "enable"
|
|
168
|
+
| "disable"
|
|
169
|
+
| "role_change",
|
|
170
|
+
options: Omit<CreateAudit, "type" | "action"> = {},
|
|
171
|
+
): Promise<AuditEntity> {
|
|
172
|
+
return this.create({
|
|
173
|
+
type: "user",
|
|
174
|
+
action,
|
|
175
|
+
resourceType: "user",
|
|
176
|
+
...options,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Record a data access event.
|
|
182
|
+
*/
|
|
183
|
+
public async recordAccess(
|
|
184
|
+
action: "view" | "export" | "download",
|
|
185
|
+
options: Omit<CreateAudit, "type" | "action"> = {},
|
|
186
|
+
): Promise<AuditEntity> {
|
|
187
|
+
return this.create({ type: "access", action, ...options });
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Record a security event.
|
|
192
|
+
*/
|
|
193
|
+
public async recordSecurity(
|
|
194
|
+
action:
|
|
195
|
+
| "permission_denied"
|
|
196
|
+
| "suspicious_activity"
|
|
197
|
+
| "rate_limited"
|
|
198
|
+
| "blocked",
|
|
199
|
+
options: Omit<CreateAudit, "type" | "action"> = {},
|
|
200
|
+
): Promise<AuditEntity> {
|
|
201
|
+
return this.create({
|
|
202
|
+
type: "security",
|
|
203
|
+
action,
|
|
204
|
+
severity: "warning",
|
|
205
|
+
...options,
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Record a system event.
|
|
211
|
+
*/
|
|
212
|
+
public async recordSystem(
|
|
213
|
+
action: "startup" | "shutdown" | "config_change" | "maintenance" | "error",
|
|
214
|
+
options: Omit<CreateAudit, "type" | "action"> = {},
|
|
215
|
+
): Promise<AuditEntity> {
|
|
216
|
+
return this.create({
|
|
217
|
+
type: "system",
|
|
218
|
+
action,
|
|
219
|
+
severity: action === "error" ? "critical" : "info",
|
|
220
|
+
...options,
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Find audit entries with filtering and pagination.
|
|
226
|
+
*/
|
|
227
|
+
public async find(query: AuditQuery = {}): Promise<Page<AuditEntity>> {
|
|
228
|
+
this.log.trace("Finding audit entries", { query });
|
|
229
|
+
|
|
230
|
+
query.sort ??= "-createdAt";
|
|
231
|
+
|
|
232
|
+
const where = this.repo.createQueryWhere();
|
|
233
|
+
|
|
234
|
+
if (query.type) {
|
|
235
|
+
where.type = { eq: query.type };
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (query.action) {
|
|
239
|
+
where.action = { eq: query.action };
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (query.severity) {
|
|
243
|
+
where.severity = { eq: query.severity };
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (query.userId) {
|
|
247
|
+
where.userId = { eq: query.userId };
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (query.userRealm) {
|
|
251
|
+
where.userRealm = { eq: query.userRealm };
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (query.resourceType) {
|
|
255
|
+
where.resourceType = { eq: query.resourceType };
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (query.resourceId) {
|
|
259
|
+
where.resourceId = { eq: query.resourceId };
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (query.success !== undefined) {
|
|
263
|
+
where.success = { eq: query.success };
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (query.from) {
|
|
267
|
+
where.createdAt = { ...(where.createdAt as object), gte: query.from };
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (query.to) {
|
|
271
|
+
where.createdAt = { ...(where.createdAt as object), lte: query.to };
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (query.search) {
|
|
275
|
+
where.description = { like: `%${query.search}%` };
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const result = await this.repo.paginate(query, { where }, { count: true });
|
|
279
|
+
|
|
280
|
+
this.log.debug("Audit entries found", {
|
|
281
|
+
count: result.content.length,
|
|
282
|
+
total: result.page.totalElements,
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
return result;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Get audit entry by ID.
|
|
290
|
+
*/
|
|
291
|
+
public async getById(id: string): Promise<AuditEntity> {
|
|
292
|
+
return this.repo.findById(id);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Get audit entries for a specific user.
|
|
297
|
+
*/
|
|
298
|
+
public async findByUser(
|
|
299
|
+
userId: string,
|
|
300
|
+
query: Omit<AuditQuery, "userId"> = {},
|
|
301
|
+
): Promise<Page<AuditEntity>> {
|
|
302
|
+
return this.find({ ...query, userId });
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Get audit entries for a specific resource.
|
|
307
|
+
*/
|
|
308
|
+
public async findByResource(
|
|
309
|
+
resourceType: string,
|
|
310
|
+
resourceId: string,
|
|
311
|
+
query: Omit<AuditQuery, "resourceType" | "resourceId"> = {},
|
|
312
|
+
): Promise<Page<AuditEntity>> {
|
|
313
|
+
return this.find({ ...query, resourceType, resourceId });
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Get audit statistics for a time period.
|
|
318
|
+
*/
|
|
319
|
+
public async getStats(
|
|
320
|
+
options: { from?: Date; to?: Date; userRealm?: string } = {},
|
|
321
|
+
): Promise<AuditStats> {
|
|
322
|
+
this.log.trace("Getting audit stats", options);
|
|
323
|
+
|
|
324
|
+
const where = this.repo.createQueryWhere();
|
|
325
|
+
|
|
326
|
+
if (options.from) {
|
|
327
|
+
where.createdAt = { gte: options.from.toISOString() };
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
if (options.to) {
|
|
331
|
+
where.createdAt = {
|
|
332
|
+
...(where.createdAt as object),
|
|
333
|
+
lte: options.to.toISOString(),
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if (options.userRealm) {
|
|
338
|
+
where.userRealm = { eq: options.userRealm };
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const all = await this.repo.findMany({ where });
|
|
342
|
+
|
|
343
|
+
const stats: AuditStats = {
|
|
344
|
+
total: all.length,
|
|
345
|
+
byType: {},
|
|
346
|
+
bySeverity: { info: 0, warning: 0, critical: 0 },
|
|
347
|
+
successRate: 0,
|
|
348
|
+
recentFailures: [],
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
let successCount = 0;
|
|
352
|
+
|
|
353
|
+
for (const entry of all) {
|
|
354
|
+
// Count by type
|
|
355
|
+
stats.byType[entry.type] = (stats.byType[entry.type] || 0) + 1;
|
|
356
|
+
|
|
357
|
+
// Count by severity
|
|
358
|
+
const severity = entry.severity as AuditSeverity;
|
|
359
|
+
stats.bySeverity[severity]++;
|
|
360
|
+
|
|
361
|
+
// Count successes
|
|
362
|
+
if (entry.success) {
|
|
363
|
+
successCount++;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
stats.successRate = stats.total > 0 ? successCount / stats.total : 1;
|
|
368
|
+
|
|
369
|
+
// Get recent failures
|
|
370
|
+
const failures = all
|
|
371
|
+
.filter((e) => !e.success)
|
|
372
|
+
.sort(
|
|
373
|
+
(a, b) =>
|
|
374
|
+
new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(),
|
|
375
|
+
)
|
|
376
|
+
.slice(0, 10);
|
|
377
|
+
|
|
378
|
+
stats.recentFailures = failures;
|
|
379
|
+
|
|
380
|
+
return stats;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Delete old audit entries (for retention policy).
|
|
385
|
+
*/
|
|
386
|
+
public async deleteOlderThan(date: Date): Promise<number> {
|
|
387
|
+
this.log.info("Deleting old audit entries", { olderThan: date });
|
|
388
|
+
|
|
389
|
+
const old = await this.repo.findMany({
|
|
390
|
+
where: { createdAt: { lt: date.toISOString() } },
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
for (const entry of old) {
|
|
394
|
+
await this.repo.deleteById(entry.id);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
this.log.info("Old audit entries deleted", { count: old.length });
|
|
398
|
+
|
|
399
|
+
return old.length;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Audit statistics summary.
|
|
405
|
+
*/
|
|
406
|
+
export interface AuditStats {
|
|
407
|
+
total: number;
|
|
408
|
+
byType: Record<string, number>;
|
|
409
|
+
bySeverity: Record<AuditSeverity, number>;
|
|
410
|
+
successRate: number;
|
|
411
|
+
recentFailures: AuditEntity[];
|
|
412
|
+
}
|