forked from boos/BoosAPI
fix(db): add schema compatibility for account_tokens.token_group column
Existing databases created before the token_group column was added would fail on data import. Add accountTokenSchemaCompatibility module to ALTER TABLE add the missing column, following the established pattern.
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
export type AccountTokenSchemaDialect = 'sqlite' | 'mysql' | 'postgres';
|
||||
|
||||
export interface AccountTokenSchemaInspector {
|
||||
dialect: AccountTokenSchemaDialect;
|
||||
tableExists(table: string): Promise<boolean>;
|
||||
columnExists(table: string, column: string): Promise<boolean>;
|
||||
execute(sqlText: string): Promise<void>;
|
||||
}
|
||||
|
||||
type AccountTokenColumnCompatibilitySpec = {
|
||||
table: 'account_tokens';
|
||||
column: string;
|
||||
addSql: Record<AccountTokenSchemaDialect, string>;
|
||||
};
|
||||
|
||||
const ACCOUNT_TOKEN_COLUMN_COMPATIBILITY_SPECS: AccountTokenColumnCompatibilitySpec[] = [
|
||||
{
|
||||
table: 'account_tokens',
|
||||
column: 'token_group',
|
||||
addSql: {
|
||||
sqlite: 'ALTER TABLE account_tokens ADD COLUMN token_group text;',
|
||||
mysql: 'ALTER TABLE `account_tokens` ADD COLUMN `token_group` TEXT NULL',
|
||||
postgres: 'ALTER TABLE "account_tokens" ADD COLUMN "token_group" TEXT',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
function normalizeSchemaErrorMessage(error: unknown): string {
|
||||
if (typeof error === 'object' && error && 'message' in error) {
|
||||
return String((error as { message?: unknown }).message || '');
|
||||
}
|
||||
return String(error || '');
|
||||
}
|
||||
|
||||
function isDuplicateColumnError(error: unknown): boolean {
|
||||
const lowered = normalizeSchemaErrorMessage(error).toLowerCase();
|
||||
return lowered.includes('duplicate column')
|
||||
|| lowered.includes('already exists')
|
||||
|| lowered.includes('duplicate column name');
|
||||
}
|
||||
|
||||
async function executeAddColumn(inspector: AccountTokenSchemaInspector, sqlText: string): Promise<void> {
|
||||
try {
|
||||
await inspector.execute(sqlText);
|
||||
} catch (error) {
|
||||
if (!isDuplicateColumnError(error)) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function ensureAccountTokenSchemaCompatibility(inspector: AccountTokenSchemaInspector): Promise<void> {
|
||||
for (const spec of ACCOUNT_TOKEN_COLUMN_COMPATIBILITY_SPECS) {
|
||||
const hasTable = await inspector.tableExists(spec.table);
|
||||
if (!hasTable) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const hasColumn = await inspector.columnExists(spec.table, spec.column);
|
||||
if (!hasColumn) {
|
||||
await executeAddColumn(inspector, spec.addSql[inspector.dialect]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import { dirname, resolve } from 'node:path';
|
||||
import { ensureSiteSchemaCompatibility, type SiteSchemaInspector } from './siteSchemaCompatibility.js';
|
||||
import { ensureRouteGroupingSchemaCompatibility } from './routeGroupingSchemaCompatibility.js';
|
||||
import { ensureProxyFileSchemaCompatibility } from './proxyFileSchemaCompatibility.js';
|
||||
import { ensureAccountTokenSchemaCompatibility } from './accountTokenSchemaCompatibility.js';
|
||||
|
||||
export type RuntimeSchemaDialect = 'sqlite' | 'mysql' | 'postgres';
|
||||
|
||||
@@ -241,6 +242,7 @@ export async function ensureRuntimeDatabaseSchema(client: RuntimeSchemaClient):
|
||||
await ensureSiteSchemaCompatibility(createSiteSchemaInspector(client));
|
||||
await ensureRouteGroupingSchemaCompatibility(createSiteSchemaInspector(client));
|
||||
await ensureProxyFileSchemaCompatibility(createSiteSchemaInspector(client));
|
||||
await ensureAccountTokenSchemaCompatibility(createSiteSchemaInspector(client));
|
||||
}
|
||||
|
||||
export async function bootstrapRuntimeDatabaseSchema(input: RuntimeSchemaConnectionInput): Promise<void> {
|
||||
|
||||
Reference in New Issue
Block a user