@hacimertgokhan/next-audit 1.0.2 → 1.0.4
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/decorators/audit.js +4 -0
- package/dist/lib/mysql.js +109 -47
- package/package.json +1 -1
- package/src/decorators/audit.ts +19 -15
- package/src/lib/mysql.ts +132 -58
package/dist/decorators/audit.js
CHANGED
|
@@ -71,6 +71,9 @@ function Audit(options) {
|
|
|
71
71
|
const duration = Date.now() - startTime;
|
|
72
72
|
const config = (0, config_1.loadConfig)();
|
|
73
73
|
if (config) {
|
|
74
|
+
const clientIp = req?.headers.get('x-forwarded-for')?.split(',')[0] ||
|
|
75
|
+
req?.ip ||
|
|
76
|
+
req?.socket?.remoteAddress || '';
|
|
74
77
|
const entry = {
|
|
75
78
|
timestamp: new Date(),
|
|
76
79
|
method,
|
|
@@ -79,6 +82,7 @@ function Audit(options) {
|
|
|
79
82
|
status,
|
|
80
83
|
duration_ms: duration,
|
|
81
84
|
user_agent: userAgent,
|
|
85
|
+
ip: clientIp,
|
|
82
86
|
old_value: oldData,
|
|
83
87
|
new_value: newData,
|
|
84
88
|
};
|
package/dist/lib/mysql.js
CHANGED
|
@@ -3,78 +3,140 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.getDbConnection = getDbConnection;
|
|
4
4
|
exports.saveAuditLog = saveAuditLog;
|
|
5
5
|
const promise_1 = require("mysql2/promise");
|
|
6
|
-
|
|
6
|
+
// Use global to persist pool across HMR reloads in Next.js development
|
|
7
|
+
const globalForAudit = global;
|
|
7
8
|
async function getDbConnection(config) {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
const configKey = JSON.stringify(config.database);
|
|
10
|
+
if (globalForAudit._nextAuditPool && globalForAudit._nextAuditConfig === configKey) {
|
|
11
|
+
return globalForAudit._nextAuditPool;
|
|
12
|
+
}
|
|
13
|
+
// Cleanup old pool if config changed
|
|
14
|
+
if (globalForAudit._nextAuditPool) {
|
|
15
|
+
await globalForAudit._nextAuditPool.end().catch(() => { });
|
|
16
|
+
globalForAudit._nextAuditTableInitialized = false;
|
|
17
|
+
}
|
|
18
|
+
const { database: dbConfig } = config;
|
|
19
|
+
const poolOptions = {
|
|
20
|
+
waitForConnections: true,
|
|
21
|
+
connectionLimit: 10,
|
|
22
|
+
maxIdle: 10,
|
|
23
|
+
idleTimeout: 60000,
|
|
24
|
+
queueLimit: 0,
|
|
25
|
+
enableKeepAlive: true,
|
|
26
|
+
keepAliveInitialDelay: 10000,
|
|
27
|
+
connectTimeout: 10000,
|
|
28
|
+
};
|
|
29
|
+
let pool;
|
|
11
30
|
if (dbConfig.url) {
|
|
12
|
-
|
|
31
|
+
// Try to parse URL to avoid driver quirks with some IP formats
|
|
32
|
+
try {
|
|
33
|
+
const mysqlUrl = new URL(dbConfig.url.replace('mysql://', 'http://'));
|
|
34
|
+
pool = (0, promise_1.createPool)({
|
|
35
|
+
...poolOptions,
|
|
36
|
+
host: mysqlUrl.hostname,
|
|
37
|
+
port: parseInt(mysqlUrl.port) || 3306,
|
|
38
|
+
user: mysqlUrl.username,
|
|
39
|
+
password: mysqlUrl.password,
|
|
40
|
+
database: mysqlUrl.pathname.substring(1),
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
catch (e) {
|
|
44
|
+
pool = (0, promise_1.createPool)(dbConfig.url);
|
|
45
|
+
}
|
|
13
46
|
}
|
|
14
47
|
else {
|
|
15
48
|
pool = (0, promise_1.createPool)({
|
|
49
|
+
...poolOptions,
|
|
16
50
|
host: dbConfig.host,
|
|
17
51
|
user: dbConfig.user,
|
|
18
52
|
password: dbConfig.password,
|
|
19
53
|
database: dbConfig.database,
|
|
20
|
-
port: dbConfig.port,
|
|
21
|
-
waitForConnections: true,
|
|
22
|
-
connectionLimit: 10,
|
|
23
|
-
queueLimit: 0,
|
|
54
|
+
port: dbConfig.port || 3306,
|
|
24
55
|
});
|
|
25
56
|
}
|
|
57
|
+
// Explicit error listener to clear global pool on fatal errors
|
|
58
|
+
pool.on('error', (err) => {
|
|
59
|
+
if (err.code === 'PROTOCOL_CONNECTION_LOST' || err.fatal) {
|
|
60
|
+
globalForAudit._nextAuditPool = undefined;
|
|
61
|
+
globalForAudit._nextAuditTableInitialized = false;
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
globalForAudit._nextAuditPool = pool;
|
|
65
|
+
globalForAudit._nextAuditConfig = configKey;
|
|
66
|
+
globalForAudit._nextAuditTableInitialized = false;
|
|
26
67
|
return pool;
|
|
27
68
|
}
|
|
69
|
+
async function ensureTableExists(connection, tableName) {
|
|
70
|
+
if (globalForAudit._nextAuditTableInitialized)
|
|
71
|
+
return;
|
|
72
|
+
await connection.query(`
|
|
73
|
+
CREATE TABLE IF NOT EXISTS \`${tableName}\` (
|
|
74
|
+
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
75
|
+
timestamp DATETIME,
|
|
76
|
+
method VARCHAR(10),
|
|
77
|
+
url VARCHAR(255),
|
|
78
|
+
user_id VARCHAR(50),
|
|
79
|
+
entity VARCHAR(50),
|
|
80
|
+
entity_id VARCHAR(50),
|
|
81
|
+
old_value JSON,
|
|
82
|
+
new_value JSON,
|
|
83
|
+
action_type VARCHAR(50),
|
|
84
|
+
status INT,
|
|
85
|
+
duration_ms INT,
|
|
86
|
+
ip VARCHAR(45),
|
|
87
|
+
user_agent VARCHAR(255)
|
|
88
|
+
)
|
|
89
|
+
`);
|
|
90
|
+
globalForAudit._nextAuditTableInitialized = true;
|
|
91
|
+
}
|
|
28
92
|
async function saveAuditLog(entry, config) {
|
|
93
|
+
if (config.testMode && config.debug) {
|
|
94
|
+
console.log(`[Next-Audit] (Test Mode) Log: ${entry.method} ${entry.url}`);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
let connection = null;
|
|
29
98
|
try {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
const
|
|
37
|
-
const tableName = config.tableName || 'audit_logs';
|
|
38
|
-
await db.query(`
|
|
39
|
-
CREATE TABLE IF NOT EXISTS \`${tableName}\` (
|
|
40
|
-
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
41
|
-
timestamp DATETIME,
|
|
42
|
-
method VARCHAR(10),
|
|
43
|
-
url VARCHAR(255),
|
|
44
|
-
user_id VARCHAR(50),
|
|
45
|
-
entity VARCHAR(50),
|
|
46
|
-
entity_id VARCHAR(50),
|
|
47
|
-
old_value JSON,
|
|
48
|
-
new_value JSON,
|
|
49
|
-
action_type VARCHAR(50),
|
|
50
|
-
status INT,
|
|
51
|
-
duration_ms INT,
|
|
52
|
-
ip VARCHAR(45),
|
|
53
|
-
user_agent VARCHAR(255)
|
|
54
|
-
)
|
|
55
|
-
`);
|
|
56
|
-
await db.query(`INSERT INTO \`${tableName}\`
|
|
57
|
-
(timestamp, method, url, user_id, entity, entity_id, old_value, new_value, action_type, status, duration_ms, ip, user_agent)
|
|
58
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
99
|
+
const pool = await getDbConnection(config);
|
|
100
|
+
// Checkout connection from pool
|
|
101
|
+
connection = await pool.getConnection();
|
|
102
|
+
// One-time table initialization per pool lifecycle
|
|
103
|
+
await ensureTableExists(connection, config.tableName || 'audit_logs');
|
|
104
|
+
// Prepare values - ensure JSON is stringified or null
|
|
105
|
+
const values = [
|
|
59
106
|
entry.timestamp,
|
|
60
107
|
entry.method,
|
|
61
108
|
entry.url,
|
|
62
|
-
entry.user_id,
|
|
63
|
-
entry.entity,
|
|
64
|
-
entry.entity_id,
|
|
65
|
-
JSON.stringify(entry.old_value),
|
|
66
|
-
JSON.stringify(entry.new_value),
|
|
109
|
+
entry.user_id || null,
|
|
110
|
+
entry.entity || null,
|
|
111
|
+
entry.entity_id || null,
|
|
112
|
+
entry.old_value ? JSON.stringify(entry.old_value) : null,
|
|
113
|
+
entry.new_value ? JSON.stringify(entry.new_value) : null,
|
|
67
114
|
entry.action_type,
|
|
68
115
|
entry.status,
|
|
69
116
|
entry.duration_ms,
|
|
70
|
-
entry.ip,
|
|
71
|
-
entry.user_agent
|
|
72
|
-
]
|
|
117
|
+
entry.ip || null,
|
|
118
|
+
entry.user_agent || null
|
|
119
|
+
];
|
|
120
|
+
const tableName = config.tableName || 'audit_logs';
|
|
121
|
+
await connection.query(`INSERT INTO \`${tableName}\`
|
|
122
|
+
(timestamp, method, url, user_id, entity, entity_id, old_value, new_value, action_type, status, duration_ms, ip, user_agent)
|
|
123
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, values);
|
|
73
124
|
if (config.debug) {
|
|
74
125
|
console.log(`[Next-Audit] Log saved: ${entry.method} ${entry.url}`);
|
|
75
126
|
}
|
|
76
127
|
}
|
|
77
128
|
catch (error) {
|
|
78
|
-
|
|
129
|
+
const dbHost = config.database.url || config.database.host || 'unknown';
|
|
130
|
+
console.error(`[Next-Audit] Failed to save audit log to ${dbHost}:`, error.message || error);
|
|
131
|
+
// If it's a connection error, clear the pool so it recreates next time
|
|
132
|
+
if (error.code === 'PROTOCOL_CONNECTION_LOST' || error.code === 'ETIMEDOUT' || error.fatal) {
|
|
133
|
+
globalForAudit._nextAuditPool = undefined;
|
|
134
|
+
globalForAudit._nextAuditTableInitialized = false;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
finally {
|
|
138
|
+
if (connection) {
|
|
139
|
+
connection.release();
|
|
140
|
+
}
|
|
79
141
|
}
|
|
80
142
|
}
|
package/package.json
CHANGED
package/src/decorators/audit.ts
CHANGED
|
@@ -17,7 +17,7 @@ export function Audit(options?: { actionType?: string }) {
|
|
|
17
17
|
let req: NextRequest | undefined;
|
|
18
18
|
let context: any;
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
|
|
21
21
|
for (const arg of args) {
|
|
22
22
|
if (arg instanceof Request || (arg && arg.constructor && arg.constructor.name === 'NextRequest')) {
|
|
23
23
|
req = arg as NextRequest;
|
|
@@ -25,16 +25,16 @@ export function Audit(options?: { actionType?: string }) {
|
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
|
|
29
29
|
const url = req?.url || '';
|
|
30
30
|
const method = req?.method || 'UNKNOWN';
|
|
31
31
|
const userAgent = req?.headers.get('user-agent') || '';
|
|
32
|
-
|
|
33
32
|
|
|
34
|
-
|
|
33
|
+
|
|
34
|
+
let oldData = null;
|
|
35
35
|
let newData = null;
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
|
|
38
38
|
let result;
|
|
39
39
|
let status = 200;
|
|
40
40
|
let errorOccurred = false;
|
|
@@ -42,33 +42,33 @@ export function Audit(options?: { actionType?: string }) {
|
|
|
42
42
|
try {
|
|
43
43
|
result = await originalMethod.apply(this, args);
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
|
|
46
46
|
if (result instanceof Response) {
|
|
47
47
|
status = result.status;
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
|
|
51
51
|
try {
|
|
52
52
|
const clone = result.clone();
|
|
53
53
|
const bodyText = await clone.text();
|
|
54
54
|
if (bodyText) {
|
|
55
55
|
try {
|
|
56
56
|
const json = JSON.parse(bodyText);
|
|
57
|
-
|
|
57
|
+
|
|
58
58
|
if (method === 'DELETE') {
|
|
59
59
|
oldData = json;
|
|
60
60
|
} else if (method === 'POST' || method === 'PUT' || method === 'PATCH') {
|
|
61
61
|
newData = json;
|
|
62
62
|
}
|
|
63
63
|
} catch (e) {
|
|
64
|
-
|
|
64
|
+
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
67
|
} catch (e) {
|
|
68
|
-
|
|
68
|
+
|
|
69
69
|
}
|
|
70
70
|
} else {
|
|
71
|
-
|
|
71
|
+
|
|
72
72
|
if (method === 'DELETE') {
|
|
73
73
|
oldData = result;
|
|
74
74
|
} else {
|
|
@@ -79,7 +79,7 @@ export function Audit(options?: { actionType?: string }) {
|
|
|
79
79
|
} catch (error: any) {
|
|
80
80
|
errorOccurred = true;
|
|
81
81
|
status = 500;
|
|
82
|
-
|
|
82
|
+
|
|
83
83
|
if (error.status) status = error.status;
|
|
84
84
|
if (error.statusCode) status = error.statusCode;
|
|
85
85
|
throw error;
|
|
@@ -88,6 +88,10 @@ export function Audit(options?: { actionType?: string }) {
|
|
|
88
88
|
const config = loadConfig();
|
|
89
89
|
|
|
90
90
|
if (config) {
|
|
91
|
+
const clientIp = req?.headers.get('x-forwarded-for')?.split(',')[0] ||
|
|
92
|
+
(req as any)?.ip ||
|
|
93
|
+
(req as any)?.socket?.remoteAddress || '';
|
|
94
|
+
|
|
91
95
|
const entry: AuditLogEntry = {
|
|
92
96
|
timestamp: new Date(),
|
|
93
97
|
method,
|
|
@@ -96,9 +100,9 @@ export function Audit(options?: { actionType?: string }) {
|
|
|
96
100
|
status,
|
|
97
101
|
duration_ms: duration,
|
|
98
102
|
user_agent: userAgent,
|
|
103
|
+
ip: clientIp,
|
|
99
104
|
old_value: oldData,
|
|
100
105
|
new_value: newData,
|
|
101
|
-
|
|
102
106
|
};
|
|
103
107
|
|
|
104
108
|
saveAuditLog(entry, config).catch(e => console.error('Audit Log Error', e));
|
package/src/lib/mysql.ts
CHANGED
|
@@ -1,88 +1,162 @@
|
|
|
1
|
-
import { createPool, Pool } from 'mysql2/promise';
|
|
1
|
+
import { createPool, Pool, PoolConnection } from 'mysql2/promise';
|
|
2
2
|
import { AuditConfig, AuditLogEntry } from '../types';
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
// Use global to persist pool across HMR reloads in Next.js development
|
|
5
|
+
const globalForAudit = global as unknown as {
|
|
6
|
+
_nextAuditPool?: Pool;
|
|
7
|
+
_nextAuditConfig?: string;
|
|
8
|
+
_nextAuditTableInitialized?: boolean;
|
|
9
|
+
};
|
|
5
10
|
|
|
6
|
-
export async function getDbConnection(config: AuditConfig) {
|
|
7
|
-
|
|
11
|
+
export async function getDbConnection(config: AuditConfig): Promise<Pool> {
|
|
12
|
+
const configKey = JSON.stringify(config.database);
|
|
13
|
+
|
|
14
|
+
if (globalForAudit._nextAuditPool && globalForAudit._nextAuditConfig === configKey) {
|
|
15
|
+
return globalForAudit._nextAuditPool;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Cleanup old pool if config changed
|
|
19
|
+
if (globalForAudit._nextAuditPool) {
|
|
20
|
+
await globalForAudit._nextAuditPool.end().catch(() => { });
|
|
21
|
+
globalForAudit._nextAuditTableInitialized = false;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const { database: dbConfig } = config;
|
|
25
|
+
const poolOptions: any = {
|
|
26
|
+
waitForConnections: true,
|
|
27
|
+
connectionLimit: 10,
|
|
28
|
+
maxIdle: 10,
|
|
29
|
+
idleTimeout: 60000,
|
|
30
|
+
queueLimit: 0,
|
|
31
|
+
enableKeepAlive: true,
|
|
32
|
+
keepAliveInitialDelay: 10000,
|
|
33
|
+
connectTimeout: 10000,
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
let pool: Pool;
|
|
8
37
|
|
|
9
|
-
const dbConfig = config.database;
|
|
10
38
|
if (dbConfig.url) {
|
|
11
|
-
|
|
39
|
+
// Try to parse URL to avoid driver quirks with some IP formats
|
|
40
|
+
try {
|
|
41
|
+
const mysqlUrl = new URL(dbConfig.url.replace('mysql://', 'http://'));
|
|
42
|
+
pool = createPool({
|
|
43
|
+
...poolOptions,
|
|
44
|
+
host: mysqlUrl.hostname,
|
|
45
|
+
port: parseInt(mysqlUrl.port) || 3306,
|
|
46
|
+
user: mysqlUrl.username,
|
|
47
|
+
password: mysqlUrl.password,
|
|
48
|
+
database: mysqlUrl.pathname.substring(1),
|
|
49
|
+
});
|
|
50
|
+
} catch (e) {
|
|
51
|
+
pool = createPool(dbConfig.url);
|
|
52
|
+
}
|
|
12
53
|
} else {
|
|
13
54
|
pool = createPool({
|
|
55
|
+
...poolOptions,
|
|
14
56
|
host: dbConfig.host,
|
|
15
57
|
user: dbConfig.user,
|
|
16
58
|
password: dbConfig.password,
|
|
17
59
|
database: dbConfig.database,
|
|
18
|
-
port: dbConfig.port,
|
|
19
|
-
waitForConnections: true,
|
|
20
|
-
connectionLimit: 10,
|
|
21
|
-
queueLimit: 0,
|
|
60
|
+
port: dbConfig.port || 3306,
|
|
22
61
|
});
|
|
23
62
|
}
|
|
63
|
+
|
|
64
|
+
// Explicit error listener to clear global pool on fatal errors
|
|
65
|
+
(pool as any).on('error', (err: any) => {
|
|
66
|
+
if (err.code === 'PROTOCOL_CONNECTION_LOST' || err.fatal) {
|
|
67
|
+
globalForAudit._nextAuditPool = undefined;
|
|
68
|
+
globalForAudit._nextAuditTableInitialized = false;
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
globalForAudit._nextAuditPool = pool;
|
|
73
|
+
globalForAudit._nextAuditConfig = configKey;
|
|
74
|
+
globalForAudit._nextAuditTableInitialized = false;
|
|
75
|
+
|
|
24
76
|
return pool;
|
|
25
77
|
}
|
|
26
78
|
|
|
79
|
+
async function ensureTableExists(connection: PoolConnection, tableName: string) {
|
|
80
|
+
if (globalForAudit._nextAuditTableInitialized) return;
|
|
81
|
+
|
|
82
|
+
await connection.query(`
|
|
83
|
+
CREATE TABLE IF NOT EXISTS \`${tableName}\` (
|
|
84
|
+
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
85
|
+
timestamp DATETIME,
|
|
86
|
+
method VARCHAR(10),
|
|
87
|
+
url VARCHAR(255),
|
|
88
|
+
user_id VARCHAR(50),
|
|
89
|
+
entity VARCHAR(50),
|
|
90
|
+
entity_id VARCHAR(50),
|
|
91
|
+
old_value JSON,
|
|
92
|
+
new_value JSON,
|
|
93
|
+
action_type VARCHAR(50),
|
|
94
|
+
status INT,
|
|
95
|
+
duration_ms INT,
|
|
96
|
+
ip VARCHAR(45),
|
|
97
|
+
user_agent VARCHAR(255)
|
|
98
|
+
)
|
|
99
|
+
`);
|
|
100
|
+
|
|
101
|
+
globalForAudit._nextAuditTableInitialized = true;
|
|
102
|
+
}
|
|
103
|
+
|
|
27
104
|
export async function saveAuditLog(entry: AuditLogEntry, config: AuditConfig) {
|
|
105
|
+
if (config.testMode && config.debug) {
|
|
106
|
+
console.log(`[Next-Audit] (Test Mode) Log: ${entry.method} ${entry.url}`);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
let connection: PoolConnection | null = null;
|
|
28
111
|
try {
|
|
29
|
-
|
|
30
|
-
if (config.debug) {
|
|
31
|
-
console.log(`[Next-Audit] (Test Mode) Log would be saved: ${entry.method} ${entry.url}`);
|
|
32
|
-
}
|
|
33
|
-
return;
|
|
34
|
-
}
|
|
112
|
+
const pool = await getDbConnection(config);
|
|
35
113
|
|
|
36
|
-
|
|
37
|
-
|
|
114
|
+
// Checkout connection from pool
|
|
115
|
+
connection = await pool.getConnection();
|
|
38
116
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
`);
|
|
117
|
+
// One-time table initialization per pool lifecycle
|
|
118
|
+
await ensureTableExists(connection, config.tableName || 'audit_logs');
|
|
119
|
+
|
|
120
|
+
// Prepare values - ensure JSON is stringified or null
|
|
121
|
+
const values = [
|
|
122
|
+
entry.timestamp,
|
|
123
|
+
entry.method,
|
|
124
|
+
entry.url,
|
|
125
|
+
entry.user_id || null,
|
|
126
|
+
entry.entity || null,
|
|
127
|
+
entry.entity_id || null,
|
|
128
|
+
entry.old_value ? JSON.stringify(entry.old_value) : null,
|
|
129
|
+
entry.new_value ? JSON.stringify(entry.new_value) : null,
|
|
130
|
+
entry.action_type,
|
|
131
|
+
entry.status,
|
|
132
|
+
entry.duration_ms,
|
|
133
|
+
entry.ip || null,
|
|
134
|
+
entry.user_agent || null
|
|
135
|
+
];
|
|
59
136
|
|
|
60
|
-
|
|
137
|
+
const tableName = config.tableName || 'audit_logs';
|
|
138
|
+
await connection.query(
|
|
61
139
|
`INSERT INTO \`${tableName}\`
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
entry.timestamp,
|
|
66
|
-
entry.method,
|
|
67
|
-
entry.url,
|
|
68
|
-
entry.user_id,
|
|
69
|
-
entry.entity,
|
|
70
|
-
entry.entity_id,
|
|
71
|
-
JSON.stringify(entry.old_value),
|
|
72
|
-
JSON.stringify(entry.new_value),
|
|
73
|
-
entry.action_type,
|
|
74
|
-
entry.status,
|
|
75
|
-
entry.duration_ms,
|
|
76
|
-
entry.ip,
|
|
77
|
-
entry.user_agent
|
|
78
|
-
]
|
|
140
|
+
(timestamp, method, url, user_id, entity, entity_id, old_value, new_value, action_type, status, duration_ms, ip, user_agent)
|
|
141
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
142
|
+
values
|
|
79
143
|
);
|
|
80
144
|
|
|
81
145
|
if (config.debug) {
|
|
82
146
|
console.log(`[Next-Audit] Log saved: ${entry.method} ${entry.url}`);
|
|
83
147
|
}
|
|
148
|
+
} catch (error: any) {
|
|
149
|
+
const dbHost = config.database.url || config.database.host || 'unknown';
|
|
150
|
+
console.error(`[Next-Audit] Failed to save audit log to ${dbHost}:`, error.message || error);
|
|
84
151
|
|
|
85
|
-
|
|
86
|
-
|
|
152
|
+
// If it's a connection error, clear the pool so it recreates next time
|
|
153
|
+
if (error.code === 'PROTOCOL_CONNECTION_LOST' || error.code === 'ETIMEDOUT' || error.fatal) {
|
|
154
|
+
globalForAudit._nextAuditPool = undefined;
|
|
155
|
+
globalForAudit._nextAuditTableInitialized = false;
|
|
156
|
+
}
|
|
157
|
+
} finally {
|
|
158
|
+
if (connection) {
|
|
159
|
+
connection.release();
|
|
160
|
+
}
|
|
87
161
|
}
|
|
88
162
|
}
|