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 { ensureSiteSchemaCompatibility, type SiteSchemaInspector } from './siteSchemaCompatibility.js';
|
||||||
import { ensureRouteGroupingSchemaCompatibility } from './routeGroupingSchemaCompatibility.js';
|
import { ensureRouteGroupingSchemaCompatibility } from './routeGroupingSchemaCompatibility.js';
|
||||||
import { ensureProxyFileSchemaCompatibility } from './proxyFileSchemaCompatibility.js';
|
import { ensureProxyFileSchemaCompatibility } from './proxyFileSchemaCompatibility.js';
|
||||||
|
import { ensureAccountTokenSchemaCompatibility } from './accountTokenSchemaCompatibility.js';
|
||||||
|
|
||||||
export type RuntimeSchemaDialect = 'sqlite' | 'mysql' | 'postgres';
|
export type RuntimeSchemaDialect = 'sqlite' | 'mysql' | 'postgres';
|
||||||
|
|
||||||
@@ -241,6 +242,7 @@ export async function ensureRuntimeDatabaseSchema(client: RuntimeSchemaClient):
|
|||||||
await ensureSiteSchemaCompatibility(createSiteSchemaInspector(client));
|
await ensureSiteSchemaCompatibility(createSiteSchemaInspector(client));
|
||||||
await ensureRouteGroupingSchemaCompatibility(createSiteSchemaInspector(client));
|
await ensureRouteGroupingSchemaCompatibility(createSiteSchemaInspector(client));
|
||||||
await ensureProxyFileSchemaCompatibility(createSiteSchemaInspector(client));
|
await ensureProxyFileSchemaCompatibility(createSiteSchemaInspector(client));
|
||||||
|
await ensureAccountTokenSchemaCompatibility(createSiteSchemaInspector(client));
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function bootstrapRuntimeDatabaseSchema(input: RuntimeSchemaConnectionInput): Promise<void> {
|
export async function bootstrapRuntimeDatabaseSchema(input: RuntimeSchemaConnectionInput): Promise<void> {
|
||||||
|
|||||||
Reference in New Issue
Block a user