Skip to content

Commit 6c9d84a

Browse files
authored
fix: add missing ip_address/user_agent columns to audit_log for pre-3.7 databases (#2361)
1 parent d3a487d commit 6c9d84a

File tree

3 files changed

+123
-7
lines changed

3 files changed

+123
-7
lines changed

src/db/migrations.test.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import { describe, it, expect } from 'vitest';
22
import { registry } from './migrations.js';
33

44
describe('migrations registry', () => {
5-
it('has all 12 migrations registered', () => {
6-
expect(registry.count()).toBe(12);
5+
it('has all 13 migrations registered', () => {
6+
expect(registry.count()).toBe(13);
77
});
88

99
it('first migration is v37 baseline', () => {
@@ -12,11 +12,11 @@ describe('migrations registry', () => {
1212
expect(all[0].name).toContain('v37_baseline');
1313
});
1414

15-
it('last migration is the auth schema alignment', () => {
15+
it('last migration is the audit_log missing columns fix', () => {
1616
const all = registry.getAll();
1717
const last = all[all.length - 1];
18-
expect(last.number).toBe(12);
19-
expect(last.name).toContain('auth');
18+
expect(last.number).toBe(13);
19+
expect(last.name).toContain('audit_log');
2020
});
2121

2222
it('migrations are sequentially numbered from 1 to 12', () => {
@@ -46,7 +46,7 @@ describe('migrations registry', () => {
4646
}
4747
});
4848

49-
it('migrations 002-012 all have settingsKey', () => {
49+
it('migrations 002-013 all have settingsKey', () => {
5050
const all = registry.getAll();
5151
for (let i = 1; i < all.length; i++) {
5252
expect(all[i].settingsKey, `Migration ${all[i].number} should have settingsKey`).toBeTruthy();

src/db/migrations.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/**
22
* Migration Registry Barrel File
33
*
4-
* Registers all 12 migrations in sequential order for use by the migration runner.
4+
* Registers all 13 migrations in sequential order for use by the migration runner.
55
* Migration 001 is the v3.7 baseline (selfIdempotent — handles its own detection).
66
* Migrations 002-011 were originally 078-087 and retain their original settingsKeys
77
* for upgrade compatibility.
@@ -24,6 +24,7 @@ import { migration as fixCustomThemesColumnsMigration, runMigration085Postgres,
2424
import { runMigration086Sqlite, runMigration086Postgres, runMigration086Mysql } from '../server/migrations/010_add_auto_distance_delete_log.js';
2525
import { migration as fixMessageNodeNumBigintMigration, runMigration087Postgres, runMigration087Mysql } from '../server/migrations/011_fix_message_nodenum_bigint.js';
2626
import { migration as authAlignMigration, runMigration012Postgres, runMigration012Mysql } from '../server/migrations/012_align_sqlite_auth_schema.js';
27+
import { migration as auditLogColumnsMigration, runMigration013Postgres, runMigration013Mysql } from '../server/migrations/013_add_audit_log_missing_columns.js';
2728

2829
// ============================================================================
2930
// Registry
@@ -154,3 +155,17 @@ registry.register({
154155
postgres: (client) => runMigration012Postgres(client),
155156
mysql: (pool) => runMigration012Mysql(pool),
156157
});
158+
159+
// ---------------------------------------------------------------------------
160+
// Migration 013: Add missing ip_address/user_agent columns to audit_log
161+
// Pre-3.7 SQLite databases may lack these columns.
162+
// ---------------------------------------------------------------------------
163+
164+
registry.register({
165+
number: 13,
166+
name: 'add_audit_log_missing_columns',
167+
settingsKey: 'migration_013_add_audit_log_missing_columns',
168+
sqlite: (db) => auditLogColumnsMigration.up(db),
169+
postgres: (client) => runMigration013Postgres(client),
170+
mysql: (pool) => runMigration013Mysql(pool),
171+
});
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/**
2+
* Migration 013: Add missing columns to audit_log for pre-3.7 SQLite databases
3+
*
4+
* The Drizzle schema expects ip_address and user_agent columns on audit_log,
5+
* but databases created before the v3.7 baseline may not have them.
6+
* Migration 012 added username but missed these two.
7+
*
8+
* PostgreSQL/MySQL baselines already include these columns, so those are no-ops.
9+
*/
10+
import type { Database } from 'better-sqlite3';
11+
import { logger } from '../../utils/logger.js';
12+
13+
// ============ SQLite ============
14+
15+
export const migration = {
16+
up: (db: Database): void => {
17+
logger.info('Running migration 013 (SQLite): Adding missing audit_log columns...');
18+
19+
// 1. Add ip_address to audit_log
20+
try {
21+
db.exec('ALTER TABLE audit_log ADD COLUMN ip_address TEXT');
22+
logger.debug('Added ip_address column to audit_log');
23+
} catch (e: any) {
24+
if (e.message?.includes('duplicate column')) {
25+
logger.debug('audit_log.ip_address already exists, skipping');
26+
} else {
27+
logger.warn('Could not add ip_address to audit_log:', e.message);
28+
}
29+
}
30+
31+
// 2. Add user_agent to audit_log
32+
try {
33+
db.exec('ALTER TABLE audit_log ADD COLUMN user_agent TEXT');
34+
logger.debug('Added user_agent column to audit_log');
35+
} catch (e: any) {
36+
if (e.message?.includes('duplicate column')) {
37+
logger.debug('audit_log.user_agent already exists, skipping');
38+
} else {
39+
logger.warn('Could not add user_agent to audit_log:', e.message);
40+
}
41+
}
42+
43+
logger.info('Migration 013 complete (SQLite): audit_log columns aligned');
44+
},
45+
46+
down: (_db: Database): void => {
47+
logger.debug('Migration 013 down: Not implemented (destructive column drops)');
48+
}
49+
};
50+
51+
// ============ PostgreSQL ============
52+
53+
export async function runMigration013Postgres(client: import('pg').PoolClient): Promise<void> {
54+
logger.info('Running migration 013 (PostgreSQL): Ensuring audit_log columns exist...');
55+
56+
try {
57+
await client.query('ALTER TABLE audit_log ADD COLUMN IF NOT EXISTS "ipAddress" TEXT');
58+
await client.query('ALTER TABLE audit_log ADD COLUMN IF NOT EXISTS "userAgent" TEXT');
59+
logger.debug('Ensured ipAddress/userAgent exist on audit_log');
60+
} catch (error: any) {
61+
logger.error('Migration 013 (PostgreSQL) failed:', error.message);
62+
throw error;
63+
}
64+
65+
logger.info('Migration 013 complete (PostgreSQL): audit_log columns aligned');
66+
}
67+
68+
// ============ MySQL ============
69+
70+
export async function runMigration013Mysql(pool: import('mysql2/promise').Pool): Promise<void> {
71+
logger.info('Running migration 013 (MySQL): Ensuring audit_log columns exist...');
72+
73+
try {
74+
const [ipRows] = await pool.query(`
75+
SELECT COLUMN_NAME FROM information_schema.COLUMNS
76+
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'audit_log' AND COLUMN_NAME = 'ipAddress'
77+
`);
78+
if (!Array.isArray(ipRows) || ipRows.length === 0) {
79+
await pool.query('ALTER TABLE audit_log ADD COLUMN ipAddress TEXT');
80+
logger.debug('Added ipAddress to audit_log');
81+
} else {
82+
logger.debug('audit_log.ipAddress already exists, skipping');
83+
}
84+
85+
const [uaRows] = await pool.query(`
86+
SELECT COLUMN_NAME FROM information_schema.COLUMNS
87+
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'audit_log' AND COLUMN_NAME = 'userAgent'
88+
`);
89+
if (!Array.isArray(uaRows) || uaRows.length === 0) {
90+
await pool.query('ALTER TABLE audit_log ADD COLUMN userAgent TEXT');
91+
logger.debug('Added userAgent to audit_log');
92+
} else {
93+
logger.debug('audit_log.userAgent already exists, skipping');
94+
}
95+
} catch (error: any) {
96+
logger.error('Migration 013 (MySQL) failed:', error.message);
97+
throw error;
98+
}
99+
100+
logger.info('Migration 013 complete (MySQL): audit_log columns aligned');
101+
}

0 commit comments

Comments
 (0)