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:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user