273 lines
14 KiB
TypeScript
273 lines
14 KiB
TypeScript
import { sqliteTable, text, integer, real, uniqueIndex, index } from 'drizzle-orm/sqlite-core';
|
|
import { sql } from 'drizzle-orm';
|
|
|
|
export const sites = sqliteTable('sites', {
|
|
id: integer('id').primaryKey({ autoIncrement: true }),
|
|
name: text('name').notNull(),
|
|
url: text('url').notNull(),
|
|
externalCheckinUrl: text('external_checkin_url'),
|
|
platform: text('platform').notNull(), // 'new-api' | 'one-api' | 'veloera' | 'one-hub' | 'done-hub' | 'sub2api' | 'openai' | 'claude' | 'gemini'
|
|
proxyUrl: text('proxy_url'),
|
|
useSystemProxy: integer('use_system_proxy', { mode: 'boolean' }).default(false),
|
|
customHeaders: text('custom_headers'),
|
|
status: text('status').notNull().default('active'), // 'active' | 'disabled'
|
|
isPinned: integer('is_pinned', { mode: 'boolean' }).default(false),
|
|
sortOrder: integer('sort_order').default(0),
|
|
globalWeight: real('global_weight').default(1),
|
|
apiKey: text('api_key'),
|
|
createdAt: text('created_at').default(sql`(datetime('now'))`),
|
|
updatedAt: text('updated_at').default(sql`(datetime('now'))`),
|
|
}, (table) => ({
|
|
statusIdx: index('sites_status_idx').on(table.status),
|
|
}));
|
|
|
|
export const siteDisabledModels = sqliteTable('site_disabled_models', {
|
|
id: integer('id').primaryKey({ autoIncrement: true }),
|
|
siteId: integer('site_id').notNull().references(() => sites.id, { onDelete: 'cascade' }),
|
|
modelName: text('model_name').notNull(),
|
|
createdAt: text('created_at').default(sql`(datetime('now'))`),
|
|
}, (table) => ({
|
|
siteModelUnique: uniqueIndex('site_disabled_models_site_model_unique').on(table.siteId, table.modelName),
|
|
siteIdIdx: index('site_disabled_models_site_id_idx').on(table.siteId),
|
|
}));
|
|
|
|
export const accounts = sqliteTable('accounts', {
|
|
id: integer('id').primaryKey({ autoIncrement: true }),
|
|
siteId: integer('site_id').notNull().references(() => sites.id, { onDelete: 'cascade' }),
|
|
username: text('username'),
|
|
accessToken: text('access_token').notNull(),
|
|
apiToken: text('api_token'),
|
|
balance: real('balance').default(0),
|
|
balanceUsed: real('balance_used').default(0),
|
|
quota: real('quota').default(0),
|
|
unitCost: real('unit_cost'),
|
|
valueScore: real('value_score').default(0),
|
|
status: text('status').default('active'), // 'active' | 'disabled' | 'expired'
|
|
isPinned: integer('is_pinned', { mode: 'boolean' }).default(false),
|
|
sortOrder: integer('sort_order').default(0),
|
|
checkinEnabled: integer('checkin_enabled', { mode: 'boolean' }).default(true),
|
|
lastCheckinAt: text('last_checkin_at'),
|
|
lastBalanceRefresh: text('last_balance_refresh'),
|
|
extraConfig: text('extra_config'), // JSON string
|
|
createdAt: text('created_at').default(sql`(datetime('now'))`),
|
|
updatedAt: text('updated_at').default(sql`(datetime('now'))`),
|
|
}, (table) => ({
|
|
siteIdIdx: index('accounts_site_id_idx').on(table.siteId),
|
|
statusIdx: index('accounts_status_idx').on(table.status),
|
|
siteStatusIdx: index('accounts_site_status_idx').on(table.siteId, table.status),
|
|
}));
|
|
|
|
export const accountTokens = sqliteTable('account_tokens', {
|
|
id: integer('id').primaryKey({ autoIncrement: true }),
|
|
accountId: integer('account_id').notNull().references(() => accounts.id, { onDelete: 'cascade' }),
|
|
name: text('name').notNull(),
|
|
token: text('token').notNull(),
|
|
tokenGroup: text('token_group'),
|
|
source: text('source').default('manual'), // 'manual' | 'sync' | 'legacy'
|
|
enabled: integer('enabled', { mode: 'boolean' }).default(true),
|
|
isDefault: integer('is_default', { mode: 'boolean' }).default(false),
|
|
createdAt: text('created_at').default(sql`(datetime('now'))`),
|
|
updatedAt: text('updated_at').default(sql`(datetime('now'))`),
|
|
}, (table) => ({
|
|
accountIdIdx: index('account_tokens_account_id_idx').on(table.accountId),
|
|
accountEnabledIdx: index('account_tokens_account_enabled_idx').on(table.accountId, table.enabled),
|
|
enabledIdx: index('account_tokens_enabled_idx').on(table.enabled),
|
|
}));
|
|
|
|
export const checkinLogs = sqliteTable('checkin_logs', {
|
|
id: integer('id').primaryKey({ autoIncrement: true }),
|
|
accountId: integer('account_id').notNull().references(() => accounts.id, { onDelete: 'cascade' }),
|
|
status: text('status').notNull(), // 'success' | 'failed' | 'skipped'
|
|
message: text('message'),
|
|
reward: text('reward'),
|
|
createdAt: text('created_at').default(sql`(datetime('now'))`),
|
|
}, (table) => ({
|
|
accountCreatedIdx: index('checkin_logs_account_created_at_idx').on(table.accountId, table.createdAt),
|
|
createdAtIdx: index('checkin_logs_created_at_idx').on(table.createdAt),
|
|
statusIdx: index('checkin_logs_status_idx').on(table.status),
|
|
}));
|
|
|
|
export const modelAvailability = sqliteTable('model_availability', {
|
|
id: integer('id').primaryKey({ autoIncrement: true }),
|
|
accountId: integer('account_id').notNull().references(() => accounts.id, { onDelete: 'cascade' }),
|
|
modelName: text('model_name').notNull(),
|
|
available: integer('available', { mode: 'boolean' }),
|
|
isManual: integer('is_manual', { mode: 'boolean' }).default(false),
|
|
latencyMs: integer('latency_ms'),
|
|
checkedAt: text('checked_at').default(sql`(datetime('now'))`),
|
|
}, (table) => ({
|
|
accountModelUnique: uniqueIndex('model_availability_account_model_unique').on(table.accountId, table.modelName),
|
|
accountAvailableIdx: index('model_availability_account_available_idx').on(table.accountId, table.available),
|
|
modelNameIdx: index('model_availability_model_name_idx').on(table.modelName),
|
|
}));
|
|
|
|
export const tokenModelAvailability = sqliteTable('token_model_availability', {
|
|
id: integer('id').primaryKey({ autoIncrement: true }),
|
|
tokenId: integer('token_id').notNull().references(() => accountTokens.id, { onDelete: 'cascade' }),
|
|
modelName: text('model_name').notNull(),
|
|
available: integer('available', { mode: 'boolean' }),
|
|
latencyMs: integer('latency_ms'),
|
|
checkedAt: text('checked_at').default(sql`(datetime('now'))`),
|
|
}, (table) => ({
|
|
tokenModelUnique: uniqueIndex('token_model_availability_token_model_unique').on(table.tokenId, table.modelName),
|
|
tokenAvailableIdx: index('token_model_availability_token_available_idx').on(table.tokenId, table.available),
|
|
modelNameIdx: index('token_model_availability_model_name_idx').on(table.modelName),
|
|
availableIdx: index('token_model_availability_available_idx').on(table.available),
|
|
}));
|
|
|
|
export const tokenRoutes = sqliteTable('token_routes', {
|
|
id: integer('id').primaryKey({ autoIncrement: true }),
|
|
modelPattern: text('model_pattern').notNull(),
|
|
displayName: text('display_name'),
|
|
displayIcon: text('display_icon'),
|
|
modelMapping: text('model_mapping'), // JSON
|
|
decisionSnapshot: text('decision_snapshot'), // JSON
|
|
decisionRefreshedAt: text('decision_refreshed_at'),
|
|
routingStrategy: text('routing_strategy').default('weighted'),
|
|
enabled: integer('enabled', { mode: 'boolean' }).default(true),
|
|
createdAt: text('created_at').default(sql`(datetime('now'))`),
|
|
updatedAt: text('updated_at').default(sql`(datetime('now'))`),
|
|
}, (table) => ({
|
|
modelPatternIdx: index('token_routes_model_pattern_idx').on(table.modelPattern),
|
|
enabledIdx: index('token_routes_enabled_idx').on(table.enabled),
|
|
}));
|
|
|
|
export const routeChannels = sqliteTable('route_channels', {
|
|
id: integer('id').primaryKey({ autoIncrement: true }),
|
|
routeId: integer('route_id').notNull().references(() => tokenRoutes.id, { onDelete: 'cascade' }),
|
|
accountId: integer('account_id').notNull().references(() => accounts.id, { onDelete: 'cascade' }),
|
|
tokenId: integer('token_id').references(() => accountTokens.id, { onDelete: 'set null' }),
|
|
sourceModel: text('source_model'),
|
|
priority: integer('priority').default(0),
|
|
weight: integer('weight').default(10),
|
|
enabled: integer('enabled', { mode: 'boolean' }).default(true),
|
|
manualOverride: integer('manual_override', { mode: 'boolean' }).default(false),
|
|
successCount: integer('success_count').default(0),
|
|
failCount: integer('fail_count').default(0),
|
|
totalLatencyMs: integer('total_latency_ms').default(0),
|
|
totalCost: real('total_cost').default(0),
|
|
lastUsedAt: text('last_used_at'),
|
|
lastSelectedAt: text('last_selected_at'),
|
|
lastFailAt: text('last_fail_at'),
|
|
consecutiveFailCount: integer('consecutive_fail_count').notNull().default(0),
|
|
cooldownLevel: integer('cooldown_level').notNull().default(0),
|
|
cooldownUntil: text('cooldown_until'),
|
|
}, (table) => ({
|
|
routeIdIdx: index('route_channels_route_id_idx').on(table.routeId),
|
|
accountIdIdx: index('route_channels_account_id_idx').on(table.accountId),
|
|
tokenIdIdx: index('route_channels_token_id_idx').on(table.tokenId),
|
|
routeEnabledIdx: index('route_channels_route_enabled_idx').on(table.routeId, table.enabled),
|
|
routeTokenIdx: index('route_channels_route_token_idx').on(table.routeId, table.tokenId),
|
|
}));
|
|
|
|
export const proxyLogs = sqliteTable('proxy_logs', {
|
|
id: integer('id').primaryKey({ autoIncrement: true }),
|
|
routeId: integer('route_id'),
|
|
channelId: integer('channel_id'),
|
|
accountId: integer('account_id'),
|
|
modelRequested: text('model_requested'),
|
|
modelActual: text('model_actual'),
|
|
status: text('status'), // 'success' | 'failed' | 'retried'
|
|
httpStatus: integer('http_status'),
|
|
latencyMs: integer('latency_ms'),
|
|
promptTokens: integer('prompt_tokens'),
|
|
completionTokens: integer('completion_tokens'),
|
|
totalTokens: integer('total_tokens'),
|
|
estimatedCost: real('estimated_cost'),
|
|
billingDetails: text('billing_details'),
|
|
errorMessage: text('error_message'),
|
|
retryCount: integer('retry_count').default(0),
|
|
createdAt: text('created_at').default(sql`(datetime('now'))`),
|
|
}, (table) => ({
|
|
createdAtIdx: index('proxy_logs_created_at_idx').on(table.createdAt),
|
|
accountCreatedIdx: index('proxy_logs_account_created_at_idx').on(table.accountId, table.createdAt),
|
|
statusCreatedIdx: index('proxy_logs_status_created_at_idx').on(table.status, table.createdAt),
|
|
modelActualCreatedIdx: index('proxy_logs_model_actual_created_at_idx').on(table.modelActual, table.createdAt),
|
|
}));
|
|
|
|
export const proxyVideoTasks = sqliteTable('proxy_video_tasks', {
|
|
id: integer('id').primaryKey({ autoIncrement: true }),
|
|
publicId: text('public_id').notNull(),
|
|
upstreamVideoId: text('upstream_video_id').notNull(),
|
|
siteUrl: text('site_url').notNull(),
|
|
tokenValue: text('token_value').notNull(),
|
|
requestedModel: text('requested_model'),
|
|
actualModel: text('actual_model'),
|
|
channelId: integer('channel_id'),
|
|
accountId: integer('account_id'),
|
|
statusSnapshot: text('status_snapshot'),
|
|
upstreamResponseMeta: text('upstream_response_meta'),
|
|
lastUpstreamStatus: integer('last_upstream_status'),
|
|
lastPolledAt: text('last_polled_at'),
|
|
createdAt: text('created_at').default(sql`(datetime('now'))`),
|
|
updatedAt: text('updated_at').default(sql`(datetime('now'))`),
|
|
}, (table) => ({
|
|
publicIdUnique: uniqueIndex('proxy_video_tasks_public_id_unique').on(table.publicId),
|
|
upstreamVideoIdIdx: index('proxy_video_tasks_upstream_video_id_idx').on(table.upstreamVideoId),
|
|
createdAtIdx: index('proxy_video_tasks_created_at_idx').on(table.createdAt),
|
|
}));
|
|
|
|
export const proxyFiles = sqliteTable('proxy_files', {
|
|
id: integer('id').primaryKey({ autoIncrement: true }),
|
|
publicId: text('public_id').notNull(),
|
|
ownerType: text('owner_type').notNull(),
|
|
ownerId: text('owner_id').notNull(),
|
|
filename: text('filename').notNull(),
|
|
mimeType: text('mime_type').notNull(),
|
|
purpose: text('purpose'),
|
|
byteSize: integer('byte_size').notNull(),
|
|
sha256: text('sha256').notNull(),
|
|
contentBase64: text('content_base64').notNull(),
|
|
createdAt: text('created_at').default(sql`(datetime('now'))`),
|
|
updatedAt: text('updated_at').default(sql`(datetime('now'))`),
|
|
deletedAt: text('deleted_at'),
|
|
}, (table) => ({
|
|
publicIdUnique: uniqueIndex('proxy_files_public_id_unique').on(table.publicId),
|
|
ownerLookupIdx: index('proxy_files_owner_lookup_idx').on(table.ownerType, table.ownerId, table.deletedAt),
|
|
}));
|
|
|
|
export const settings = sqliteTable('settings', {
|
|
key: text('key').primaryKey(),
|
|
value: text('value'), // JSON
|
|
});
|
|
|
|
export const downstreamApiKeys = sqliteTable('downstream_api_keys', {
|
|
id: integer('id').primaryKey({ autoIncrement: true }),
|
|
name: text('name').notNull(),
|
|
key: text('key').notNull(),
|
|
description: text('description'),
|
|
enabled: integer('enabled', { mode: 'boolean' }).default(true),
|
|
expiresAt: text('expires_at'),
|
|
maxCost: real('max_cost'),
|
|
usedCost: real('used_cost').default(0),
|
|
maxRequests: integer('max_requests'),
|
|
usedRequests: integer('used_requests').default(0),
|
|
supportedModels: text('supported_models'), // JSON array<string>
|
|
allowedRouteIds: text('allowed_route_ids'), // JSON array<number>
|
|
siteWeightMultipliers: text('site_weight_multipliers'), // JSON object { [siteId]: multiplier }
|
|
lastUsedAt: text('last_used_at'),
|
|
createdAt: text('created_at').default(sql`(datetime('now'))`),
|
|
updatedAt: text('updated_at').default(sql`(datetime('now'))`),
|
|
}, (table) => ({
|
|
keyUnique: uniqueIndex('downstream_api_keys_key_unique').on(table.key),
|
|
nameIdx: index('downstream_api_keys_name_idx').on(table.name),
|
|
enabledIdx: index('downstream_api_keys_enabled_idx').on(table.enabled),
|
|
expiresAtIdx: index('downstream_api_keys_expires_at_idx').on(table.expiresAt),
|
|
}));
|
|
|
|
export const events = sqliteTable('events', {
|
|
id: integer('id').primaryKey({ autoIncrement: true }),
|
|
type: text('type').notNull(), // 'checkin' | 'balance' | 'token' | 'proxy' | 'status'
|
|
title: text('title').notNull(),
|
|
message: text('message'),
|
|
level: text('level').notNull().default('info'), // 'info' | 'warning' | 'error'
|
|
read: integer('read', { mode: 'boolean' }).default(false),
|
|
relatedId: integer('related_id'),
|
|
relatedType: text('related_type'), // 'account' | 'site' | 'route'
|
|
createdAt: text('created_at').default(sql`(datetime('now'))`),
|
|
}, (table) => ({
|
|
readCreatedIdx: index('events_read_created_at_idx').on(table.read, table.createdAt),
|
|
typeCreatedIdx: index('events_type_created_at_idx').on(table.type, table.createdAt),
|
|
createdAtIdx: index('events_created_at_idx').on(table.createdAt),
|
|
}));
|