@cepseudo/adonis-audit-log 1.0.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.
@@ -0,0 +1,10 @@
1
+ import { BaseModel } from '@adonisjs/lucid/orm';
2
+ import { DateTime } from 'luxon';
3
+ export default class AuditLog extends BaseModel {
4
+ static table: string;
5
+ id: number;
6
+ actionType: string;
7
+ metadata: Record<string, unknown>;
8
+ userId: number | null;
9
+ createdAt: DateTime;
10
+ }
@@ -0,0 +1,37 @@
1
+ /*
2
+ |--------------------------------------------------------------------------
3
+ | Audit Log Model
4
+ |--------------------------------------------------------------------------
5
+ |
6
+ | Lucid model for the audit_logs table. Stores custom application logs
7
+ | for tracking user actions and business events.
8
+ |
9
+ */
10
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
11
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
12
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
13
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
14
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
15
+ };
16
+ import { BaseModel, column } from '@adonisjs/lucid/orm';
17
+ export default class AuditLog extends BaseModel {
18
+ static table = 'audit_logs';
19
+ }
20
+ __decorate([
21
+ column({ isPrimary: true })
22
+ ], AuditLog.prototype, "id", void 0);
23
+ __decorate([
24
+ column()
25
+ ], AuditLog.prototype, "actionType", void 0);
26
+ __decorate([
27
+ column({
28
+ prepare: (value) => JSON.stringify(value),
29
+ consume: (value) => (typeof value === 'string' ? JSON.parse(value) : value),
30
+ })
31
+ ], AuditLog.prototype, "metadata", void 0);
32
+ __decorate([
33
+ column()
34
+ ], AuditLog.prototype, "userId", void 0);
35
+ __decorate([
36
+ column.dateTime({ autoCreate: true })
37
+ ], AuditLog.prototype, "createdAt", void 0);
@@ -0,0 +1,15 @@
1
+ import { BaseModel } from '@adonisjs/lucid/orm';
2
+ import { DateTime } from 'luxon';
3
+ export default class ErrorLog extends BaseModel {
4
+ static table: string;
5
+ id: number;
6
+ errorType: string;
7
+ message: string;
8
+ stack: string | null;
9
+ url: string | null;
10
+ method: string | null;
11
+ statusCode: number | null;
12
+ userId: number | null;
13
+ context: Record<string, unknown> | null;
14
+ createdAt: DateTime;
15
+ }
@@ -0,0 +1,52 @@
1
+ /*
2
+ |--------------------------------------------------------------------------
3
+ | Error Log Model
4
+ |--------------------------------------------------------------------------
5
+ |
6
+ | Lucid model for the error_logs table. Stores application error tracking
7
+ | data.
8
+ |
9
+ */
10
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
11
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
12
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
13
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
14
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
15
+ };
16
+ import { BaseModel, column } from '@adonisjs/lucid/orm';
17
+ export default class ErrorLog extends BaseModel {
18
+ static table = 'error_logs';
19
+ }
20
+ __decorate([
21
+ column({ isPrimary: true })
22
+ ], ErrorLog.prototype, "id", void 0);
23
+ __decorate([
24
+ column()
25
+ ], ErrorLog.prototype, "errorType", void 0);
26
+ __decorate([
27
+ column()
28
+ ], ErrorLog.prototype, "message", void 0);
29
+ __decorate([
30
+ column()
31
+ ], ErrorLog.prototype, "stack", void 0);
32
+ __decorate([
33
+ column()
34
+ ], ErrorLog.prototype, "url", void 0);
35
+ __decorate([
36
+ column()
37
+ ], ErrorLog.prototype, "method", void 0);
38
+ __decorate([
39
+ column()
40
+ ], ErrorLog.prototype, "statusCode", void 0);
41
+ __decorate([
42
+ column()
43
+ ], ErrorLog.prototype, "userId", void 0);
44
+ __decorate([
45
+ column({
46
+ prepare: (value) => (value ? JSON.stringify(value) : null),
47
+ consume: (value) => value ? (typeof value === 'string' ? JSON.parse(value) : value) : null,
48
+ })
49
+ ], ErrorLog.prototype, "context", void 0);
50
+ __decorate([
51
+ column.dateTime({ autoCreate: true })
52
+ ], ErrorLog.prototype, "createdAt", void 0);
@@ -0,0 +1,17 @@
1
+ import { BaseModel } from '@adonisjs/lucid/orm';
2
+ import { DateTime } from 'luxon';
3
+ export default class RequestLog extends BaseModel {
4
+ static table: string;
5
+ id: number;
6
+ method: string;
7
+ url: string;
8
+ routeName: string | null;
9
+ statusCode: number;
10
+ responseTimeMs: number;
11
+ ip: string | null;
12
+ userAgent: string | null;
13
+ userId: number | null;
14
+ requestBody: Record<string, unknown> | null;
15
+ requestQuery: Record<string, unknown> | null;
16
+ createdAt: DateTime;
17
+ }
@@ -0,0 +1,61 @@
1
+ /*
2
+ |--------------------------------------------------------------------------
3
+ | Request Log Model
4
+ |--------------------------------------------------------------------------
5
+ |
6
+ | Lucid model for the request_logs table. Stores automatic HTTP request
7
+ | logging data.
8
+ |
9
+ */
10
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
11
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
12
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
13
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
14
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
15
+ };
16
+ import { BaseModel, column } from '@adonisjs/lucid/orm';
17
+ export default class RequestLog extends BaseModel {
18
+ static table = 'request_logs';
19
+ }
20
+ __decorate([
21
+ column({ isPrimary: true })
22
+ ], RequestLog.prototype, "id", void 0);
23
+ __decorate([
24
+ column()
25
+ ], RequestLog.prototype, "method", void 0);
26
+ __decorate([
27
+ column()
28
+ ], RequestLog.prototype, "url", void 0);
29
+ __decorate([
30
+ column()
31
+ ], RequestLog.prototype, "routeName", void 0);
32
+ __decorate([
33
+ column()
34
+ ], RequestLog.prototype, "statusCode", void 0);
35
+ __decorate([
36
+ column()
37
+ ], RequestLog.prototype, "responseTimeMs", void 0);
38
+ __decorate([
39
+ column()
40
+ ], RequestLog.prototype, "ip", void 0);
41
+ __decorate([
42
+ column()
43
+ ], RequestLog.prototype, "userAgent", void 0);
44
+ __decorate([
45
+ column()
46
+ ], RequestLog.prototype, "userId", void 0);
47
+ __decorate([
48
+ column({
49
+ prepare: (value) => (value ? JSON.stringify(value) : null),
50
+ consume: (value) => value ? (typeof value === 'string' ? JSON.parse(value) : value) : null,
51
+ })
52
+ ], RequestLog.prototype, "requestBody", void 0);
53
+ __decorate([
54
+ column({
55
+ prepare: (value) => (value ? JSON.stringify(value) : null),
56
+ consume: (value) => value ? (typeof value === 'string' ? JSON.parse(value) : value) : null,
57
+ })
58
+ ], RequestLog.prototype, "requestQuery", void 0);
59
+ __decorate([
60
+ column.dateTime({ autoCreate: true })
61
+ ], RequestLog.prototype, "createdAt", void 0);
@@ -0,0 +1,24 @@
1
+ import type { HttpContext } from '@adonisjs/core/http';
2
+ import type { AuditConfig } from './types.js';
3
+ import RequestLog from './models/request_log.js';
4
+ export declare class RequestLogger {
5
+ #private;
6
+ protected config: AuditConfig;
7
+ constructor(config: AuditConfig);
8
+ /**
9
+ * Check if the request should be logged
10
+ */
11
+ shouldLog(ctx: HttpContext): boolean;
12
+ /**
13
+ * Sanitize sensitive fields from an object
14
+ */
15
+ sanitize(data: Record<string, unknown> | null): Record<string, unknown> | null;
16
+ /**
17
+ * Log the request (fire-and-forget, non-blocking)
18
+ */
19
+ logAsync(ctx: HttpContext, responseTimeMs: number): void;
20
+ /**
21
+ * Log the request (blocking, returns the created log)
22
+ */
23
+ log(ctx: HttpContext, responseTimeMs: number): Promise<RequestLog | null>;
24
+ }
@@ -0,0 +1,130 @@
1
+ /*
2
+ |--------------------------------------------------------------------------
3
+ | Request Logger
4
+ |--------------------------------------------------------------------------
5
+ |
6
+ | Logic for logging HTTP requests. Used by the request logger middleware.
7
+ |
8
+ */
9
+ import { getUserIdFromContext } from './types.js';
10
+ import RequestLog from './models/request_log.js';
11
+ export class RequestLogger {
12
+ config;
13
+ #excludedMethods;
14
+ #excludedRoutes;
15
+ #sanitizeFields;
16
+ constructor(config) {
17
+ this.config = config;
18
+ // Pre-compute sets for O(1) lookups
19
+ this.#excludedMethods = new Set(config.requestLog.excludeMethods);
20
+ this.#excludedRoutes = config.requestLog.excludeRoutes;
21
+ this.#sanitizeFields = new Set(config.requestLog.sanitizeFields);
22
+ }
23
+ /**
24
+ * Check if the request should be logged
25
+ */
26
+ shouldLog(ctx) {
27
+ if (!this.config.enabled || !this.config.requestLog.enabled) {
28
+ return false;
29
+ }
30
+ const { request } = ctx;
31
+ const method = request.method();
32
+ const url = request.url();
33
+ // O(1) lookup instead of O(n)
34
+ if (this.#excludedMethods.has(method)) {
35
+ return false;
36
+ }
37
+ // Check excluded routes
38
+ for (const excludedRoute of this.#excludedRoutes) {
39
+ if (url.startsWith(excludedRoute)) {
40
+ return false;
41
+ }
42
+ }
43
+ return true;
44
+ }
45
+ /**
46
+ * Check if object needs sanitization
47
+ */
48
+ #needsSanitization(data) {
49
+ for (const key of Object.keys(data)) {
50
+ if (this.#sanitizeFields.has(key)) {
51
+ return true;
52
+ }
53
+ const value = data[key];
54
+ if (typeof value === 'object' &&
55
+ value !== null &&
56
+ this.#needsSanitization(value)) {
57
+ return true;
58
+ }
59
+ }
60
+ return false;
61
+ }
62
+ /**
63
+ * Sanitize sensitive fields from an object
64
+ */
65
+ sanitize(data) {
66
+ if (!data || this.#sanitizeFields.size === 0) {
67
+ return data;
68
+ }
69
+ // Skip copy if no sanitization needed
70
+ if (!this.#needsSanitization(data)) {
71
+ return data;
72
+ }
73
+ const sanitized = {};
74
+ for (const [key, value] of Object.entries(data)) {
75
+ if (this.#sanitizeFields.has(key)) {
76
+ sanitized[key] = '[REDACTED]';
77
+ }
78
+ else if (typeof value === 'object' && value !== null) {
79
+ sanitized[key] = this.sanitize(value);
80
+ }
81
+ else {
82
+ sanitized[key] = value;
83
+ }
84
+ }
85
+ return sanitized;
86
+ }
87
+ /**
88
+ * Log the request (fire-and-forget, non-blocking)
89
+ */
90
+ logAsync(ctx, responseTimeMs) {
91
+ if (!this.shouldLog(ctx)) {
92
+ return;
93
+ }
94
+ // Fire-and-forget to not block the response
95
+ setImmediate(() => {
96
+ this.#createLog(ctx, responseTimeMs).catch((error) => {
97
+ console.error('[RequestLogger] Failed to log request:', error);
98
+ });
99
+ });
100
+ }
101
+ /**
102
+ * Log the request (blocking, returns the created log)
103
+ */
104
+ async log(ctx, responseTimeMs) {
105
+ if (!this.shouldLog(ctx)) {
106
+ return null;
107
+ }
108
+ return this.#createLog(ctx, responseTimeMs);
109
+ }
110
+ async #createLog(ctx, responseTimeMs) {
111
+ const { request, response, route } = ctx;
112
+ const entry = {
113
+ method: request.method(),
114
+ url: request.url(true),
115
+ routeName: route?.name ?? null,
116
+ statusCode: response.getStatus(),
117
+ responseTimeMs,
118
+ ip: request.ip(),
119
+ userAgent: request.header('user-agent')?.substring(0, 512) ?? null,
120
+ userId: getUserIdFromContext(ctx),
121
+ requestBody: this.config.requestLog.logBody
122
+ ? this.sanitize(request.body())
123
+ : null,
124
+ requestQuery: this.config.requestLog.logQuery
125
+ ? this.sanitize(request.qs())
126
+ : null,
127
+ };
128
+ return RequestLog.create(entry);
129
+ }
130
+ }
@@ -0,0 +1,94 @@
1
+ import type { HttpContext } from '@adonisjs/core/http';
2
+ /**
3
+ * Interface for authenticated user with minimal required properties
4
+ */
5
+ export interface AuthUser {
6
+ id: number;
7
+ }
8
+ /**
9
+ * Interface for auth object when @adonisjs/auth is installed
10
+ */
11
+ export interface AuthContract {
12
+ user?: AuthUser | null;
13
+ }
14
+ /**
15
+ * Extended HttpContext that may include auth from @adonisjs/auth package
16
+ */
17
+ export interface HttpContextWithAuth extends HttpContext {
18
+ auth?: AuthContract;
19
+ }
20
+ /**
21
+ * Type guard to check if context has auth
22
+ */
23
+ export declare function hasAuth(ctx: HttpContext): ctx is HttpContextWithAuth;
24
+ /**
25
+ * Helper to safely get user ID from context
26
+ */
27
+ export declare function getUserIdFromContext(ctx: HttpContext): number | null;
28
+ /**
29
+ * Interface for HTTP errors with status code
30
+ */
31
+ export interface HttpError extends Error {
32
+ status?: number;
33
+ code?: string;
34
+ }
35
+ /**
36
+ * Type guard to check if error is an HttpError
37
+ */
38
+ export declare function isHttpError(error: unknown): error is HttpError;
39
+ /**
40
+ * Helper to get status code from error
41
+ */
42
+ export declare function getErrorStatusCode(error: unknown): number | undefined;
43
+ export interface AuditConfig {
44
+ enabled: boolean;
45
+ requestLog: {
46
+ enabled: boolean;
47
+ excludeRoutes: string[];
48
+ excludeMethods: string[];
49
+ logBody: boolean;
50
+ logQuery: boolean;
51
+ sanitizeFields: string[];
52
+ };
53
+ errorLog: {
54
+ enabled: boolean;
55
+ excludeStatusCodes: number[];
56
+ includeStack: boolean;
57
+ };
58
+ auditLog: {
59
+ enabled: boolean;
60
+ };
61
+ retention: {
62
+ requestLogs: number;
63
+ errorLogs: number;
64
+ auditLogs: number;
65
+ };
66
+ }
67
+ export interface AuditLogEntry {
68
+ actionType: string;
69
+ metadata: Record<string, unknown>;
70
+ userId?: number | null;
71
+ }
72
+ export interface RequestLogEntry {
73
+ method: string;
74
+ url: string;
75
+ routeName: string | null;
76
+ statusCode: number;
77
+ responseTimeMs: number;
78
+ ip: string | null;
79
+ userAgent: string | null;
80
+ userId: number | null;
81
+ requestBody: Record<string, unknown> | null;
82
+ requestQuery: Record<string, unknown> | null;
83
+ }
84
+ export interface ErrorLogEntry {
85
+ errorType: string;
86
+ message: string;
87
+ stack: string | null;
88
+ url: string | null;
89
+ method: string | null;
90
+ statusCode: number | null;
91
+ userId: number | null;
92
+ context: Record<string, unknown> | null;
93
+ }
94
+ export declare function defineConfig(config: Partial<AuditConfig>): AuditConfig;
@@ -0,0 +1,64 @@
1
+ /*
2
+ |--------------------------------------------------------------------------
3
+ | Types
4
+ |--------------------------------------------------------------------------
5
+ |
6
+ | TypeScript interfaces and types for the audit log package.
7
+ |
8
+ */
9
+ /**
10
+ * Type guard to check if context has auth
11
+ */
12
+ export function hasAuth(ctx) {
13
+ return 'auth' in ctx && ctx.auth !== undefined;
14
+ }
15
+ /**
16
+ * Helper to safely get user ID from context
17
+ */
18
+ export function getUserIdFromContext(ctx) {
19
+ if (hasAuth(ctx)) {
20
+ return ctx.auth?.user?.id ?? null;
21
+ }
22
+ return null;
23
+ }
24
+ /**
25
+ * Type guard to check if error is an HttpError
26
+ */
27
+ export function isHttpError(error) {
28
+ return error instanceof Error;
29
+ }
30
+ /**
31
+ * Helper to get status code from error
32
+ */
33
+ export function getErrorStatusCode(error) {
34
+ if (isHttpError(error)) {
35
+ return error.status;
36
+ }
37
+ return undefined;
38
+ }
39
+ export function defineConfig(config) {
40
+ return {
41
+ enabled: config.enabled ?? true,
42
+ requestLog: {
43
+ enabled: config.requestLog?.enabled ?? true,
44
+ excludeRoutes: config.requestLog?.excludeRoutes ?? ['/health', '/metrics'],
45
+ excludeMethods: config.requestLog?.excludeMethods ?? ['OPTIONS'],
46
+ logBody: config.requestLog?.logBody ?? false,
47
+ logQuery: config.requestLog?.logQuery ?? true,
48
+ sanitizeFields: config.requestLog?.sanitizeFields ?? ['password', 'token', 'secret'],
49
+ },
50
+ errorLog: {
51
+ enabled: config.errorLog?.enabled ?? true,
52
+ excludeStatusCodes: config.errorLog?.excludeStatusCodes ?? [404],
53
+ includeStack: config.errorLog?.includeStack ?? true,
54
+ },
55
+ auditLog: {
56
+ enabled: config.auditLog?.enabled ?? true,
57
+ },
58
+ retention: {
59
+ requestLogs: config.retention?.requestLogs ?? 30,
60
+ errorLogs: config.retention?.errorLogs ?? 90,
61
+ auditLogs: config.retention?.auditLogs ?? 365,
62
+ },
63
+ };
64
+ }
@@ -0,0 +1,40 @@
1
+ {{{
2
+ exports({
3
+ to: app.configPath('audit.ts')
4
+ })
5
+ }}}
6
+ import { defineConfig } from '@cepseudo/adonis-audit-log'
7
+
8
+ export default defineConfig({
9
+ // Enable/disable all logging
10
+ enabled: true,
11
+
12
+ // Request logging options
13
+ requestLog: {
14
+ enabled: true,
15
+ excludeRoutes: ['/health', '/metrics'],
16
+ excludeMethods: ['OPTIONS'],
17
+ logBody: false,
18
+ logQuery: true,
19
+ sanitizeFields: ['password', 'token', 'secret'],
20
+ },
21
+
22
+ // Error logging options
23
+ errorLog: {
24
+ enabled: true,
25
+ excludeStatusCodes: [404],
26
+ includeStack: true,
27
+ },
28
+
29
+ // Audit log options
30
+ auditLog: {
31
+ enabled: true,
32
+ },
33
+
34
+ // Retention (days) - for cleanup job
35
+ retention: {
36
+ requestLogs: 30,
37
+ errorLogs: 90,
38
+ auditLogs: 365,
39
+ },
40
+ })
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Path to the root directory where the stubs are stored. We use
3
+ * this path within commands and the configure hook
4
+ */
5
+ export declare const stubsRoot: string;
@@ -0,0 +1,7 @@
1
+ import { dirname } from 'node:path';
2
+ import { fileURLToPath } from 'node:url';
3
+ /**
4
+ * Path to the root directory where the stubs are stored. We use
5
+ * this path within commands and the configure hook
6
+ */
7
+ export const stubsRoot = dirname(fileURLToPath(import.meta.url));
@@ -0,0 +1,24 @@
1
+ {{#var tableName = 'audit_logs'}}
2
+ {{#var migrationName = 'create_' + tableName + '_table'}}
3
+ import { BaseSchema } from '@adonisjs/lucid/schema'
4
+
5
+ export default class extends BaseSchema {
6
+ protected tableName = '{{ tableName }}'
7
+
8
+ async up() {
9
+ this.schema.createTable(this.tableName, (table) => {
10
+ table.increments('id')
11
+ table.string('action_type').notNullable().index()
12
+ table.jsonb('metadata').notNullable().defaultTo('{}')
13
+ table.integer('user_id').unsigned().nullable().references('id').inTable('users').onDelete('SET NULL')
14
+ table.timestamp('created_at').notNullable().defaultTo(this.now())
15
+
16
+ table.index(['created_at'])
17
+ table.index(['user_id'])
18
+ })
19
+ }
20
+
21
+ async down() {
22
+ this.schema.dropTable(this.tableName)
23
+ }
24
+ }
@@ -0,0 +1,31 @@
1
+ {{#var tableName = 'error_logs'}}
2
+ {{#var migrationName = 'create_' + tableName + '_table'}}
3
+ import { BaseSchema } from '@adonisjs/lucid/schema'
4
+
5
+ export default class extends BaseSchema {
6
+ protected tableName = '{{ tableName }}'
7
+
8
+ async up() {
9
+ this.schema.createTable(this.tableName, (table) => {
10
+ table.increments('id')
11
+ table.string('error_type').notNullable()
12
+ table.text('message').notNullable()
13
+ table.text('stack').nullable()
14
+ table.string('url', 2048).nullable()
15
+ table.string('method', 10).nullable()
16
+ table.integer('status_code').nullable()
17
+ table.integer('user_id').unsigned().nullable().references('id').inTable('users').onDelete('SET NULL')
18
+ table.jsonb('context').nullable()
19
+ table.timestamp('created_at').notNullable().defaultTo(this.now())
20
+
21
+ table.index(['created_at'])
22
+ table.index(['user_id'])
23
+ table.index(['error_type'])
24
+ table.index(['status_code'])
25
+ })
26
+ }
27
+
28
+ async down() {
29
+ this.schema.dropTable(this.tableName)
30
+ }
31
+ }
@@ -0,0 +1,33 @@
1
+ {{#var tableName = 'request_logs'}}
2
+ {{#var migrationName = 'create_' + tableName + '_table'}}
3
+ import { BaseSchema } from '@adonisjs/lucid/schema'
4
+
5
+ export default class extends BaseSchema {
6
+ protected tableName = '{{ tableName }}'
7
+
8
+ async up() {
9
+ this.schema.createTable(this.tableName, (table) => {
10
+ table.increments('id')
11
+ table.string('method', 10).notNullable()
12
+ table.string('url', 2048).notNullable()
13
+ table.string('route_name').nullable()
14
+ table.integer('status_code').notNullable()
15
+ table.integer('response_time_ms').notNullable()
16
+ table.string('ip', 45).nullable()
17
+ table.string('user_agent', 512).nullable()
18
+ table.integer('user_id').unsigned().nullable().references('id').inTable('users').onDelete('SET NULL')
19
+ table.jsonb('request_body').nullable()
20
+ table.jsonb('request_query').nullable()
21
+ table.timestamp('created_at').notNullable().defaultTo(this.now())
22
+
23
+ table.index(['created_at'])
24
+ table.index(['user_id'])
25
+ table.index(['status_code'])
26
+ table.index(['method'])
27
+ })
28
+ }
29
+
30
+ async down() {
31
+ this.schema.dropTable(this.tableName)
32
+ }
33
+ }