feat: add site_disabled_models and proxy tasks to database schema

- Implemented site_disabled_models table with necessary SQL commands for SQLite, MySQL, and PostgreSQL.
- Updated ensureSiteSchemaCompatibility to create site_disabled_models if it doesn't exist.
- Enhanced database migration service to include site_disabled_models, proxy_video_tasks, and proxy_files in migration statements.
- Created shared index compatibility for new tables and ensured existing indexes are maintained.
- Added tests for schema parity to ensure consistency between runtime schema and defined schema.
This commit is contained in:
ciat-777
2026-03-14 21:16:43 +08:00
parent 0c109278b9
commit b834a9bef8
10 changed files with 757 additions and 7 deletions
@@ -175,6 +175,7 @@ describe('databaseMigrationService', () => {
customHeaders: '{"x-site-scope":"internal"}',
status: 'active',
}],
siteDisabledModels: [],
accounts: [],
accountTokens: [],
checkinLogs: [],
@@ -183,6 +184,8 @@ describe('databaseMigrationService', () => {
tokenRoutes: [],
routeChannels: [],
proxyLogs: [],
proxyVideoTasks: [],
proxyFiles: [],
downstreamApiKeys: [],
events: [],
},
@@ -200,4 +203,69 @@ describe('databaseMigrationService', () => {
expect(customHeadersIndex).toBeGreaterThanOrEqual(0);
expect(siteStatement?.values[customHeadersIndex]).toBe('{"x-site-scope":"internal"}');
});
it('includes disabled models, proxy video tasks, and proxy files in migration statements', () => {
const statements = __databaseMigrationServiceTestUtils.buildStatements({
version: 'test',
timestamp: Date.now(),
accounts: {
sites: [],
siteDisabledModels: [{
id: 3,
siteId: 12,
modelName: 'claude-opus-4-6',
createdAt: '2026-03-14T00:00:00.000Z',
}],
accounts: [],
accountTokens: [],
checkinLogs: [],
modelAvailability: [],
tokenModelAvailability: [],
tokenRoutes: [],
routeChannels: [],
proxyLogs: [],
proxyVideoTasks: [{
id: 5,
publicId: 'video-public-id',
upstreamVideoId: 'upstream-video-id',
siteUrl: 'https://example.com',
tokenValue: 'sk-video',
requestedModel: 'veo-3',
actualModel: 'veo-3',
channelId: 7,
accountId: 9,
statusSnapshot: '{"status":"done"}',
upstreamResponseMeta: '{"id":"video"}',
lastUpstreamStatus: 200,
lastPolledAt: '2026-03-14T01:00:00.000Z',
createdAt: '2026-03-14T00:00:00.000Z',
updatedAt: '2026-03-14T01:00:00.000Z',
}],
proxyFiles: [{
id: 8,
publicId: 'file-public-id',
ownerType: 'downstream_key',
ownerId: 'key-1',
filename: 'demo.txt',
mimeType: 'text/plain',
purpose: 'assistants',
byteSize: 4,
sha256: 'abcd',
contentBase64: 'ZGVtbw==',
createdAt: '2026-03-14T00:00:00.000Z',
updatedAt: '2026-03-14T01:00:00.000Z',
deletedAt: null,
}],
downstreamApiKeys: [],
events: [],
},
preferences: {
settings: [],
},
} as any);
expect(statements.some((statement) => statement.table === 'site_disabled_models')).toBe(true);
expect(statements.some((statement) => statement.table === 'proxy_video_tasks')).toBe(true);
expect(statements.some((statement) => statement.table === 'proxy_files')).toBe(true);
});
});
@@ -28,6 +28,7 @@ type BackupSnapshot = {
timestamp: number;
accounts: {
sites: Array<Record<string, unknown>>;
siteDisabledModels: Array<Record<string, unknown>>;
accounts: Array<Record<string, unknown>>;
accountTokens: Array<Record<string, unknown>>;
checkinLogs: Array<Record<string, unknown>>;
@@ -36,6 +37,8 @@ type BackupSnapshot = {
tokenRoutes: Array<Record<string, unknown>>;
routeChannels: Array<Record<string, unknown>>;
proxyLogs: Array<Record<string, unknown>>;
proxyVideoTasks: Array<Record<string, unknown>>;
proxyFiles: Array<Record<string, unknown>>;
downstreamApiKeys: Array<Record<string, unknown>>;
events: Array<Record<string, unknown>>;
};
@@ -52,6 +55,7 @@ export interface DatabaseMigrationSummary {
timestamp: number;
rows: {
sites: number;
siteDisabledModels: number;
accounts: number;
accountTokens: number;
tokenRoutes: number;
@@ -60,6 +64,8 @@ export interface DatabaseMigrationSummary {
modelAvailability: number;
tokenModelAvailability: number;
proxyLogs: number;
proxyVideoTasks: number;
proxyFiles: number;
downstreamApiKeys: number;
events: number;
settings: number;
@@ -204,6 +210,7 @@ async function toBackupSnapshot(): Promise<BackupSnapshot> {
timestamp: Date.now(),
accounts: {
sites: await db.select().from(schema.sites).all() as Array<Record<string, unknown>>,
siteDisabledModels: await db.select().from(schema.siteDisabledModels).all() as Array<Record<string, unknown>>,
accounts: await db.select().from(schema.accounts).all() as Array<Record<string, unknown>>,
accountTokens: await db.select().from(schema.accountTokens).all() as Array<Record<string, unknown>>,
checkinLogs: await db.select().from(schema.checkinLogs).all() as Array<Record<string, unknown>>,
@@ -212,6 +219,8 @@ async function toBackupSnapshot(): Promise<BackupSnapshot> {
tokenRoutes: await db.select().from(schema.tokenRoutes).all() as Array<Record<string, unknown>>,
routeChannels: await db.select().from(schema.routeChannels).all() as Array<Record<string, unknown>>,
proxyLogs: await db.select().from(schema.proxyLogs).all() as Array<Record<string, unknown>>,
proxyVideoTasks: await db.select().from(schema.proxyVideoTasks).all() as Array<Record<string, unknown>>,
proxyFiles: await db.select().from(schema.proxyFiles).all() as Array<Record<string, unknown>>,
downstreamApiKeys: await db.select().from(schema.downstreamApiKeys).all() as Array<Record<string, unknown>>,
events: await db.select().from(schema.events).all() as Array<Record<string, unknown>>,
},
@@ -240,8 +249,11 @@ async function clearTargetData(client: SqlClient): Promise<void> {
'model_availability',
'checkin_logs',
'proxy_logs',
'proxy_video_tasks',
'proxy_files',
'account_tokens',
'accounts',
'site_disabled_models',
'token_routes',
'sites',
'downstream_api_keys',
@@ -280,6 +292,19 @@ function buildStatements(snapshot: BackupSnapshot): InsertStatement[] {
});
}
for (const row of snapshot.accounts.siteDisabledModels) {
statements.push({
table: 'site_disabled_models',
columns: ['id', 'site_id', 'model_name', 'created_at'],
values: [
asNumber(row.id, 0),
asNumber(row.siteId, 0),
asNullableString(row.modelName),
asNullableString(row.createdAt),
],
});
}
for (const row of snapshot.accounts.accounts) {
statements.push({
table: 'accounts',
@@ -446,6 +471,52 @@ function buildStatements(snapshot: BackupSnapshot): InsertStatement[] {
});
}
for (const row of snapshot.accounts.proxyVideoTasks) {
statements.push({
table: 'proxy_video_tasks',
columns: ['id', 'public_id', 'upstream_video_id', 'site_url', 'token_value', 'requested_model', 'actual_model', 'channel_id', 'account_id', 'status_snapshot', 'upstream_response_meta', 'last_upstream_status', 'last_polled_at', 'created_at', 'updated_at'],
values: [
asNumber(row.id, 0),
asNullableString(row.publicId),
asNullableString(row.upstreamVideoId),
asNullableString(row.siteUrl),
asNullableString(row.tokenValue),
asNullableString(row.requestedModel),
asNullableString(row.actualModel),
asNumber(row.channelId, null),
asNumber(row.accountId, null),
asNullableString(row.statusSnapshot),
asNullableString(row.upstreamResponseMeta),
asNumber(row.lastUpstreamStatus, null),
asNullableString(row.lastPolledAt),
asNullableString(row.createdAt),
asNullableString(row.updatedAt),
],
});
}
for (const row of snapshot.accounts.proxyFiles) {
statements.push({
table: 'proxy_files',
columns: ['id', 'public_id', 'owner_type', 'owner_id', 'filename', 'mime_type', 'purpose', 'byte_size', 'sha256', 'content_base64', 'created_at', 'updated_at', 'deleted_at'],
values: [
asNumber(row.id, 0),
asNullableString(row.publicId),
asNullableString(row.ownerType),
asNullableString(row.ownerId),
asNullableString(row.filename),
asNullableString(row.mimeType),
asNullableString(row.purpose),
asNumber(row.byteSize, 0),
asNullableString(row.sha256),
asNullableString(row.contentBase64),
asNullableString(row.createdAt),
asNullableString(row.updatedAt),
asNullableString(row.deletedAt),
],
});
}
for (const row of snapshot.accounts.downstreamApiKeys) {
statements.push({
table: 'downstream_api_keys',
@@ -527,6 +598,7 @@ async function syncPostgresSequences(client: SqlClient): Promise<void> {
if (client.dialect !== 'postgres') return;
const tables = [
'sites',
'site_disabled_models',
'accounts',
'account_tokens',
'checkin_logs',
@@ -535,6 +607,8 @@ async function syncPostgresSequences(client: SqlClient): Promise<void> {
'token_routes',
'route_channels',
'proxy_logs',
'proxy_video_tasks',
'proxy_files',
'downstream_api_keys',
'events',
];
@@ -590,6 +664,7 @@ export async function migrateCurrentDatabase(input: DatabaseMigrationInput): Pro
timestamp: snapshot.timestamp,
rows: {
sites: snapshot.accounts.sites.length,
siteDisabledModels: snapshot.accounts.siteDisabledModels.length,
accounts: snapshot.accounts.accounts.length,
accountTokens: snapshot.accounts.accountTokens.length,
tokenRoutes: snapshot.accounts.tokenRoutes.length,
@@ -598,6 +673,8 @@ export async function migrateCurrentDatabase(input: DatabaseMigrationInput): Pro
modelAvailability: snapshot.accounts.modelAvailability.length,
tokenModelAvailability: snapshot.accounts.tokenModelAvailability.length,
proxyLogs: snapshot.accounts.proxyLogs.length,
proxyVideoTasks: snapshot.accounts.proxyVideoTasks.length,
proxyFiles: snapshot.accounts.proxyFiles.length,
downstreamApiKeys: snapshot.accounts.downstreamApiKeys.length,
events: snapshot.accounts.events.length,
settings: snapshot.preferences.settings.length,