@goplusvn/core 0.1.53 → 0.1.55
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/features/README.md +11 -1
- package/features/audit-logs/README.md +18 -0
- package/features/audit-logs/migrations/0001_init.sql +33 -0
- package/features/audit-logs/schema.prisma +20 -0
- package/features/system-jobs/README.md +40 -0
- package/features/system-jobs/migrations/0001_init.sql +47 -0
- package/features/system-jobs/schema.prisma +42 -0
- package/package.json +5 -1
- package/src/audit/__tests__/audit-context.test.ts +174 -0
- package/src/audit/__tests__/prisma-audit-extension.test.ts +426 -0
- package/src/audit/audit-actor.ts +42 -0
- package/src/audit/audit-context.ts +47 -0
- package/src/audit/entity-audit.ts +71 -0
- package/src/audit/index.ts +29 -10
- package/src/audit/prisma-audit-extension.ts +572 -0
- package/src/cron/__tests__/db-cron-manager.test.ts +316 -0
- package/src/cron/db-cron-manager.ts +459 -0
- package/src/cron/index.ts +24 -0
- package/src/system/pages/__tests__/system-audit-page.test.tsx +140 -0
- package/src/system/pages/__tests__/system-jobs-page.test.tsx +92 -0
- package/src/system/pages/system-audit-page.tsx +532 -0
- package/src/system/pages/system-jobs-page.tsx +571 -0
- package/src/ui/management/audit-log-page.tsx +12 -207
- package/src/audit/audit-manager.ts +0 -139
- package/src/audit/memory-audit-logger.ts +0 -86
- package/src/audit/types.ts +0 -50
|
@@ -1,209 +1,14 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
import
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
TableBody,
|
|
16
|
-
TableCell,
|
|
17
|
-
TableHead,
|
|
18
|
-
TableHeader,
|
|
19
|
-
TableRow,
|
|
20
|
-
} from "../primitives/table";
|
|
21
|
-
import {
|
|
22
|
-
Select,
|
|
23
|
-
SelectContent,
|
|
24
|
-
SelectItem,
|
|
25
|
-
SelectTrigger,
|
|
26
|
-
SelectValue,
|
|
27
|
-
} from "../primitives/select";
|
|
28
|
-
|
|
29
|
-
export const AuditLogPage = () => {
|
|
30
|
-
const [logs, setLogs] = useState<AuditLog[]>([]);
|
|
31
|
-
const [userIdFilter, setUserIdFilter] = useState("");
|
|
32
|
-
const [actionFilter, setActionFilter] = useState("");
|
|
33
|
-
const [resourceFilter, setResourceFilter] = useState("");
|
|
34
|
-
const [typeFilter, setTypeFilter] = useState<string>("all");
|
|
35
|
-
|
|
36
|
-
const fetchLogs = () => {
|
|
37
|
-
// Safe cast as we know we added getLogs
|
|
38
|
-
// In real app, we would have proper interface
|
|
39
|
-
const manager = auditManager as any;
|
|
40
|
-
if (typeof manager.getLogs === "function") {
|
|
41
|
-
const filter: any = {};
|
|
42
|
-
if (userIdFilter) filter.userId = userIdFilter;
|
|
43
|
-
if (actionFilter) filter.action = actionFilter;
|
|
44
|
-
if (resourceFilter) filter.resource = resourceFilter;
|
|
45
|
-
if (typeFilter && typeFilter !== "all") filter.type = typeFilter;
|
|
46
|
-
|
|
47
|
-
setLogs(manager.getLogs(filter));
|
|
48
|
-
}
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
useEffect(() => {
|
|
52
|
-
fetchLogs();
|
|
53
|
-
// Poll for updates in demo
|
|
54
|
-
const interval = setInterval(fetchLogs, 5000);
|
|
55
|
-
return () => clearInterval(interval);
|
|
56
|
-
}, [userIdFilter, actionFilter, resourceFilter, typeFilter]);
|
|
57
|
-
|
|
58
|
-
// Format changes object for display
|
|
59
|
-
const renderChanges = (changes: any) => {
|
|
60
|
-
if (!changes) return <span className="text-muted-foreground">-</span>;
|
|
61
|
-
return (
|
|
62
|
-
<code
|
|
63
|
-
className="text-xs bg-slate-100 dark:bg-slate-800 p-1 rounded block max-w-[300px] overflow-hidden text-ellipsis whitespace-nowrap"
|
|
64
|
-
title={JSON.stringify(changes, null, 2)}
|
|
65
|
-
>
|
|
66
|
-
{Object.keys(changes).join(", ")}
|
|
67
|
-
</code>
|
|
68
|
-
);
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
return (
|
|
72
|
-
<div className="p-6 space-y-6 h-full flex flex-col">
|
|
73
|
-
<div className="flex justify-between items-center mb-4">
|
|
74
|
-
<div>
|
|
75
|
-
<h1 className="text-2xl font-bold tracking-tight">Audit Logs</h1>
|
|
76
|
-
<p className="text-muted-foreground text-sm">
|
|
77
|
-
Track system changes and user actions.
|
|
78
|
-
</p>
|
|
79
|
-
</div>
|
|
80
|
-
<Button onClick={fetchLogs} variant="outline">
|
|
81
|
-
Refresh
|
|
82
|
-
</Button>
|
|
83
|
-
</div>
|
|
84
|
-
|
|
85
|
-
<Card>
|
|
86
|
-
<CardHeader>
|
|
87
|
-
<CardTitle className="text-base font-medium">Filters</CardTitle>
|
|
88
|
-
</CardHeader>
|
|
89
|
-
<CardContent>
|
|
90
|
-
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 gap-4">
|
|
91
|
-
<div className="space-y-1">
|
|
92
|
-
<Label htmlFor="user-filter">User ID</Label>
|
|
93
|
-
<Input
|
|
94
|
-
id="user-filter"
|
|
95
|
-
placeholder="Search by user..."
|
|
96
|
-
value={userIdFilter}
|
|
97
|
-
onChange={(e) => setUserIdFilter(e.target.value)}
|
|
98
|
-
/>
|
|
99
|
-
</div>
|
|
100
|
-
<div className="space-y-1">
|
|
101
|
-
<Label htmlFor="action-filter">Action</Label>
|
|
102
|
-
<Input
|
|
103
|
-
id="action-filter"
|
|
104
|
-
placeholder="e.g. create, update"
|
|
105
|
-
value={actionFilter}
|
|
106
|
-
onChange={(e) => setActionFilter(e.target.value)}
|
|
107
|
-
/>
|
|
108
|
-
</div>
|
|
109
|
-
<div className="space-y-1">
|
|
110
|
-
<Label htmlFor="resource-filter">Resource</Label>
|
|
111
|
-
<Input
|
|
112
|
-
id="resource-filter"
|
|
113
|
-
placeholder="e.g. product, order"
|
|
114
|
-
value={resourceFilter}
|
|
115
|
-
onChange={(e) => setResourceFilter(e.target.value)}
|
|
116
|
-
/>
|
|
117
|
-
</div>
|
|
118
|
-
<div className="space-y-1">
|
|
119
|
-
<Label htmlFor="type-filter">Type</Label>
|
|
120
|
-
<Select value={typeFilter} onValueChange={setTypeFilter}>
|
|
121
|
-
<SelectTrigger>
|
|
122
|
-
<SelectValue placeholder="All Types" />
|
|
123
|
-
</SelectTrigger>
|
|
124
|
-
<SelectContent>
|
|
125
|
-
<SelectItem value="all">All Types</SelectItem>
|
|
126
|
-
<SelectItem value="info">Info</SelectItem>
|
|
127
|
-
<SelectItem value="warning">Warning</SelectItem>
|
|
128
|
-
<SelectItem value="error">Error</SelectItem>
|
|
129
|
-
</SelectContent>
|
|
130
|
-
</Select>
|
|
131
|
-
</div>
|
|
132
|
-
</div>
|
|
133
|
-
</CardContent>
|
|
134
|
-
</Card>
|
|
135
|
-
|
|
136
|
-
<Card className="flex-1 flex flex-col min-h-0">
|
|
137
|
-
<CardContent className="flex-1 overflow-auto p-0">
|
|
138
|
-
<Table>
|
|
139
|
-
<TableHeader>
|
|
140
|
-
<TableRow>
|
|
141
|
-
<TableHead className="w-[180px]">Timestamp</TableHead>
|
|
142
|
-
<TableHead className="w-[150px]">Action</TableHead>
|
|
143
|
-
<TableHead className="w-[150px]">Resource</TableHead>
|
|
144
|
-
<TableHead className="w-[150px]">User</TableHead>
|
|
145
|
-
<TableHead>Changes / Metadata</TableHead>
|
|
146
|
-
<TableHead className="w-[100px] text-right">Status</TableHead>
|
|
147
|
-
</TableRow>
|
|
148
|
-
</TableHeader>
|
|
149
|
-
<TableBody>
|
|
150
|
-
{logs.length === 0 && (
|
|
151
|
-
<TableRow>
|
|
152
|
-
<TableCell
|
|
153
|
-
colSpan={6}
|
|
154
|
-
className="text-center h-24 text-muted-foreground"
|
|
155
|
-
>
|
|
156
|
-
No logs found matching filters.
|
|
157
|
-
</TableCell>
|
|
158
|
-
</TableRow>
|
|
159
|
-
)}
|
|
160
|
-
{logs.map((log) => (
|
|
161
|
-
<TableRow key={log.id}>
|
|
162
|
-
<TableCell className="font-mono text-xs">
|
|
163
|
-
{new Date(log.createdAt).toLocaleString()}
|
|
164
|
-
</TableCell>
|
|
165
|
-
<TableCell>
|
|
166
|
-
<Badge variant="outline" className="font-normal">
|
|
167
|
-
{log.action}
|
|
168
|
-
</Badge>
|
|
169
|
-
</TableCell>
|
|
170
|
-
<TableCell>{log.resource}</TableCell>
|
|
171
|
-
<TableCell>
|
|
172
|
-
<div className="flex flex-col">
|
|
173
|
-
<span>{log.userId || "System"}</span>
|
|
174
|
-
{log.roleName && (
|
|
175
|
-
<span className="text-[10px] text-muted-foreground">
|
|
176
|
-
{log.roleName}
|
|
177
|
-
</span>
|
|
178
|
-
)}
|
|
179
|
-
</div>
|
|
180
|
-
</TableCell>
|
|
181
|
-
<TableCell>
|
|
182
|
-
{renderChanges(log.changes)}
|
|
183
|
-
{log.metadata && Object.keys(log.metadata).length > 0 && (
|
|
184
|
-
<div className="text-[10px] text-muted-foreground mt-1">
|
|
185
|
-
MD: {JSON.stringify(log.metadata).substring(0, 50)}...
|
|
186
|
-
</div>
|
|
187
|
-
)}
|
|
188
|
-
</TableCell>
|
|
189
|
-
<TableCell className="text-right">
|
|
190
|
-
{(log.status || 200) >= 400 ? (
|
|
191
|
-
<Badge variant="destructive">{log.status}</Badge>
|
|
192
|
-
) : (
|
|
193
|
-
<Badge
|
|
194
|
-
variant="secondary"
|
|
195
|
-
className="bg-emerald-100 text-emerald-700 hover:bg-emerald-100"
|
|
196
|
-
>
|
|
197
|
-
{log.status || 200}
|
|
198
|
-
</Badge>
|
|
199
|
-
)}
|
|
200
|
-
</TableCell>
|
|
201
|
-
</TableRow>
|
|
202
|
-
))}
|
|
203
|
-
</TableBody>
|
|
204
|
-
</Table>
|
|
205
|
-
</CardContent>
|
|
206
|
-
</Card>
|
|
207
|
-
</div>
|
|
208
|
-
);
|
|
209
|
-
};
|
|
3
|
+
/**
|
|
4
|
+
* @deprecated Dùng `@goerp/core/system/pages/system-audit-page` (`SystemAuditPage`).
|
|
5
|
+
*
|
|
6
|
+
* Bản cũ ở đây đọc `auditManager` trong RAM ngay trong CLIENT component — luôn
|
|
7
|
+
* rỗng, vì dữ liệu audit nằm ở server. Nay trỏ thẳng sang trang thật (đọc bảng
|
|
8
|
+
* `audit_logs` qua API) để app đang import tên `AuditLogPage` không phải sửa
|
|
9
|
+
* ngay; app mới hãy import thẳng `SystemAuditPage`.
|
|
10
|
+
*/
|
|
11
|
+
export {
|
|
12
|
+
SystemAuditPage as AuditLogPage,
|
|
13
|
+
type SystemAuditPageProps as AuditLogPageProps,
|
|
14
|
+
} from "../../system/pages/system-audit-page";
|
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
AuditLog,
|
|
3
|
-
AuditLogger,
|
|
4
|
-
AuditManagerOptions,
|
|
5
|
-
CreateAuditInput,
|
|
6
|
-
} from "./types";
|
|
7
|
-
import { createLogger } from "../infrastructure/logger";
|
|
8
|
-
import { MemoryAuditLogger } from "./memory-audit-logger";
|
|
9
|
-
|
|
10
|
-
const logger = createLogger("AuditManager");
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Console-based audit logger (default)
|
|
14
|
-
* In production, replace with database or external service
|
|
15
|
-
*/
|
|
16
|
-
class ConsoleAuditLogger implements AuditLogger {
|
|
17
|
-
async log(entry: AuditLog): Promise<void> {
|
|
18
|
-
logger.info(`AUDIT: ${entry.action} ${entry.resource}`, {
|
|
19
|
-
id: entry.id,
|
|
20
|
-
resourceId: entry.resourceId,
|
|
21
|
-
userId: entry.userId,
|
|
22
|
-
changes: entry.changes,
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* AuditManager - tracks actions for compliance and debugging
|
|
29
|
-
*
|
|
30
|
-
* @example
|
|
31
|
-
* ```typescript
|
|
32
|
-
* import { auditManager } from '@goerp/core/audit';
|
|
33
|
-
*
|
|
34
|
-
* // Log an action
|
|
35
|
-
* await auditManager.log({
|
|
36
|
-
* action: 'update',
|
|
37
|
-
* resource: 'purchase-order',
|
|
38
|
-
* resourceId: '123',
|
|
39
|
-
* userId: session.user.id,
|
|
40
|
-
* changes: { status: { old: 'pending', new: 'approved' } }
|
|
41
|
-
* });
|
|
42
|
-
* ```
|
|
43
|
-
*/
|
|
44
|
-
class AuditManagerImpl {
|
|
45
|
-
private auditLogger: AuditLogger;
|
|
46
|
-
private registeredActions: Set<string>;
|
|
47
|
-
|
|
48
|
-
constructor(options: AuditManagerOptions = {}) {
|
|
49
|
-
this.auditLogger = options.logger || new MemoryAuditLogger();
|
|
50
|
-
this.registeredActions = new Set(
|
|
51
|
-
options.actions || ["create", "update", "delete", "login", "logout"],
|
|
52
|
-
);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/** Set custom audit logger */
|
|
56
|
-
setLogger(auditLogger: AuditLogger): void {
|
|
57
|
-
this.auditLogger = auditLogger;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/** Register action for automatic auditing */
|
|
61
|
-
registerAction(action: string): void {
|
|
62
|
-
this.registeredActions.add(action);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/** Check if action is registered for auditing */
|
|
66
|
-
isRegistered(action: string): boolean {
|
|
67
|
-
return this.registeredActions.has(action);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/** Log an audit entry */
|
|
71
|
-
async log(input: CreateAuditInput): Promise<void> {
|
|
72
|
-
const entry: AuditLog = {
|
|
73
|
-
id: crypto.randomUUID(),
|
|
74
|
-
...input,
|
|
75
|
-
createdAt: new Date(),
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
try {
|
|
79
|
-
await this.auditLogger.log(entry);
|
|
80
|
-
} catch (error) {
|
|
81
|
-
logger.error("Failed to log audit entry", { error: String(error) });
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Get logs if the logger supports it (specifically MemoryAuditLogger)
|
|
87
|
-
*/
|
|
88
|
-
getLogs(filter: any = {}): AuditLog[] {
|
|
89
|
-
if ("getLogs" in this.auditLogger) {
|
|
90
|
-
return (this.auditLogger as any).getLogs(filter);
|
|
91
|
-
}
|
|
92
|
-
return [];
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/** Create middleware for automatic API route auditing */
|
|
96
|
-
middleware() {
|
|
97
|
-
return async (
|
|
98
|
-
ctx: {
|
|
99
|
-
action?: { actionName: string; resourceName: string };
|
|
100
|
-
state?: { currentUser?: { id: string }; currentRole?: string };
|
|
101
|
-
request?: { ip?: string; header?: Record<string, string> };
|
|
102
|
-
status?: number;
|
|
103
|
-
},
|
|
104
|
-
next: () => Promise<void>,
|
|
105
|
-
): Promise<void> => {
|
|
106
|
-
const startTime = Date.now();
|
|
107
|
-
let error: Error | null = null;
|
|
108
|
-
|
|
109
|
-
try {
|
|
110
|
-
await next();
|
|
111
|
-
} catch (e) {
|
|
112
|
-
error = e as Error;
|
|
113
|
-
throw e;
|
|
114
|
-
} finally {
|
|
115
|
-
if (ctx.action && this.isRegistered(ctx.action.actionName)) {
|
|
116
|
-
await this.log({
|
|
117
|
-
action: ctx.action.actionName,
|
|
118
|
-
resource: ctx.action.resourceName,
|
|
119
|
-
userId: ctx.state?.currentUser?.id,
|
|
120
|
-
roleName: ctx.state?.currentRole,
|
|
121
|
-
ip: ctx.request?.ip,
|
|
122
|
-
userAgent: ctx.request?.header?.["user-agent"],
|
|
123
|
-
status: ctx.status || (error ? 500 : 200),
|
|
124
|
-
metadata: {
|
|
125
|
-
duration: Date.now() - startTime,
|
|
126
|
-
...(error && { error: error.message }),
|
|
127
|
-
},
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
};
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// Singleton instance
|
|
136
|
-
export const auditManager = new AuditManagerImpl();
|
|
137
|
-
|
|
138
|
-
// Export class for testing
|
|
139
|
-
export { AuditManagerImpl, ConsoleAuditLogger, MemoryAuditLogger };
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
import type { AuditLog, AuditLogger } from "./types";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Filter options for retrieving audit logs
|
|
5
|
-
*/
|
|
6
|
-
export interface AuditLogFilter {
|
|
7
|
-
fromDate?: Date;
|
|
8
|
-
toDate?: Date;
|
|
9
|
-
userId?: string;
|
|
10
|
-
action?: string;
|
|
11
|
-
resource?: string;
|
|
12
|
-
type?: "info" | "warning" | "error";
|
|
13
|
-
limit?: number;
|
|
14
|
-
offset?: number;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* In-memory audit logger that supports retrieval
|
|
19
|
-
*/
|
|
20
|
-
export class MemoryAuditLogger implements AuditLogger {
|
|
21
|
-
private logs: AuditLog[] = [];
|
|
22
|
-
private readonly maxLogs: number;
|
|
23
|
-
|
|
24
|
-
constructor(maxLogs = 1000) {
|
|
25
|
-
this.maxLogs = maxLogs;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
async log(entry: AuditLog): Promise<void> {
|
|
29
|
-
// Add to beginning of array
|
|
30
|
-
this.logs.unshift(entry);
|
|
31
|
-
|
|
32
|
-
// Trim if exceeds max size
|
|
33
|
-
if (this.logs.length > this.maxLogs) {
|
|
34
|
-
this.logs = this.logs.slice(0, this.maxLogs);
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Get logs with filtering
|
|
40
|
-
*/
|
|
41
|
-
getLogs(filter: AuditLogFilter = {}): AuditLog[] {
|
|
42
|
-
let filtered = this.logs;
|
|
43
|
-
|
|
44
|
-
if (filter.fromDate) {
|
|
45
|
-
filtered = filtered.filter((log) => log.createdAt >= filter.fromDate!);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
if (filter.toDate) {
|
|
49
|
-
filtered = filtered.filter((log) => log.createdAt <= filter.toDate!);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
if (filter.userId) {
|
|
53
|
-
filtered = filtered.filter((log) => log.userId === filter.userId);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
if (filter.action) {
|
|
57
|
-
filtered = filtered.filter((log) => log.action === filter.action);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
if (filter.resource) {
|
|
61
|
-
filtered = filtered.filter((log) => log.resource === filter.resource);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// "type" filter is a fuzzy mapping based on status code or action name for demo purposes
|
|
65
|
-
if (filter.type) {
|
|
66
|
-
filtered = filtered.filter((log) => {
|
|
67
|
-
if (filter.type === "error") return (log.status || 200) >= 400;
|
|
68
|
-
if (filter.type === "warning")
|
|
69
|
-
return (log.status || 200) >= 300 && (log.status || 200) < 400;
|
|
70
|
-
return (log.status || 200) < 300;
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const offset = filter.offset || 0;
|
|
75
|
-
const limit = filter.limit || 50;
|
|
76
|
-
|
|
77
|
-
return filtered.slice(offset, offset + limit);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Clear all logs
|
|
82
|
-
*/
|
|
83
|
-
clear(): void {
|
|
84
|
-
this.logs = [];
|
|
85
|
-
}
|
|
86
|
-
}
|
package/src/audit/types.ts
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
// Audit Types
|
|
2
|
-
export interface AuditLog {
|
|
3
|
-
id: string;
|
|
4
|
-
/** Action type: create, update, delete, login, logout, custom */
|
|
5
|
-
action: string;
|
|
6
|
-
/** Resource/entity name (e.g., 'purchase-order', 'user') */
|
|
7
|
-
resource: string;
|
|
8
|
-
/** Resource ID */
|
|
9
|
-
resourceId?: string;
|
|
10
|
-
/** User who performed the action */
|
|
11
|
-
userId?: string;
|
|
12
|
-
/** User's role at time of action */
|
|
13
|
-
roleName?: string;
|
|
14
|
-
/** Changes made (for update actions) */
|
|
15
|
-
changes?: Record<string, { old: unknown; new: unknown }>;
|
|
16
|
-
/** Additional metadata */
|
|
17
|
-
metadata?: Record<string, unknown>;
|
|
18
|
-
/** Request IP address */
|
|
19
|
-
ip?: string;
|
|
20
|
-
/** User agent */
|
|
21
|
-
userAgent?: string;
|
|
22
|
-
/** HTTP status code */
|
|
23
|
-
status?: number;
|
|
24
|
-
/** Timestamp */
|
|
25
|
-
createdAt: Date;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export interface CreateAuditInput {
|
|
29
|
-
action: string;
|
|
30
|
-
resource: string;
|
|
31
|
-
resourceId?: string;
|
|
32
|
-
userId?: string;
|
|
33
|
-
roleName?: string;
|
|
34
|
-
changes?: Record<string, { old: unknown; new: unknown }>;
|
|
35
|
-
metadata?: Record<string, unknown>;
|
|
36
|
-
ip?: string;
|
|
37
|
-
userAgent?: string;
|
|
38
|
-
status?: number;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export interface AuditLogger {
|
|
42
|
-
log(entry: AuditLog): Promise<void>;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export interface AuditManagerOptions {
|
|
46
|
-
/** Custom logger implementation */
|
|
47
|
-
logger?: AuditLogger;
|
|
48
|
-
/** Actions to automatically audit */
|
|
49
|
-
actions?: string[];
|
|
50
|
-
}
|