@usebetterdev/audit-drizzle 0.6.1 → 0.8.0
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/index.cjs +145 -350
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +40 -42
- package/dist/index.d.ts +40 -42
- package/dist/index.js +144 -342
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// src/adapter.ts
|
|
2
|
+
import { encodeCursor, assembleStats } from "@usebetterdev/audit-core";
|
|
2
3
|
import { and as and2, eq as eq2, gte as gte2, lt as lt2, isNotNull, sql as sql2, desc as desc2 } from "drizzle-orm";
|
|
3
4
|
|
|
4
5
|
// src/schema.ts
|
|
@@ -50,23 +51,7 @@ var auditLogs = pgTable(
|
|
|
50
51
|
);
|
|
51
52
|
|
|
52
53
|
// src/column-map.ts
|
|
53
|
-
|
|
54
|
-
"INSERT",
|
|
55
|
-
"UPDATE",
|
|
56
|
-
"DELETE"
|
|
57
|
-
]);
|
|
58
|
-
var VALID_SEVERITIES = /* @__PURE__ */ new Set([
|
|
59
|
-
"low",
|
|
60
|
-
"medium",
|
|
61
|
-
"high",
|
|
62
|
-
"critical"
|
|
63
|
-
]);
|
|
64
|
-
function isAuditOperation(value) {
|
|
65
|
-
return VALID_OPERATIONS.has(value);
|
|
66
|
-
}
|
|
67
|
-
function isAuditSeverity(value) {
|
|
68
|
-
return VALID_SEVERITIES.has(value);
|
|
69
|
-
}
|
|
54
|
+
import { isAuditOperation, isAuditSeverity } from "@usebetterdev/audit-core";
|
|
70
55
|
function auditLogToRow(log) {
|
|
71
56
|
const row = {
|
|
72
57
|
id: log.id,
|
|
@@ -171,7 +156,10 @@ function rowToAuditLog(row) {
|
|
|
171
156
|
}
|
|
172
157
|
|
|
173
158
|
// src/query.ts
|
|
174
|
-
import {
|
|
159
|
+
import {
|
|
160
|
+
decodeCursor,
|
|
161
|
+
interpretFilters
|
|
162
|
+
} from "@usebetterdev/audit-core";
|
|
175
163
|
import {
|
|
176
164
|
and,
|
|
177
165
|
or,
|
|
@@ -186,85 +174,66 @@ import {
|
|
|
186
174
|
desc,
|
|
187
175
|
sql
|
|
188
176
|
} from "drizzle-orm";
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
}
|
|
201
|
-
function buildWhereConditions(filters) {
|
|
202
|
-
const conditions = [];
|
|
203
|
-
if (filters.resource !== void 0) {
|
|
204
|
-
conditions.push(eq(auditLogs.tableName, filters.resource.tableName));
|
|
205
|
-
if (filters.resource.recordId !== void 0) {
|
|
206
|
-
conditions.push(eq(auditLogs.recordId, filters.resource.recordId));
|
|
177
|
+
var FIELD_COLUMNS = {
|
|
178
|
+
tableName: auditLogs.tableName,
|
|
179
|
+
recordId: auditLogs.recordId,
|
|
180
|
+
actorId: auditLogs.actorId,
|
|
181
|
+
severity: auditLogs.severity,
|
|
182
|
+
operation: auditLogs.operation
|
|
183
|
+
};
|
|
184
|
+
function mapCondition(condition) {
|
|
185
|
+
switch (condition.kind) {
|
|
186
|
+
case "eq": {
|
|
187
|
+
return eq(FIELD_COLUMNS[condition.field], condition.value);
|
|
207
188
|
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
if (filters.actorIds.length === 1) {
|
|
211
|
-
conditions.push(eq(auditLogs.actorId, filters.actorIds[0]));
|
|
212
|
-
} else {
|
|
213
|
-
conditions.push(inArray(auditLogs.actorId, filters.actorIds));
|
|
189
|
+
case "in": {
|
|
190
|
+
return inArray(FIELD_COLUMNS[condition.field], condition.values);
|
|
214
191
|
}
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
if (filters.severities.length === 1) {
|
|
218
|
-
conditions.push(eq(auditLogs.severity, filters.severities[0]));
|
|
219
|
-
} else {
|
|
220
|
-
conditions.push(inArray(auditLogs.severity, filters.severities));
|
|
192
|
+
case "timestampGte": {
|
|
193
|
+
return gte(auditLogs.timestamp, condition.value);
|
|
221
194
|
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
if (filters.operations.length === 1) {
|
|
225
|
-
conditions.push(eq(auditLogs.operation, filters.operations[0]));
|
|
226
|
-
} else {
|
|
227
|
-
conditions.push(inArray(auditLogs.operation, filters.operations));
|
|
195
|
+
case "timestampLte": {
|
|
196
|
+
return lte(auditLogs.timestamp, condition.value);
|
|
228
197
|
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
198
|
+
case "search": {
|
|
199
|
+
return or(
|
|
200
|
+
ilike(auditLogs.label, condition.pattern),
|
|
201
|
+
ilike(auditLogs.description, condition.pattern)
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
case "compliance": {
|
|
205
|
+
return sql`${auditLogs.compliance} @> ${JSON.stringify(condition.tags)}::jsonb`;
|
|
206
|
+
}
|
|
207
|
+
case "cursor": {
|
|
208
|
+
const tsCompare = condition.sortOrder === "asc" ? gt : lt;
|
|
209
|
+
const idCompare = condition.sortOrder === "asc" ? gt : lt;
|
|
210
|
+
return or(
|
|
211
|
+
tsCompare(auditLogs.timestamp, condition.timestamp),
|
|
212
|
+
and(
|
|
213
|
+
eq(auditLogs.timestamp, condition.timestamp),
|
|
214
|
+
idCompare(auditLogs.id, condition.id)
|
|
215
|
+
)
|
|
216
|
+
);
|
|
245
217
|
}
|
|
246
218
|
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
219
|
+
}
|
|
220
|
+
function buildWhereConditions(filters, cursor, sortOrder) {
|
|
221
|
+
const decoded = cursor !== void 0 ? decodeCursor(cursor) : void 0;
|
|
222
|
+
const irConditions = interpretFilters(filters, {
|
|
223
|
+
cursor: decoded,
|
|
224
|
+
sortOrder
|
|
225
|
+
});
|
|
226
|
+
const sqlConditions = [];
|
|
227
|
+
for (const c of irConditions) {
|
|
228
|
+
const mapped = mapCondition(c);
|
|
229
|
+
if (mapped !== void 0) {
|
|
230
|
+
sqlConditions.push(mapped);
|
|
231
|
+
}
|
|
251
232
|
}
|
|
252
|
-
if (
|
|
233
|
+
if (sqlConditions.length === 0) {
|
|
253
234
|
return void 0;
|
|
254
235
|
}
|
|
255
|
-
return and(...
|
|
256
|
-
}
|
|
257
|
-
function buildCursorCondition(cursor, sortOrder) {
|
|
258
|
-
const decoded = decodeCursor(cursor);
|
|
259
|
-
const tsCompare = sortOrder === "asc" ? gt : lt;
|
|
260
|
-
const idCompare = sortOrder === "asc" ? gt : lt;
|
|
261
|
-
return or(
|
|
262
|
-
tsCompare(auditLogs.timestamp, decoded.timestamp),
|
|
263
|
-
and(
|
|
264
|
-
eq(auditLogs.timestamp, decoded.timestamp),
|
|
265
|
-
idCompare(auditLogs.id, decoded.id)
|
|
266
|
-
)
|
|
267
|
-
);
|
|
236
|
+
return and(...sqlConditions);
|
|
268
237
|
}
|
|
269
238
|
function buildOrderBy(sortOrder) {
|
|
270
239
|
if (sortOrder === "asc") {
|
|
@@ -272,47 +241,10 @@ function buildOrderBy(sortOrder) {
|
|
|
272
241
|
}
|
|
273
242
|
return [desc(auditLogs.timestamp), desc(auditLogs.id)];
|
|
274
243
|
}
|
|
275
|
-
function encodeCursor(timestamp2, id) {
|
|
276
|
-
const payload = JSON.stringify({ t: timestamp2.toISOString(), i: id });
|
|
277
|
-
return btoa(payload);
|
|
278
|
-
}
|
|
279
|
-
var UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
280
|
-
function decodeCursor(cursor) {
|
|
281
|
-
let parsed;
|
|
282
|
-
try {
|
|
283
|
-
parsed = JSON.parse(atob(cursor));
|
|
284
|
-
} catch {
|
|
285
|
-
throw new Error("Invalid cursor: failed to decode");
|
|
286
|
-
}
|
|
287
|
-
if (typeof parsed !== "object" || parsed === null || !("t" in parsed) || !("i" in parsed)) {
|
|
288
|
-
throw new Error("Invalid cursor: missing required fields");
|
|
289
|
-
}
|
|
290
|
-
const { t, i } = parsed;
|
|
291
|
-
if (typeof t !== "string" || typeof i !== "string") {
|
|
292
|
-
throw new Error("Invalid cursor: fields must be strings");
|
|
293
|
-
}
|
|
294
|
-
const timestamp2 = new Date(t);
|
|
295
|
-
if (isNaN(timestamp2.getTime())) {
|
|
296
|
-
throw new Error("Invalid cursor: invalid timestamp");
|
|
297
|
-
}
|
|
298
|
-
if (!UUID_PATTERN.test(i)) {
|
|
299
|
-
throw new Error("Invalid cursor: id must be a valid UUID");
|
|
300
|
-
}
|
|
301
|
-
return { timestamp: timestamp2, id: i };
|
|
302
|
-
}
|
|
303
244
|
|
|
304
245
|
// src/adapter.ts
|
|
305
246
|
var DEFAULT_LIMIT = 50;
|
|
306
247
|
var MAX_LIMIT = 250;
|
|
307
|
-
function toCount(value) {
|
|
308
|
-
if (typeof value === "number") {
|
|
309
|
-
return value;
|
|
310
|
-
}
|
|
311
|
-
if (typeof value === "string") {
|
|
312
|
-
return Number(value);
|
|
313
|
-
}
|
|
314
|
-
return 0;
|
|
315
|
-
}
|
|
316
248
|
function drizzleAuditAdapter(db) {
|
|
317
249
|
return {
|
|
318
250
|
async writeLog(log) {
|
|
@@ -322,9 +254,7 @@ function drizzleAuditAdapter(db) {
|
|
|
322
254
|
async queryLogs(spec) {
|
|
323
255
|
const sortOrder = spec.sortOrder ?? "desc";
|
|
324
256
|
const limit = Math.min(spec.limit ?? DEFAULT_LIMIT, MAX_LIMIT);
|
|
325
|
-
const
|
|
326
|
-
const cursorCondition = spec.cursor !== void 0 ? buildCursorCondition(spec.cursor, sortOrder) : void 0;
|
|
327
|
-
const combined = and2(whereCondition, cursorCondition);
|
|
257
|
+
const combined = buildWhereConditions(spec.filters, spec.cursor, sortOrder);
|
|
328
258
|
const fetchLimit = limit + 1;
|
|
329
259
|
const orderColumns = buildOrderBy(sortOrder);
|
|
330
260
|
const query = db.select().from(auditLogs).where(combined).orderBy(...orderColumns).limit(fetchLimit);
|
|
@@ -368,30 +298,32 @@ function drizzleAuditAdapter(db) {
|
|
|
368
298
|
},
|
|
369
299
|
async getStats(options) {
|
|
370
300
|
const sinceCondition = options?.since !== void 0 ? gte2(auditLogs.timestamp, options.since) : void 0;
|
|
301
|
+
const untilCondition = options?.until !== void 0 ? lt2(auditLogs.timestamp, options.until) : void 0;
|
|
302
|
+
const timeCondition = and2(sinceCondition, untilCondition);
|
|
371
303
|
const summaryQuery = db.select({
|
|
372
304
|
totalLogs: sql2`count(*)`,
|
|
373
305
|
tablesAudited: sql2`count(DISTINCT ${auditLogs.tableName})`
|
|
374
|
-
}).from(auditLogs).where(
|
|
306
|
+
}).from(auditLogs).where(timeCondition);
|
|
375
307
|
const eventsPerDayQuery = db.select({
|
|
376
308
|
date: sql2`date_trunc('day', ${auditLogs.timestamp})::date`,
|
|
377
309
|
count: sql2`count(*)`
|
|
378
|
-
}).from(auditLogs).where(
|
|
310
|
+
}).from(auditLogs).where(timeCondition).groupBy(sql2`date_trunc('day', ${auditLogs.timestamp})`).orderBy(sql2`date_trunc('day', ${auditLogs.timestamp})`).limit(365);
|
|
379
311
|
const topActorsQuery = db.select({
|
|
380
312
|
actorId: auditLogs.actorId,
|
|
381
313
|
count: sql2`count(*)`
|
|
382
|
-
}).from(auditLogs).where(and2(
|
|
314
|
+
}).from(auditLogs).where(and2(timeCondition, isNotNull(auditLogs.actorId))).groupBy(auditLogs.actorId).orderBy(desc2(sql2`count(*)`)).limit(10);
|
|
383
315
|
const topTablesQuery = db.select({
|
|
384
316
|
tableName: auditLogs.tableName,
|
|
385
317
|
count: sql2`count(*)`
|
|
386
|
-
}).from(auditLogs).where(
|
|
318
|
+
}).from(auditLogs).where(timeCondition).groupBy(auditLogs.tableName).orderBy(desc2(sql2`count(*)`)).limit(10);
|
|
387
319
|
const operationQuery = db.select({
|
|
388
320
|
operation: auditLogs.operation,
|
|
389
321
|
count: sql2`count(*)`
|
|
390
|
-
}).from(auditLogs).where(
|
|
322
|
+
}).from(auditLogs).where(timeCondition).groupBy(auditLogs.operation);
|
|
391
323
|
const severityQuery = db.select({
|
|
392
324
|
severity: auditLogs.severity,
|
|
393
325
|
count: sql2`count(*)`
|
|
394
|
-
}).from(auditLogs).where(and2(
|
|
326
|
+
}).from(auditLogs).where(and2(timeCondition, isNotNull(auditLogs.severity))).groupBy(auditLogs.severity);
|
|
395
327
|
const results = await Promise.all([
|
|
396
328
|
summaryQuery,
|
|
397
329
|
eventsPerDayQuery,
|
|
@@ -419,42 +351,9 @@ function drizzleAuditAdapter(db) {
|
|
|
419
351
|
}
|
|
420
352
|
};
|
|
421
353
|
}
|
|
422
|
-
function assembleStats(summaryRows, eventsPerDayRows, topActorsRows, topTablesRows, operationRows, severityRows) {
|
|
423
|
-
const summary = summaryRows[0];
|
|
424
|
-
const totalLogs = summary !== void 0 ? toCount(summary.totalLogs) : 0;
|
|
425
|
-
const tablesAudited = summary !== void 0 ? toCount(summary.tablesAudited) : 0;
|
|
426
|
-
const eventsPerDay = eventsPerDayRows.map((row) => ({
|
|
427
|
-
date: row.date instanceof Date ? row.date.toISOString().split("T")[0] : String(row.date),
|
|
428
|
-
count: toCount(row.count)
|
|
429
|
-
}));
|
|
430
|
-
const topActors = topActorsRows.map((row) => ({
|
|
431
|
-
actorId: String(row.actorId),
|
|
432
|
-
count: toCount(row.count)
|
|
433
|
-
}));
|
|
434
|
-
const topTables = topTablesRows.map((row) => ({
|
|
435
|
-
tableName: String(row.tableName),
|
|
436
|
-
count: toCount(row.count)
|
|
437
|
-
}));
|
|
438
|
-
const operationBreakdown = {};
|
|
439
|
-
for (const row of operationRows) {
|
|
440
|
-
operationBreakdown[String(row.operation)] = toCount(row.count);
|
|
441
|
-
}
|
|
442
|
-
const severityBreakdown = {};
|
|
443
|
-
for (const row of severityRows) {
|
|
444
|
-
severityBreakdown[String(row.severity)] = toCount(row.count);
|
|
445
|
-
}
|
|
446
|
-
return {
|
|
447
|
-
totalLogs,
|
|
448
|
-
tablesAudited,
|
|
449
|
-
eventsPerDay,
|
|
450
|
-
topActors,
|
|
451
|
-
topTables,
|
|
452
|
-
operationBreakdown,
|
|
453
|
-
severityBreakdown
|
|
454
|
-
};
|
|
455
|
-
}
|
|
456
354
|
|
|
457
355
|
// src/sqlite-adapter.ts
|
|
356
|
+
import { encodeCursor as encodeCursor2, toCount, assembleStats as assembleStats2 } from "@usebetterdev/audit-core";
|
|
458
357
|
import { and as and4, eq as eq4, gte as gte4, lt as lt4, isNotNull as isNotNull2, sql as sql4, desc as desc4 } from "drizzle-orm";
|
|
459
358
|
|
|
460
359
|
// src/sqlite-schema.ts
|
|
@@ -503,23 +402,7 @@ var sqliteAuditLogs = sqliteTable(
|
|
|
503
402
|
);
|
|
504
403
|
|
|
505
404
|
// src/sqlite-column-map.ts
|
|
506
|
-
|
|
507
|
-
"INSERT",
|
|
508
|
-
"UPDATE",
|
|
509
|
-
"DELETE"
|
|
510
|
-
]);
|
|
511
|
-
var VALID_SEVERITIES2 = /* @__PURE__ */ new Set([
|
|
512
|
-
"low",
|
|
513
|
-
"medium",
|
|
514
|
-
"high",
|
|
515
|
-
"critical"
|
|
516
|
-
]);
|
|
517
|
-
function isAuditOperation2(value) {
|
|
518
|
-
return VALID_OPERATIONS2.has(value);
|
|
519
|
-
}
|
|
520
|
-
function isAuditSeverity2(value) {
|
|
521
|
-
return VALID_SEVERITIES2.has(value);
|
|
522
|
-
}
|
|
405
|
+
import { isAuditOperation as isAuditOperation2, isAuditSeverity as isAuditSeverity2 } from "@usebetterdev/audit-core";
|
|
523
406
|
function sqliteAuditLogToRow(log) {
|
|
524
407
|
const row = {
|
|
525
408
|
id: log.id,
|
|
@@ -624,7 +507,10 @@ function sqliteRowToAuditLog(row) {
|
|
|
624
507
|
}
|
|
625
508
|
|
|
626
509
|
// src/sqlite-query.ts
|
|
627
|
-
import {
|
|
510
|
+
import {
|
|
511
|
+
decodeCursor as decodeCursor2,
|
|
512
|
+
interpretFilters as interpretFilters2
|
|
513
|
+
} from "@usebetterdev/audit-core";
|
|
628
514
|
import {
|
|
629
515
|
and as and3,
|
|
630
516
|
or as or2,
|
|
@@ -639,87 +525,72 @@ import {
|
|
|
639
525
|
desc as desc3,
|
|
640
526
|
sql as sql3
|
|
641
527
|
} from "drizzle-orm";
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
}
|
|
654
|
-
function buildSqliteWhereConditions(filters) {
|
|
655
|
-
const conditions = [];
|
|
656
|
-
if (filters.resource !== void 0) {
|
|
657
|
-
conditions.push(eq3(sqliteAuditLogs.tableName, filters.resource.tableName));
|
|
658
|
-
if (filters.resource.recordId !== void 0) {
|
|
659
|
-
conditions.push(eq3(sqliteAuditLogs.recordId, filters.resource.recordId));
|
|
528
|
+
var FIELD_COLUMNS2 = {
|
|
529
|
+
tableName: sqliteAuditLogs.tableName,
|
|
530
|
+
recordId: sqliteAuditLogs.recordId,
|
|
531
|
+
actorId: sqliteAuditLogs.actorId,
|
|
532
|
+
severity: sqliteAuditLogs.severity,
|
|
533
|
+
operation: sqliteAuditLogs.operation
|
|
534
|
+
};
|
|
535
|
+
function mapCondition2(condition) {
|
|
536
|
+
switch (condition.kind) {
|
|
537
|
+
case "eq": {
|
|
538
|
+
return eq3(FIELD_COLUMNS2[condition.field], condition.value);
|
|
660
539
|
}
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
if (filters.actorIds.length === 1) {
|
|
664
|
-
conditions.push(eq3(sqliteAuditLogs.actorId, filters.actorIds[0]));
|
|
665
|
-
} else {
|
|
666
|
-
conditions.push(inArray2(sqliteAuditLogs.actorId, filters.actorIds));
|
|
540
|
+
case "in": {
|
|
541
|
+
return inArray2(FIELD_COLUMNS2[condition.field], condition.values);
|
|
667
542
|
}
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
if (filters.severities.length === 1) {
|
|
671
|
-
conditions.push(eq3(sqliteAuditLogs.severity, filters.severities[0]));
|
|
672
|
-
} else {
|
|
673
|
-
conditions.push(inArray2(sqliteAuditLogs.severity, filters.severities));
|
|
543
|
+
case "timestampGte": {
|
|
544
|
+
return gte3(sqliteAuditLogs.timestamp, condition.value);
|
|
674
545
|
}
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
if (filters.operations.length === 1) {
|
|
678
|
-
conditions.push(eq3(sqliteAuditLogs.operation, filters.operations[0]));
|
|
679
|
-
} else {
|
|
680
|
-
conditions.push(inArray2(sqliteAuditLogs.operation, filters.operations));
|
|
546
|
+
case "timestampLte": {
|
|
547
|
+
return lte2(sqliteAuditLogs.timestamp, condition.value);
|
|
681
548
|
}
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
conditions.push(lte2(sqliteAuditLogs.timestamp, resolveTimeFilter2(filters.until)));
|
|
688
|
-
}
|
|
689
|
-
if (filters.searchText !== void 0 && filters.searchText.length > 0) {
|
|
690
|
-
const escaped = escapeLikePattern2(filters.searchText);
|
|
691
|
-
const pattern = `%${escaped}%`;
|
|
692
|
-
const searchCondition = or2(
|
|
693
|
-
like(sqliteAuditLogs.label, pattern),
|
|
694
|
-
like(sqliteAuditLogs.description, pattern)
|
|
695
|
-
);
|
|
696
|
-
if (searchCondition !== void 0) {
|
|
697
|
-
conditions.push(searchCondition);
|
|
549
|
+
case "search": {
|
|
550
|
+
return or2(
|
|
551
|
+
like(sqliteAuditLogs.label, condition.pattern),
|
|
552
|
+
like(sqliteAuditLogs.description, condition.pattern)
|
|
553
|
+
);
|
|
698
554
|
}
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
555
|
+
case "compliance": {
|
|
556
|
+
const tagConditions = [];
|
|
557
|
+
for (const tag of condition.tags) {
|
|
558
|
+
tagConditions.push(
|
|
559
|
+
sql3`EXISTS (SELECT 1 FROM json_each(${sqliteAuditLogs.compliance}) WHERE value = ${tag})`
|
|
560
|
+
);
|
|
561
|
+
}
|
|
562
|
+
return and3(...tagConditions);
|
|
563
|
+
}
|
|
564
|
+
case "cursor": {
|
|
565
|
+
const tsCompare = condition.sortOrder === "asc" ? gt2 : lt3;
|
|
566
|
+
const idCompare = condition.sortOrder === "asc" ? gt2 : lt3;
|
|
567
|
+
return or2(
|
|
568
|
+
tsCompare(sqliteAuditLogs.timestamp, condition.timestamp),
|
|
569
|
+
and3(
|
|
570
|
+
eq3(sqliteAuditLogs.timestamp, condition.timestamp),
|
|
571
|
+
idCompare(sqliteAuditLogs.id, condition.id)
|
|
572
|
+
)
|
|
704
573
|
);
|
|
705
574
|
}
|
|
706
575
|
}
|
|
707
|
-
|
|
576
|
+
}
|
|
577
|
+
function buildSqliteWhereConditions(filters, cursor, sortOrder) {
|
|
578
|
+
const decoded = cursor !== void 0 ? decodeCursor2(cursor) : void 0;
|
|
579
|
+
const irConditions = interpretFilters2(filters, {
|
|
580
|
+
cursor: decoded,
|
|
581
|
+
sortOrder
|
|
582
|
+
});
|
|
583
|
+
const sqlConditions = [];
|
|
584
|
+
for (const c of irConditions) {
|
|
585
|
+
const mapped = mapCondition2(c);
|
|
586
|
+
if (mapped !== void 0) {
|
|
587
|
+
sqlConditions.push(mapped);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
if (sqlConditions.length === 0) {
|
|
708
591
|
return void 0;
|
|
709
592
|
}
|
|
710
|
-
return and3(...
|
|
711
|
-
}
|
|
712
|
-
function buildSqliteCursorCondition(cursor, sortOrder) {
|
|
713
|
-
const decoded = decodeSqliteCursor(cursor);
|
|
714
|
-
const tsCompare = sortOrder === "asc" ? gt2 : lt3;
|
|
715
|
-
const idCompare = sortOrder === "asc" ? gt2 : lt3;
|
|
716
|
-
return or2(
|
|
717
|
-
tsCompare(sqliteAuditLogs.timestamp, decoded.timestamp),
|
|
718
|
-
and3(
|
|
719
|
-
eq3(sqliteAuditLogs.timestamp, decoded.timestamp),
|
|
720
|
-
idCompare(sqliteAuditLogs.id, decoded.id)
|
|
721
|
-
)
|
|
722
|
-
);
|
|
593
|
+
return and3(...sqlConditions);
|
|
723
594
|
}
|
|
724
595
|
function buildSqliteOrderBy(sortOrder) {
|
|
725
596
|
if (sortOrder === "asc") {
|
|
@@ -727,47 +598,10 @@ function buildSqliteOrderBy(sortOrder) {
|
|
|
727
598
|
}
|
|
728
599
|
return [desc3(sqliteAuditLogs.timestamp), desc3(sqliteAuditLogs.id)];
|
|
729
600
|
}
|
|
730
|
-
function encodeSqliteCursor(timestamp2, id) {
|
|
731
|
-
const payload = JSON.stringify({ t: timestamp2.toISOString(), i: id });
|
|
732
|
-
return btoa(payload);
|
|
733
|
-
}
|
|
734
|
-
var UUID_PATTERN2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
735
|
-
function decodeSqliteCursor(cursor) {
|
|
736
|
-
let parsed;
|
|
737
|
-
try {
|
|
738
|
-
parsed = JSON.parse(atob(cursor));
|
|
739
|
-
} catch {
|
|
740
|
-
throw new Error("Invalid cursor: failed to decode");
|
|
741
|
-
}
|
|
742
|
-
if (typeof parsed !== "object" || parsed === null || !("t" in parsed) || !("i" in parsed)) {
|
|
743
|
-
throw new Error("Invalid cursor: missing required fields");
|
|
744
|
-
}
|
|
745
|
-
const { t, i } = parsed;
|
|
746
|
-
if (typeof t !== "string" || typeof i !== "string") {
|
|
747
|
-
throw new Error("Invalid cursor: fields must be strings");
|
|
748
|
-
}
|
|
749
|
-
const timestamp2 = new Date(t);
|
|
750
|
-
if (isNaN(timestamp2.getTime())) {
|
|
751
|
-
throw new Error("Invalid cursor: invalid timestamp");
|
|
752
|
-
}
|
|
753
|
-
if (!UUID_PATTERN2.test(i)) {
|
|
754
|
-
throw new Error("Invalid cursor: id must be a valid UUID");
|
|
755
|
-
}
|
|
756
|
-
return { timestamp: timestamp2, id: i };
|
|
757
|
-
}
|
|
758
601
|
|
|
759
602
|
// src/sqlite-adapter.ts
|
|
760
603
|
var DEFAULT_LIMIT2 = 50;
|
|
761
604
|
var MAX_LIMIT2 = 250;
|
|
762
|
-
function toCount2(value) {
|
|
763
|
-
if (typeof value === "number") {
|
|
764
|
-
return value;
|
|
765
|
-
}
|
|
766
|
-
if (typeof value === "string") {
|
|
767
|
-
return Number(value);
|
|
768
|
-
}
|
|
769
|
-
return 0;
|
|
770
|
-
}
|
|
771
605
|
function drizzleSqliteAuditAdapter(db) {
|
|
772
606
|
return {
|
|
773
607
|
async writeLog(log) {
|
|
@@ -777,9 +611,7 @@ function drizzleSqliteAuditAdapter(db) {
|
|
|
777
611
|
async queryLogs(spec) {
|
|
778
612
|
const sortOrder = spec.sortOrder ?? "desc";
|
|
779
613
|
const limit = Math.min(spec.limit ?? DEFAULT_LIMIT2, MAX_LIMIT2);
|
|
780
|
-
const
|
|
781
|
-
const cursorCondition = spec.cursor !== void 0 ? buildSqliteCursorCondition(spec.cursor, sortOrder) : void 0;
|
|
782
|
-
const combined = and4(whereCondition, cursorCondition);
|
|
614
|
+
const combined = buildSqliteWhereConditions(spec.filters, spec.cursor, sortOrder);
|
|
783
615
|
const fetchLimit = limit + 1;
|
|
784
616
|
const orderColumns = buildSqliteOrderBy(sortOrder);
|
|
785
617
|
const query = db.select().from(sqliteAuditLogs).where(combined).orderBy(...orderColumns).limit(fetchLimit);
|
|
@@ -789,7 +621,7 @@ function drizzleSqliteAuditAdapter(db) {
|
|
|
789
621
|
const entries = resultRows.map(sqliteRowToAuditLog);
|
|
790
622
|
const lastRow = resultRows[resultRows.length - 1];
|
|
791
623
|
if (hasNextPage && lastRow !== void 0) {
|
|
792
|
-
return { entries, nextCursor:
|
|
624
|
+
return { entries, nextCursor: encodeCursor2(lastRow.timestamp, lastRow.id) };
|
|
793
625
|
}
|
|
794
626
|
return { entries };
|
|
795
627
|
},
|
|
@@ -812,35 +644,37 @@ function drizzleSqliteAuditAdapter(db) {
|
|
|
812
644
|
}
|
|
813
645
|
await db.delete(sqliteAuditLogs).where(and4(...conditions));
|
|
814
646
|
const changesResult = await db.select({ count: sql4`changes()` }).from(sqliteAuditLogs);
|
|
815
|
-
const deletedCount = changesResult[0] !== void 0 ?
|
|
647
|
+
const deletedCount = changesResult[0] !== void 0 ? toCount(changesResult[0].count) : 0;
|
|
816
648
|
return { deletedCount };
|
|
817
649
|
},
|
|
818
650
|
async getStats(options) {
|
|
819
651
|
const sinceCondition = options?.since !== void 0 ? gte4(sqliteAuditLogs.timestamp, options.since) : void 0;
|
|
652
|
+
const untilCondition = options?.until !== void 0 ? lt4(sqliteAuditLogs.timestamp, options.until) : void 0;
|
|
653
|
+
const timeCondition = and4(sinceCondition, untilCondition);
|
|
820
654
|
const summaryQuery = db.select({
|
|
821
655
|
totalLogs: sql4`count(*)`,
|
|
822
656
|
tablesAudited: sql4`count(DISTINCT ${sqliteAuditLogs.tableName})`
|
|
823
|
-
}).from(sqliteAuditLogs).where(
|
|
657
|
+
}).from(sqliteAuditLogs).where(timeCondition);
|
|
824
658
|
const eventsPerDayQuery = db.select({
|
|
825
659
|
date: sql4`strftime('%Y-%m-%d', ${sqliteAuditLogs.timestamp}, 'unixepoch')`,
|
|
826
660
|
count: sql4`count(*)`
|
|
827
|
-
}).from(sqliteAuditLogs).where(
|
|
661
|
+
}).from(sqliteAuditLogs).where(timeCondition).groupBy(sql4`strftime('%Y-%m-%d', ${sqliteAuditLogs.timestamp}, 'unixepoch')`).orderBy(sql4`strftime('%Y-%m-%d', ${sqliteAuditLogs.timestamp}, 'unixepoch')`).limit(365);
|
|
828
662
|
const topActorsQuery = db.select({
|
|
829
663
|
actorId: sqliteAuditLogs.actorId,
|
|
830
664
|
count: sql4`count(*)`
|
|
831
|
-
}).from(sqliteAuditLogs).where(and4(
|
|
665
|
+
}).from(sqliteAuditLogs).where(and4(timeCondition, isNotNull2(sqliteAuditLogs.actorId))).groupBy(sqliteAuditLogs.actorId).orderBy(desc4(sql4`count(*)`)).limit(10);
|
|
832
666
|
const topTablesQuery = db.select({
|
|
833
667
|
tableName: sqliteAuditLogs.tableName,
|
|
834
668
|
count: sql4`count(*)`
|
|
835
|
-
}).from(sqliteAuditLogs).where(
|
|
669
|
+
}).from(sqliteAuditLogs).where(timeCondition).groupBy(sqliteAuditLogs.tableName).orderBy(desc4(sql4`count(*)`)).limit(10);
|
|
836
670
|
const operationQuery = db.select({
|
|
837
671
|
operation: sqliteAuditLogs.operation,
|
|
838
672
|
count: sql4`count(*)`
|
|
839
|
-
}).from(sqliteAuditLogs).where(
|
|
673
|
+
}).from(sqliteAuditLogs).where(timeCondition).groupBy(sqliteAuditLogs.operation);
|
|
840
674
|
const severityQuery = db.select({
|
|
841
675
|
severity: sqliteAuditLogs.severity,
|
|
842
676
|
count: sql4`count(*)`
|
|
843
|
-
}).from(sqliteAuditLogs).where(and4(
|
|
677
|
+
}).from(sqliteAuditLogs).where(and4(timeCondition, isNotNull2(sqliteAuditLogs.severity))).groupBy(sqliteAuditLogs.severity);
|
|
844
678
|
const results = await Promise.all([
|
|
845
679
|
summaryQuery,
|
|
846
680
|
eventsPerDayQuery,
|
|
@@ -868,40 +702,6 @@ function drizzleSqliteAuditAdapter(db) {
|
|
|
868
702
|
}
|
|
869
703
|
};
|
|
870
704
|
}
|
|
871
|
-
function assembleStats2(summaryRows, eventsPerDayRows, topActorsRows, topTablesRows, operationRows, severityRows) {
|
|
872
|
-
const summary = summaryRows[0];
|
|
873
|
-
const totalLogs = summary !== void 0 ? toCount2(summary.totalLogs) : 0;
|
|
874
|
-
const tablesAudited = summary !== void 0 ? toCount2(summary.tablesAudited) : 0;
|
|
875
|
-
const eventsPerDay = eventsPerDayRows.map((row) => ({
|
|
876
|
-
date: String(row.date),
|
|
877
|
-
count: toCount2(row.count)
|
|
878
|
-
}));
|
|
879
|
-
const topActors = topActorsRows.map((row) => ({
|
|
880
|
-
actorId: String(row.actorId),
|
|
881
|
-
count: toCount2(row.count)
|
|
882
|
-
}));
|
|
883
|
-
const topTables = topTablesRows.map((row) => ({
|
|
884
|
-
tableName: String(row.tableName),
|
|
885
|
-
count: toCount2(row.count)
|
|
886
|
-
}));
|
|
887
|
-
const operationBreakdown = {};
|
|
888
|
-
for (const row of operationRows) {
|
|
889
|
-
operationBreakdown[String(row.operation)] = toCount2(row.count);
|
|
890
|
-
}
|
|
891
|
-
const severityBreakdown = {};
|
|
892
|
-
for (const row of severityRows) {
|
|
893
|
-
severityBreakdown[String(row.severity)] = toCount2(row.count);
|
|
894
|
-
}
|
|
895
|
-
return {
|
|
896
|
-
totalLogs,
|
|
897
|
-
tablesAudited,
|
|
898
|
-
eventsPerDay,
|
|
899
|
-
topActors,
|
|
900
|
-
topTables,
|
|
901
|
-
operationBreakdown,
|
|
902
|
-
severityBreakdown
|
|
903
|
-
};
|
|
904
|
-
}
|
|
905
705
|
|
|
906
706
|
// src/proxy.ts
|
|
907
707
|
import { getTableName } from "drizzle-orm";
|
|
@@ -1394,15 +1194,17 @@ async function fireCaptureLog(result, ctx, state) {
|
|
|
1394
1194
|
}
|
|
1395
1195
|
}
|
|
1396
1196
|
}
|
|
1197
|
+
|
|
1198
|
+
// src/index.ts
|
|
1199
|
+
import { encodeCursor as encodeCursor3, decodeCursor as decodeCursor3 } from "@usebetterdev/audit-core";
|
|
1397
1200
|
export {
|
|
1398
1201
|
auditLogs,
|
|
1399
|
-
buildCursorCondition,
|
|
1400
1202
|
buildOrderBy,
|
|
1401
1203
|
buildWhereConditions,
|
|
1402
|
-
decodeCursor,
|
|
1204
|
+
decodeCursor3 as decodeCursor,
|
|
1403
1205
|
drizzleAuditAdapter,
|
|
1404
1206
|
drizzleSqliteAuditAdapter,
|
|
1405
|
-
encodeCursor,
|
|
1207
|
+
encodeCursor3 as encodeCursor,
|
|
1406
1208
|
sqliteAuditLogs,
|
|
1407
1209
|
withAuditProxy
|
|
1408
1210
|
};
|