Files
metapi/src/server/db/siteSchemaCompatibility.ts
T
cita ed05b37626 feat: enhance TokenRoutes with edit functionality and route visibility management
- Added editing capabilities for route management in TokenRoutes, allowing users to modify existing routes.
- Introduced a reset function for the route form to clear inputs after adding or editing routes.
- Implemented a visibility filter for routes, ensuring exact routes covered by group routes are hidden from the main list.
- Updated tests to cover new functionalities, including editing routes and verifying visibility logic.
- Created a new helper function to build a list of visible routes based on defined criteria.
- Enhanced schema compatibility checks for site configurations, ensuring necessary columns are added to the database.
2026-03-07 19:56:40 +08:00

102 lines
3.6 KiB
TypeScript

export type SiteSchemaDialect = 'sqlite' | 'mysql' | 'postgres';
export interface SiteSchemaInspector {
dialect: SiteSchemaDialect;
tableExists(table: string): Promise<boolean>;
columnExists(table: string, column: string): Promise<boolean>;
execute(sqlText: string): Promise<void>;
}
type SiteColumnCompatibilitySpec = {
column: string;
addSql: Record<SiteSchemaDialect, string>;
normalizeSql?: Record<SiteSchemaDialect, string>;
};
const SITE_COLUMN_COMPATIBILITY_SPECS: SiteColumnCompatibilitySpec[] = [
{
column: 'proxy_url',
addSql: {
sqlite: 'ALTER TABLE sites ADD COLUMN proxy_url text;',
mysql: 'ALTER TABLE `sites` ADD COLUMN `proxy_url` TEXT NULL',
postgres: 'ALTER TABLE "sites" ADD COLUMN "proxy_url" TEXT',
},
},
{
column: 'use_system_proxy',
addSql: {
sqlite: 'ALTER TABLE sites ADD COLUMN use_system_proxy integer DEFAULT 0;',
mysql: 'ALTER TABLE `sites` ADD COLUMN `use_system_proxy` BOOLEAN DEFAULT FALSE',
postgres: 'ALTER TABLE "sites" ADD COLUMN "use_system_proxy" BOOLEAN DEFAULT FALSE',
},
normalizeSql: {
sqlite: 'UPDATE sites SET use_system_proxy = 0 WHERE use_system_proxy IS NULL;',
mysql: 'UPDATE `sites` SET `use_system_proxy` = FALSE WHERE `use_system_proxy` IS NULL',
postgres: 'UPDATE "sites" SET "use_system_proxy" = FALSE WHERE "use_system_proxy" IS NULL',
},
},
{
column: 'external_checkin_url',
addSql: {
sqlite: 'ALTER TABLE sites ADD COLUMN external_checkin_url text;',
mysql: 'ALTER TABLE `sites` ADD COLUMN `external_checkin_url` TEXT NULL',
postgres: 'ALTER TABLE "sites" ADD COLUMN "external_checkin_url" TEXT',
},
},
{
column: 'global_weight',
addSql: {
sqlite: 'ALTER TABLE sites ADD COLUMN global_weight real DEFAULT 1;',
mysql: 'ALTER TABLE `sites` ADD COLUMN `global_weight` DOUBLE DEFAULT 1',
postgres: 'ALTER TABLE "sites" ADD COLUMN "global_weight" DOUBLE PRECISION DEFAULT 1',
},
normalizeSql: {
sqlite: 'UPDATE sites SET global_weight = 1 WHERE global_weight IS NULL OR global_weight <= 0;',
mysql: 'UPDATE `sites` SET `global_weight` = 1 WHERE `global_weight` IS NULL OR `global_weight` <= 0',
postgres: 'UPDATE "sites" SET "global_weight" = 1 WHERE "global_weight" IS NULL OR "global_weight" <= 0',
},
},
];
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: SiteSchemaInspector, sqlText: string): Promise<void> {
try {
await inspector.execute(sqlText);
} catch (error) {
if (!isDuplicateColumnError(error)) {
throw error;
}
}
}
export async function ensureSiteSchemaCompatibility(inspector: SiteSchemaInspector): Promise<void> {
const hasSitesTable = await inspector.tableExists('sites');
if (!hasSitesTable) {
return;
}
for (const spec of SITE_COLUMN_COMPATIBILITY_SPECS) {
const hasColumn = await inspector.columnExists('sites', spec.column);
if (!hasColumn) {
await executeAddColumn(inspector, spec.addSql[inspector.dialect]);
}
if (spec.normalizeSql) {
await inspector.execute(spec.normalizeSql[inspector.dialect]);
}
}
}