Files
BoosAPI/desktop/src-tauri/crates/migration/src/m20240101_000001_init.rs
T
boos e0a5f57793 feat(desktop): embed BoosAPI Desktop client as a subproject
Vendor the rebranded BoosAPI Desktop client (Tauri + Vue) into metapi as
a monorepo subdirectory under desktop/. Source only — node_modules,
dist, Cargo target and the upstream .git history are excluded.

desktop/ keeps its own independent Cargo workspace (desktop/src-tauri
with crates/*); it is not part of the backend boosapi crate, so a
`cargo check` at the metapi root still compiles only the backend. Build
the desktop app from desktop/src-tauri per desktop/README.md.

Note: app icons are still placeholder assets pending the final BoosAPI
logo. The updater signing keys live outside the repo and are not
included.
2026-06-22 11:32:57 +08:00

2384 lines
82 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
use sea_orm_migration::prelude::*;
#[derive(DeriveMigrationName)]
pub struct Migration;
// ===========================================================================
// Iden enums – one per table, all columns included
// ===========================================================================
#[derive(DeriveIden)]
enum Providers {
Table,
Id,
Name,
ProviderType,
ApiHost,
ApiPath,
Enabled,
ProxyConfig,
SortOrder,
CreatedAt,
UpdatedAt,
}
#[derive(DeriveIden)]
enum ProviderKeys {
Table,
Id,
ProviderId,
KeyEncrypted,
KeyPrefix,
Enabled,
LastValidatedAt,
LastError,
RotationIndex,
CreatedAt,
}
#[derive(DeriveIden)]
enum Models {
Table,
ProviderId,
ModelId,
Name,
Capabilities,
MaxTokens,
Enabled,
ParamOverrides,
ModelType,
GroupName,
}
#[derive(DeriveIden)]
enum Conversations {
Table,
Id,
Title,
ModelId,
ProviderId,
AppId,
SystemPrompt,
Temperature,
MaxTokens,
TopP,
FrequencyPenalty,
MessageCount,
IsPinned,
IsArchived,
WorkspaceSnapshotJson,
ActiveBranchId,
ActiveArtifactId,
ResearchMode,
SearchEnabled,
SearchProviderId,
ThinkingBudget,
EnabledMcpServerIds,
EnabledKnowledgeBaseIds,
EnabledMemoryNamespaceIds,
CreatedAt,
UpdatedAt,
}
#[derive(DeriveIden)]
enum Messages {
Table,
Id,
ConversationId,
Role,
Content,
ProviderId,
ModelId,
TokenCount,
Attachments,
Thinking,
ParentMessageId,
VersionIndex,
IsActive,
BranchId,
ToolCallsJson,
ToolCallId,
CreatedAt,
}
#[derive(DeriveIden)]
enum Categories {
Table,
Id,
Name,
SortOrder,
}
#[derive(DeriveIden)]
enum Apps {
Table,
Id,
Name,
Description,
Icon,
IconColor,
SystemPrompt,
DefaultModelId,
DefaultProviderId,
Temperature,
MaxTokens,
TopP,
CategoryId,
IsFavorite,
Variables,
SearchPolicyJson,
ToolBindingJson,
KnowledgeBindingJson,
MemoryPolicyJson,
CreatedAt,
UpdatedAt,
}
#[derive(DeriveIden)]
enum GatewayKeys {
Table,
Id,
Name,
KeyHash,
KeyPrefix,
EncryptedKey,
Enabled,
CreatedAt,
LastUsedAt,
}
#[derive(DeriveIden)]
enum GatewayUsage {
Table,
Id,
KeyId,
ProviderId,
ModelId,
RequestTokens,
ResponseTokens,
CreatedAt,
}
#[derive(DeriveIden)]
enum Settings {
Table,
Key,
Value,
}
#[derive(DeriveIden)]
enum SearchProviders {
Table,
Id,
Name,
ProviderType,
Endpoint,
ApiKeyRef,
Enabled,
Region,
Language,
SafeSearch,
ResultLimit,
TimeoutMs,
}
#[derive(DeriveIden)]
enum SearchCitations {
Table,
Id,
ConversationId,
MessageId,
Title,
Url,
Snippet,
ProviderId,
Rank,
}
#[derive(DeriveIden)]
enum McpServers {
Table,
Id,
Name,
Transport,
Command,
ArgsJson,
Endpoint,
EnvJson,
Enabled,
PermissionPolicy,
Source,
}
#[derive(DeriveIden)]
enum ToolDescriptors {
Table,
Id,
ServerId,
Name,
Description,
InputSchemaJson,
}
#[derive(DeriveIden)]
enum ToolExecutions {
Table,
Id,
ConversationId,
MessageId,
ServerId,
ToolName,
Status,
InputPreview,
OutputPreview,
ErrorMessage,
DurationMs,
CreatedAt,
}
#[derive(DeriveIden)]
enum KnowledgeBases {
Table,
Id,
Name,
Description,
EmbeddingProvider,
Enabled,
}
#[derive(DeriveIden)]
enum KnowledgeDocuments {
Table,
Id,
KnowledgeBaseId,
Title,
SourcePath,
MimeType,
SizeBytes,
IndexingStatus,
}
#[derive(DeriveIden)]
enum RetrievalHits {
Table,
Id,
ConversationId,
MessageId,
KnowledgeBaseId,
DocumentId,
ChunkRef,
Score,
Preview,
}
#[derive(DeriveIden)]
enum MemoryNamespaces {
Table,
Id,
Name,
Scope,
AppId,
EmbeddingProvider,
}
#[derive(DeriveIden)]
enum MemoryItems {
Table,
Id,
NamespaceId,
Title,
Content,
Source,
UpdatedAt,
}
#[derive(DeriveIden)]
enum Artifacts {
Table,
Id,
ConversationId,
Kind,
Title,
Content,
Format,
Pinned,
UpdatedAt,
}
#[derive(DeriveIden)]
enum ContextPacks {
Table,
Id,
AppId,
Name,
Content,
EnabledByDefault,
}
#[derive(DeriveIden)]
enum ContextSources {
Table,
Id,
ConversationId,
MessageId,
#[sea_orm(iden = "type")]
SourceType,
RefId,
Title,
Enabled,
Summary,
}
#[derive(DeriveIden)]
enum ConversationBranches {
Table,
Id,
ConversationId,
ParentMessageId,
BranchLabel,
BranchIndex,
ComparedMessageIdsJson,
CreatedAt,
}
#[derive(DeriveIden)]
enum BackupManifests {
Table,
Id,
Version,
CreatedAt,
Encrypted,
Checksum,
ObjectCountsJson,
SourceAppVersion,
FilePath,
FileSize,
}
#[derive(DeriveIden)]
enum BackupTargets {
Table,
Id,
Kind,
ConfigJson,
}
#[derive(DeriveIden)]
enum ImportJobs {
Table,
Id,
SourceType,
Status,
SummaryJson,
ConflictCount,
CreatedAt,
}
#[derive(DeriveIden)]
enum ProgramPolicies {
Table,
Id,
ProgramName,
AllowedProviderIdsJson,
AllowedModelIdsJson,
DefaultProviderId,
DefaultModelId,
RateLimitPerMinute,
}
#[derive(DeriveIden)]
enum GatewayDiagnostics {
Table,
Id,
Category,
Status,
Message,
CreatedAt,
}
#[derive(DeriveIden)]
enum DesktopState {
Table,
WindowKey,
Width,
Height,
X,
Y,
Maximized,
Visible,
}
#[derive(DeriveIden)]
enum StoredFiles {
Table,
Id,
Hash,
OriginalName,
MimeType,
SizeBytes,
StoragePath,
ConversationId,
CreatedAt,
}
#[derive(DeriveIden)]
enum GatewayRequestLogs {
Table,
Id,
KeyId,
KeyName,
Method,
Path,
Model,
ProviderId,
StatusCode,
DurationMs,
RequestTokens,
ResponseTokens,
ErrorMessage,
CreatedAt,
}
// ===========================================================================
// Migration implementation
// ===========================================================================
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
// =================================================================
// 1. Providers
// =================================================================
manager
.create_table(
Table::create()
.table(Providers::Table)
.if_not_exists()
.col(
ColumnDef::new(Providers::Id)
.string()
.not_null()
.primary_key(),
)
.col(ColumnDef::new(Providers::Name).string().not_null())
.col(ColumnDef::new(Providers::ProviderType).string().not_null())
.col(ColumnDef::new(Providers::ApiHost).string().not_null())
.col(ColumnDef::new(Providers::ApiPath).string().null())
.col(
ColumnDef::new(Providers::Enabled)
.integer()
.not_null()
.default(1),
)
.col(ColumnDef::new(Providers::ProxyConfig).string().null())
.col(
ColumnDef::new(Providers::SortOrder)
.integer()
.not_null()
.default(0),
)
.col(ColumnDef::new(Providers::CreatedAt).integer().not_null())
.col(ColumnDef::new(Providers::UpdatedAt).integer().not_null())
.to_owned(),
)
.await?;
// =================================================================
// 2. Provider Keys
// =================================================================
manager
.create_table(
Table::create()
.table(ProviderKeys::Table)
.if_not_exists()
.col(
ColumnDef::new(ProviderKeys::Id)
.string()
.not_null()
.primary_key(),
)
.col(ColumnDef::new(ProviderKeys::ProviderId).string().not_null())
.col(
ColumnDef::new(ProviderKeys::KeyEncrypted)
.string()
.not_null(),
)
.col(
ColumnDef::new(ProviderKeys::KeyPrefix)
.string()
.not_null()
.default(""),
)
.col(
ColumnDef::new(ProviderKeys::Enabled)
.integer()
.not_null()
.default(1),
)
.col(
ColumnDef::new(ProviderKeys::LastValidatedAt)
.integer()
.null(),
)
.col(ColumnDef::new(ProviderKeys::LastError).string().null())
.col(
ColumnDef::new(ProviderKeys::RotationIndex)
.integer()
.not_null()
.default(0),
)
.col(ColumnDef::new(ProviderKeys::CreatedAt).integer().not_null())
.foreign_key(
ForeignKey::create()
.from(ProviderKeys::Table, ProviderKeys::ProviderId)
.to(Providers::Table, Providers::Id)
.on_delete(ForeignKeyAction::Cascade),
)
.to_owned(),
)
.await?;
// =================================================================
// 3. Models (composite primary key)
// =================================================================
manager
.create_table(
Table::create()
.table(Models::Table)
.if_not_exists()
.col(ColumnDef::new(Models::ProviderId).string().not_null())
.col(ColumnDef::new(Models::ModelId).string().not_null())
.col(ColumnDef::new(Models::Name).string().not_null())
.col(
ColumnDef::new(Models::Capabilities)
.string()
.not_null()
.default("[]"),
)
.col(ColumnDef::new(Models::MaxTokens).integer().null())
.col(
ColumnDef::new(Models::Enabled)
.integer()
.not_null()
.default(1),
)
.col(ColumnDef::new(Models::ParamOverrides).string().null())
.col(
ColumnDef::new(Models::ModelType)
.string()
.not_null()
.default("chat"),
)
.col(ColumnDef::new(Models::GroupName).string().null())
.primary_key(Index::create().col(Models::ProviderId).col(Models::ModelId))
.foreign_key(
ForeignKey::create()
.from(Models::Table, Models::ProviderId)
.to(Providers::Table, Providers::Id)
.on_delete(ForeignKeyAction::Cascade),
)
.to_owned(),
)
.await?;
// =================================================================
// 4. Conversations
// =================================================================
manager
.create_table(
Table::create()
.table(Conversations::Table)
.if_not_exists()
.col(
ColumnDef::new(Conversations::Id)
.string()
.not_null()
.primary_key(),
)
.col(ColumnDef::new(Conversations::Title).string().not_null())
.col(ColumnDef::new(Conversations::ModelId).string().not_null())
.col(
ColumnDef::new(Conversations::ProviderId)
.string()
.not_null(),
)
.col(ColumnDef::new(Conversations::AppId).string().null())
.col(ColumnDef::new(Conversations::SystemPrompt).string().null())
.col(
ColumnDef::new(Conversations::Temperature)
.float()
.null()
.to_owned(),
)
.col(ColumnDef::new(Conversations::MaxTokens).integer().null())
.col(
ColumnDef::new(Conversations::TopP)
.float()
.null()
.to_owned(),
)
.col(
ColumnDef::new(Conversations::FrequencyPenalty)
.float()
.null()
.to_owned(),
)
.col(
ColumnDef::new(Conversations::MessageCount)
.integer()
.not_null()
.default(0),
)
.col(
ColumnDef::new(Conversations::IsPinned)
.integer()
.not_null()
.default(0),
)
.col(
ColumnDef::new(Conversations::IsArchived)
.integer()
.not_null()
.default(0),
)
.col(
ColumnDef::new(Conversations::WorkspaceSnapshotJson)
.string()
.not_null()
.default("{}"),
)
.col(
ColumnDef::new(Conversations::ActiveBranchId)
.string()
.null(),
)
.col(
ColumnDef::new(Conversations::ActiveArtifactId)
.string()
.null(),
)
.col(
ColumnDef::new(Conversations::ResearchMode)
.integer()
.not_null()
.default(0),
)
.col(
ColumnDef::new(Conversations::SearchEnabled)
.integer()
.not_null()
.default(0),
)
.col(
ColumnDef::new(Conversations::SearchProviderId)
.string()
.null(),
)
.col(
ColumnDef::new(Conversations::ThinkingBudget)
.integer()
.null(),
)
.col(
ColumnDef::new(Conversations::EnabledMcpServerIds)
.string()
.not_null()
.default("[]"),
)
.col(
ColumnDef::new(Conversations::EnabledKnowledgeBaseIds)
.string()
.not_null()
.default("[]"),
)
.col(
ColumnDef::new(Conversations::EnabledMemoryNamespaceIds)
.string()
.not_null()
.default("[]"),
)
.col(
ColumnDef::new(Conversations::CreatedAt)
.integer()
.not_null(),
)
.col(
ColumnDef::new(Conversations::UpdatedAt)
.integer()
.not_null(),
)
.to_owned(),
)
.await?;
// =================================================================
// 5. Messages
// =================================================================
manager
.create_table(
Table::create()
.table(Messages::Table)
.if_not_exists()
.col(
ColumnDef::new(Messages::Id)
.string()
.not_null()
.primary_key(),
)
.col(ColumnDef::new(Messages::ConversationId).string().not_null())
.col(ColumnDef::new(Messages::Role).string().not_null())
.col(ColumnDef::new(Messages::Content).string().not_null())
.col(ColumnDef::new(Messages::ProviderId).string().null())
.col(ColumnDef::new(Messages::ModelId).string().null())
.col(ColumnDef::new(Messages::TokenCount).integer().null())
.col(
ColumnDef::new(Messages::Attachments)
.string()
.not_null()
.default("[]"),
)
.col(ColumnDef::new(Messages::Thinking).string().null())
.col(ColumnDef::new(Messages::ParentMessageId).string().null())
.col(
ColumnDef::new(Messages::VersionIndex)
.integer()
.not_null()
.default(0),
)
.col(
ColumnDef::new(Messages::IsActive)
.integer()
.not_null()
.default(1),
)
.col(ColumnDef::new(Messages::BranchId).string().null())
.col(ColumnDef::new(Messages::ToolCallsJson).string().null())
.col(ColumnDef::new(Messages::ToolCallId).string().null())
.col(ColumnDef::new(Messages::CreatedAt).integer().not_null())
.foreign_key(
ForeignKey::create()
.from(Messages::Table, Messages::ConversationId)
.to(Conversations::Table, Conversations::Id)
.on_delete(ForeignKeyAction::Cascade),
)
.to_owned(),
)
.await?;
// =================================================================
// 6. Categories
// =================================================================
manager
.create_table(
Table::create()
.table(Categories::Table)
.if_not_exists()
.col(
ColumnDef::new(Categories::Id)
.string()
.not_null()
.primary_key(),
)
.col(ColumnDef::new(Categories::Name).string().not_null())
.col(
ColumnDef::new(Categories::SortOrder)
.integer()
.not_null()
.default(0),
)
.to_owned(),
)
.await?;
// =================================================================
// 7. Apps
// =================================================================
manager
.create_table(
Table::create()
.table(Apps::Table)
.if_not_exists()
.col(ColumnDef::new(Apps::Id).string().not_null().primary_key())
.col(ColumnDef::new(Apps::Name).string().not_null())
.col(
ColumnDef::new(Apps::Description)
.string()
.not_null()
.default(""),
)
.col(ColumnDef::new(Apps::Icon).string().not_null().default("🤖"))
.col(
ColumnDef::new(Apps::IconColor)
.string()
.not_null()
.default("#22c55e"),
)
.col(ColumnDef::new(Apps::SystemPrompt).string().not_null())
.col(ColumnDef::new(Apps::DefaultModelId).string().null())
.col(ColumnDef::new(Apps::DefaultProviderId).string().null())
.col(ColumnDef::new(Apps::Temperature).float().null().to_owned())
.col(ColumnDef::new(Apps::MaxTokens).integer().null())
.col(ColumnDef::new(Apps::TopP).float().null().to_owned())
.col(ColumnDef::new(Apps::CategoryId).string().null())
.col(
ColumnDef::new(Apps::IsFavorite)
.integer()
.not_null()
.default(0),
)
.col(
ColumnDef::new(Apps::Variables)
.string()
.not_null()
.default("[]"),
)
.col(ColumnDef::new(Apps::SearchPolicyJson).string().null())
.col(ColumnDef::new(Apps::ToolBindingJson).string().null())
.col(ColumnDef::new(Apps::KnowledgeBindingJson).string().null())
.col(ColumnDef::new(Apps::MemoryPolicyJson).string().null())
.col(ColumnDef::new(Apps::CreatedAt).integer().not_null())
.col(ColumnDef::new(Apps::UpdatedAt).integer().not_null())
.foreign_key(
ForeignKey::create()
.from(Apps::Table, Apps::CategoryId)
.to(Categories::Table, Categories::Id)
.on_delete(ForeignKeyAction::SetNull),
)
.to_owned(),
)
.await?;
// =================================================================
// 8. Gateway Keys
// =================================================================
manager
.create_table(
Table::create()
.table(GatewayKeys::Table)
.if_not_exists()
.col(
ColumnDef::new(GatewayKeys::Id)
.string()
.not_null()
.primary_key(),
)
.col(ColumnDef::new(GatewayKeys::Name).string().not_null())
.col(
ColumnDef::new(GatewayKeys::KeyHash)
.string()
.not_null()
.unique_key()
.to_owned(),
)
.col(ColumnDef::new(GatewayKeys::KeyPrefix).string().not_null())
.col(ColumnDef::new(GatewayKeys::EncryptedKey).string().null())
.col(
ColumnDef::new(GatewayKeys::Enabled)
.integer()
.not_null()
.default(1),
)
.col(ColumnDef::new(GatewayKeys::CreatedAt).integer().not_null())
.col(ColumnDef::new(GatewayKeys::LastUsedAt).integer().null())
.to_owned(),
)
.await?;
// =================================================================
// 9. Gateway Usage
// =================================================================
manager
.create_table(
Table::create()
.table(GatewayUsage::Table)
.if_not_exists()
.col(
ColumnDef::new(GatewayUsage::Id)
.integer()
.not_null()
.auto_increment()
.primary_key()
.to_owned(),
)
.col(ColumnDef::new(GatewayUsage::KeyId).string().not_null())
.col(ColumnDef::new(GatewayUsage::ProviderId).string().not_null())
.col(ColumnDef::new(GatewayUsage::ModelId).string().null())
.col(
ColumnDef::new(GatewayUsage::RequestTokens)
.integer()
.not_null()
.default(0),
)
.col(
ColumnDef::new(GatewayUsage::ResponseTokens)
.integer()
.not_null()
.default(0),
)
.col(ColumnDef::new(GatewayUsage::CreatedAt).integer().not_null())
.foreign_key(
ForeignKey::create()
.from(GatewayUsage::Table, GatewayUsage::KeyId)
.to(GatewayKeys::Table, GatewayKeys::Id)
.on_delete(ForeignKeyAction::Cascade),
)
.to_owned(),
)
.await?;
// =================================================================
// 10. Settings
// =================================================================
manager
.create_table(
Table::create()
.table(Settings::Table)
.if_not_exists()
.col(
ColumnDef::new(Settings::Key)
.string()
.not_null()
.primary_key(),
)
.col(ColumnDef::new(Settings::Value).string().not_null())
.to_owned(),
)
.await?;
// =================================================================
// 11. Search Providers
// =================================================================
manager
.create_table(
Table::create()
.table(SearchProviders::Table)
.if_not_exists()
.col(
ColumnDef::new(SearchProviders::Id)
.string()
.not_null()
.primary_key(),
)
.col(ColumnDef::new(SearchProviders::Name).string().not_null())
.col(
ColumnDef::new(SearchProviders::ProviderType)
.string()
.not_null()
.default("tavily"),
)
.col(ColumnDef::new(SearchProviders::Endpoint).string().null())
.col(ColumnDef::new(SearchProviders::ApiKeyRef).string().null())
.col(
ColumnDef::new(SearchProviders::Enabled)
.integer()
.not_null()
.default(1),
)
.col(ColumnDef::new(SearchProviders::Region).string().null())
.col(ColumnDef::new(SearchProviders::Language).string().null())
.col(ColumnDef::new(SearchProviders::SafeSearch).integer().null())
.col(
ColumnDef::new(SearchProviders::ResultLimit)
.integer()
.not_null()
.default(10),
)
.col(
ColumnDef::new(SearchProviders::TimeoutMs)
.integer()
.not_null()
.default(5000),
)
.to_owned(),
)
.await?;
manager
.create_index(
Index::create()
.if_not_exists()
.name("idx_search_providers_enabled")
.table(SearchProviders::Table)
.col(SearchProviders::Enabled)
.to_owned(),
)
.await?;
// =================================================================
// 12. Search Citations
// =================================================================
manager
.create_table(
Table::create()
.table(SearchCitations::Table)
.if_not_exists()
.col(
ColumnDef::new(SearchCitations::Id)
.string()
.not_null()
.primary_key(),
)
.col(
ColumnDef::new(SearchCitations::ConversationId)
.string()
.not_null(),
)
.col(
ColumnDef::new(SearchCitations::MessageId)
.string()
.not_null(),
)
.col(ColumnDef::new(SearchCitations::Title).string().not_null())
.col(ColumnDef::new(SearchCitations::Url).string().not_null())
.col(ColumnDef::new(SearchCitations::Snippet).string().null())
.col(
ColumnDef::new(SearchCitations::ProviderId)
.string()
.not_null(),
)
.col(
ColumnDef::new(SearchCitations::Rank)
.integer()
.not_null()
.default(0),
)
.foreign_key(
ForeignKey::create()
.from(SearchCitations::Table, SearchCitations::ConversationId)
.to(Conversations::Table, Conversations::Id)
.on_delete(ForeignKeyAction::Cascade),
)
.to_owned(),
)
.await?;
manager
.create_index(
Index::create()
.if_not_exists()
.name("idx_search_citations_conv")
.table(SearchCitations::Table)
.col(SearchCitations::ConversationId)
.to_owned(),
)
.await?;
manager
.create_index(
Index::create()
.if_not_exists()
.name("idx_search_citations_msg")
.table(SearchCitations::Table)
.col(SearchCitations::MessageId)
.to_owned(),
)
.await?;
// =================================================================
// 13. MCP Servers
// =================================================================
manager
.create_table(
Table::create()
.table(McpServers::Table)
.if_not_exists()
.col(
ColumnDef::new(McpServers::Id)
.string()
.not_null()
.primary_key(),
)
.col(ColumnDef::new(McpServers::Name).string().not_null())
.col(
ColumnDef::new(McpServers::Transport)
.string()
.not_null()
.default("stdio"),
)
.col(ColumnDef::new(McpServers::Command).string().null())
.col(ColumnDef::new(McpServers::ArgsJson).string().null())
.col(ColumnDef::new(McpServers::Endpoint).string().null())
.col(ColumnDef::new(McpServers::EnvJson).string().null())
.col(
ColumnDef::new(McpServers::Enabled)
.integer()
.not_null()
.default(1),
)
.col(
ColumnDef::new(McpServers::PermissionPolicy)
.string()
.not_null()
.default("ask"),
)
.col(
ColumnDef::new(McpServers::Source)
.string()
.not_null()
.default("custom"),
)
.to_owned(),
)
.await?;
manager
.create_index(
Index::create()
.if_not_exists()
.name("idx_mcp_servers_enabled")
.table(McpServers::Table)
.col(McpServers::Enabled)
.to_owned(),
)
.await?;
// =================================================================
// 14. Tool Descriptors
// =================================================================
manager
.create_table(
Table::create()
.table(ToolDescriptors::Table)
.if_not_exists()
.col(
ColumnDef::new(ToolDescriptors::Id)
.string()
.not_null()
.primary_key(),
)
.col(
ColumnDef::new(ToolDescriptors::ServerId)
.string()
.not_null(),
)
.col(ColumnDef::new(ToolDescriptors::Name).string().not_null())
.col(ColumnDef::new(ToolDescriptors::Description).string().null())
.col(
ColumnDef::new(ToolDescriptors::InputSchemaJson)
.string()
.null(),
)
.foreign_key(
ForeignKey::create()
.from(ToolDescriptors::Table, ToolDescriptors::ServerId)
.to(McpServers::Table, McpServers::Id)
.on_delete(ForeignKeyAction::Cascade),
)
.to_owned(),
)
.await?;
manager
.create_index(
Index::create()
.if_not_exists()
.name("idx_tool_descriptors_server")
.table(ToolDescriptors::Table)
.col(ToolDescriptors::ServerId)
.to_owned(),
)
.await?;
// =================================================================
// 15. Tool Executions
// =================================================================
manager
.create_table(
Table::create()
.table(ToolExecutions::Table)
.if_not_exists()
.col(
ColumnDef::new(ToolExecutions::Id)
.string()
.not_null()
.primary_key(),
)
.col(
ColumnDef::new(ToolExecutions::ConversationId)
.string()
.not_null(),
)
.col(ColumnDef::new(ToolExecutions::MessageId).string().null())
.col(ColumnDef::new(ToolExecutions::ServerId).string().not_null())
.col(ColumnDef::new(ToolExecutions::ToolName).string().not_null())
.col(
ColumnDef::new(ToolExecutions::Status)
.string()
.not_null()
.default("pending"),
)
.col(ColumnDef::new(ToolExecutions::InputPreview).string().null())
.col(
ColumnDef::new(ToolExecutions::OutputPreview)
.string()
.null(),
)
.col(ColumnDef::new(ToolExecutions::ErrorMessage).string().null())
.col(ColumnDef::new(ToolExecutions::DurationMs).integer().null())
.col(
ColumnDef::new(ToolExecutions::CreatedAt)
.string()
.not_null()
.default(Expr::current_timestamp()),
)
.foreign_key(
ForeignKey::create()
.from(ToolExecutions::Table, ToolExecutions::ConversationId)
.to(Conversations::Table, Conversations::Id)
.on_delete(ForeignKeyAction::Cascade),
)
.to_owned(),
)
.await?;
manager
.create_index(
Index::create()
.if_not_exists()
.name("idx_tool_executions_conv")
.table(ToolExecutions::Table)
.col(ToolExecutions::ConversationId)
.to_owned(),
)
.await?;
manager
.create_index(
Index::create()
.if_not_exists()
.name("idx_tool_executions_msg")
.table(ToolExecutions::Table)
.col(ToolExecutions::MessageId)
.to_owned(),
)
.await?;
manager
.create_index(
Index::create()
.if_not_exists()
.name("idx_tool_executions_server")
.table(ToolExecutions::Table)
.col(ToolExecutions::ServerId)
.to_owned(),
)
.await?;
// =================================================================
// 16. Knowledge Bases
// =================================================================
manager
.create_table(
Table::create()
.table(KnowledgeBases::Table)
.if_not_exists()
.col(
ColumnDef::new(KnowledgeBases::Id)
.string()
.not_null()
.primary_key(),
)
.col(ColumnDef::new(KnowledgeBases::Name).string().not_null())
.col(ColumnDef::new(KnowledgeBases::Description).string().null())
.col(
ColumnDef::new(KnowledgeBases::EmbeddingProvider)
.string()
.null(),
)
.col(
ColumnDef::new(KnowledgeBases::Enabled)
.integer()
.not_null()
.default(1),
)
.to_owned(),
)
.await?;
manager
.create_index(
Index::create()
.if_not_exists()
.name("idx_knowledge_bases_enabled")
.table(KnowledgeBases::Table)
.col(KnowledgeBases::Enabled)
.to_owned(),
)
.await?;
// =================================================================
// 17. Knowledge Documents
// =================================================================
manager
.create_table(
Table::create()
.table(KnowledgeDocuments::Table)
.if_not_exists()
.col(
ColumnDef::new(KnowledgeDocuments::Id)
.string()
.not_null()
.primary_key(),
)
.col(
ColumnDef::new(KnowledgeDocuments::KnowledgeBaseId)
.string()
.not_null(),
)
.col(
ColumnDef::new(KnowledgeDocuments::Title)
.string()
.not_null(),
)
.col(
ColumnDef::new(KnowledgeDocuments::SourcePath)
.string()
.not_null(),
)
.col(
ColumnDef::new(KnowledgeDocuments::MimeType)
.string()
.not_null(),
)
.col(
ColumnDef::new(KnowledgeDocuments::SizeBytes)
.integer()
.not_null()
.default(0),
)
.col(
ColumnDef::new(KnowledgeDocuments::IndexingStatus)
.string()
.not_null()
.default("pending"),
)
.foreign_key(
ForeignKey::create()
.from(
KnowledgeDocuments::Table,
KnowledgeDocuments::KnowledgeBaseId,
)
.to(KnowledgeBases::Table, KnowledgeBases::Id)
.on_delete(ForeignKeyAction::Cascade),
)
.to_owned(),
)
.await?;
manager
.create_index(
Index::create()
.if_not_exists()
.name("idx_knowledge_documents_kb")
.table(KnowledgeDocuments::Table)
.col(KnowledgeDocuments::KnowledgeBaseId)
.to_owned(),
)
.await?;
// =================================================================
// 18. Retrieval Hits
// =================================================================
manager
.create_table(
Table::create()
.table(RetrievalHits::Table)
.if_not_exists()
.col(
ColumnDef::new(RetrievalHits::Id)
.string()
.not_null()
.primary_key(),
)
.col(
ColumnDef::new(RetrievalHits::ConversationId)
.string()
.not_null(),
)
.col(ColumnDef::new(RetrievalHits::MessageId).string().not_null())
.col(
ColumnDef::new(RetrievalHits::KnowledgeBaseId)
.string()
.not_null(),
)
.col(
ColumnDef::new(RetrievalHits::DocumentId)
.string()
.not_null(),
)
.col(ColumnDef::new(RetrievalHits::ChunkRef).string().not_null())
.col(
ColumnDef::new(RetrievalHits::Score)
.float()
.not_null()
.default(0.0),
)
.col(ColumnDef::new(RetrievalHits::Preview).string().not_null())
.foreign_key(
ForeignKey::create()
.from(RetrievalHits::Table, RetrievalHits::ConversationId)
.to(Conversations::Table, Conversations::Id)
.on_delete(ForeignKeyAction::Cascade),
)
.foreign_key(
ForeignKey::create()
.from(RetrievalHits::Table, RetrievalHits::KnowledgeBaseId)
.to(KnowledgeBases::Table, KnowledgeBases::Id)
.on_delete(ForeignKeyAction::Cascade),
)
.to_owned(),
)
.await?;
manager
.create_index(
Index::create()
.if_not_exists()
.name("idx_retrieval_hits_conv")
.table(RetrievalHits::Table)
.col(RetrievalHits::ConversationId)
.to_owned(),
)
.await?;
manager
.create_index(
Index::create()
.if_not_exists()
.name("idx_retrieval_hits_msg")
.table(RetrievalHits::Table)
.col(RetrievalHits::MessageId)
.to_owned(),
)
.await?;
manager
.create_index(
Index::create()
.if_not_exists()
.name("idx_retrieval_hits_kb")
.table(RetrievalHits::Table)
.col(RetrievalHits::KnowledgeBaseId)
.to_owned(),
)
.await?;
// =================================================================
// 19. Memory Namespaces
// =================================================================
manager
.create_table(
Table::create()
.table(MemoryNamespaces::Table)
.if_not_exists()
.col(
ColumnDef::new(MemoryNamespaces::Id)
.string()
.not_null()
.primary_key(),
)
.col(ColumnDef::new(MemoryNamespaces::Name).string().not_null())
.col(
ColumnDef::new(MemoryNamespaces::Scope)
.string()
.not_null()
.default("global"),
)
.col(ColumnDef::new(MemoryNamespaces::AppId).string().null())
.col(
ColumnDef::new(MemoryNamespaces::EmbeddingProvider)
.string()
.null(),
)
.to_owned(),
)
.await?;
manager
.create_index(
Index::create()
.if_not_exists()
.name("idx_memory_namespaces_scope")
.table(MemoryNamespaces::Table)
.col(MemoryNamespaces::Scope)
.to_owned(),
)
.await?;
// =================================================================
// 20. Memory Items
// =================================================================
manager
.create_table(
Table::create()
.table(MemoryItems::Table)
.if_not_exists()
.col(
ColumnDef::new(MemoryItems::Id)
.string()
.not_null()
.primary_key(),
)
.col(ColumnDef::new(MemoryItems::NamespaceId).string().not_null())
.col(ColumnDef::new(MemoryItems::Title).string().not_null())
.col(ColumnDef::new(MemoryItems::Content).string().not_null())
.col(
ColumnDef::new(MemoryItems::Source)
.string()
.not_null()
.default("manual"),
)
.col(
ColumnDef::new(MemoryItems::UpdatedAt)
.string()
.not_null()
.default(Expr::current_timestamp()),
)
.foreign_key(
ForeignKey::create()
.from(MemoryItems::Table, MemoryItems::NamespaceId)
.to(MemoryNamespaces::Table, MemoryNamespaces::Id)
.on_delete(ForeignKeyAction::Cascade),
)
.to_owned(),
)
.await?;
manager
.create_index(
Index::create()
.if_not_exists()
.name("idx_memory_items_ns")
.table(MemoryItems::Table)
.col(MemoryItems::NamespaceId)
.to_owned(),
)
.await?;
// =================================================================
// 21. Artifacts
// =================================================================
manager
.create_table(
Table::create()
.table(Artifacts::Table)
.if_not_exists()
.col(
ColumnDef::new(Artifacts::Id)
.string()
.not_null()
.primary_key(),
)
.col(
ColumnDef::new(Artifacts::ConversationId)
.string()
.not_null(),
)
.col(
ColumnDef::new(Artifacts::Kind)
.string()
.not_null()
.default("draft"),
)
.col(ColumnDef::new(Artifacts::Title).string().not_null())
.col(
ColumnDef::new(Artifacts::Content)
.string()
.not_null()
.default(""),
)
.col(
ColumnDef::new(Artifacts::Format)
.string()
.not_null()
.default("markdown"),
)
.col(
ColumnDef::new(Artifacts::Pinned)
.integer()
.not_null()
.default(0),
)
.col(
ColumnDef::new(Artifacts::UpdatedAt)
.string()
.not_null()
.default(Expr::current_timestamp()),
)
.foreign_key(
ForeignKey::create()
.from(Artifacts::Table, Artifacts::ConversationId)
.to(Conversations::Table, Conversations::Id)
.on_delete(ForeignKeyAction::Cascade),
)
.to_owned(),
)
.await?;
manager
.create_index(
Index::create()
.if_not_exists()
.name("idx_artifacts_conv")
.table(Artifacts::Table)
.col(Artifacts::ConversationId)
.to_owned(),
)
.await?;
manager
.create_index(
Index::create()
.if_not_exists()
.name("idx_artifacts_pinned")
.table(Artifacts::Table)
.col(Artifacts::Pinned)
.to_owned(),
)
.await?;
// =================================================================
// 22. Context Packs
// =================================================================
manager
.create_table(
Table::create()
.table(ContextPacks::Table)
.if_not_exists()
.col(
ColumnDef::new(ContextPacks::Id)
.string()
.not_null()
.primary_key(),
)
.col(ColumnDef::new(ContextPacks::AppId).string().not_null())
.col(ColumnDef::new(ContextPacks::Name).string().not_null())
.col(
ColumnDef::new(ContextPacks::Content)
.string()
.not_null()
.default(""),
)
.col(
ColumnDef::new(ContextPacks::EnabledByDefault)
.integer()
.not_null()
.default(1),
)
.foreign_key(
ForeignKey::create()
.from(ContextPacks::Table, ContextPacks::AppId)
.to(Apps::Table, Apps::Id)
.on_delete(ForeignKeyAction::Cascade),
)
.to_owned(),
)
.await?;
manager
.create_index(
Index::create()
.if_not_exists()
.name("idx_context_packs_app")
.table(ContextPacks::Table)
.col(ContextPacks::AppId)
.to_owned(),
)
.await?;
// =================================================================
// 23. Context Sources
// =================================================================
manager
.create_table(
Table::create()
.table(ContextSources::Table)
.if_not_exists()
.col(
ColumnDef::new(ContextSources::Id)
.string()
.not_null()
.primary_key(),
)
.col(
ColumnDef::new(ContextSources::ConversationId)
.string()
.not_null(),
)
.col(ColumnDef::new(ContextSources::MessageId).string().null())
.col(
ColumnDef::new(ContextSources::SourceType)
.string()
.not_null(),
)
.col(ColumnDef::new(ContextSources::RefId).string().not_null())
.col(ColumnDef::new(ContextSources::Title).string().not_null())
.col(
ColumnDef::new(ContextSources::Enabled)
.integer()
.not_null()
.default(1),
)
.col(ColumnDef::new(ContextSources::Summary).string().null())
.foreign_key(
ForeignKey::create()
.from(ContextSources::Table, ContextSources::ConversationId)
.to(Conversations::Table, Conversations::Id)
.on_delete(ForeignKeyAction::Cascade),
)
.to_owned(),
)
.await?;
manager
.create_index(
Index::create()
.if_not_exists()
.name("idx_context_sources_conv")
.table(ContextSources::Table)
.col(ContextSources::ConversationId)
.to_owned(),
)
.await?;
manager
.create_index(
Index::create()
.if_not_exists()
.name("idx_context_sources_msg")
.table(ContextSources::Table)
.col(ContextSources::MessageId)
.to_owned(),
)
.await?;
// =================================================================
// 24. Conversation Branches
// =================================================================
manager
.create_table(
Table::create()
.table(ConversationBranches::Table)
.if_not_exists()
.col(
ColumnDef::new(ConversationBranches::Id)
.string()
.not_null()
.primary_key(),
)
.col(
ColumnDef::new(ConversationBranches::ConversationId)
.string()
.not_null(),
)
.col(
ColumnDef::new(ConversationBranches::ParentMessageId)
.string()
.not_null(),
)
.col(
ColumnDef::new(ConversationBranches::BranchLabel)
.string()
.not_null(),
)
.col(
ColumnDef::new(ConversationBranches::BranchIndex)
.integer()
.not_null()
.default(0),
)
.col(
ColumnDef::new(ConversationBranches::ComparedMessageIdsJson)
.string()
.null(),
)
.col(
ColumnDef::new(ConversationBranches::CreatedAt)
.string()
.not_null()
.default(Expr::current_timestamp()),
)
.foreign_key(
ForeignKey::create()
.from(
ConversationBranches::Table,
ConversationBranches::ConversationId,
)
.to(Conversations::Table, Conversations::Id)
.on_delete(ForeignKeyAction::Cascade),
)
.to_owned(),
)
.await?;
manager
.create_index(
Index::create()
.if_not_exists()
.name("idx_conv_branches_parent")
.table(ConversationBranches::Table)
.col(ConversationBranches::ParentMessageId)
.to_owned(),
)
.await?;
// =================================================================
// 25. Backup Manifests
// =================================================================
manager
.create_table(
Table::create()
.table(BackupManifests::Table)
.if_not_exists()
.col(
ColumnDef::new(BackupManifests::Id)
.string()
.not_null()
.primary_key(),
)
.col(ColumnDef::new(BackupManifests::Version).string().not_null())
.col(
ColumnDef::new(BackupManifests::CreatedAt)
.string()
.not_null()
.default(Expr::current_timestamp()),
)
.col(
ColumnDef::new(BackupManifests::Encrypted)
.integer()
.not_null()
.default(0),
)
.col(
ColumnDef::new(BackupManifests::Checksum)
.string()
.not_null(),
)
.col(
ColumnDef::new(BackupManifests::ObjectCountsJson)
.string()
.not_null()
.default("{}"),
)
.col(
ColumnDef::new(BackupManifests::SourceAppVersion)
.string()
.not_null(),
)
.col(ColumnDef::new(BackupManifests::FilePath).string().null())
.col(
ColumnDef::new(BackupManifests::FileSize)
.big_integer()
.not_null()
.default(0),
)
.to_owned(),
)
.await?;
// =================================================================
// 26. Backup Targets
// =================================================================
manager
.create_table(
Table::create()
.table(BackupTargets::Table)
.if_not_exists()
.col(
ColumnDef::new(BackupTargets::Id)
.string()
.not_null()
.primary_key(),
)
.col(
ColumnDef::new(BackupTargets::Kind)
.string()
.not_null()
.default("local"),
)
.col(
ColumnDef::new(BackupTargets::ConfigJson)
.string()
.not_null()
.default("{}"),
)
.to_owned(),
)
.await?;
manager
.create_index(
Index::create()
.if_not_exists()
.name("idx_backup_targets_kind")
.table(BackupTargets::Table)
.col(BackupTargets::Kind)
.to_owned(),
)
.await?;
// =================================================================
// 27. Import Jobs
// =================================================================
manager
.create_table(
Table::create()
.table(ImportJobs::Table)
.if_not_exists()
.col(
ColumnDef::new(ImportJobs::Id)
.string()
.not_null()
.primary_key(),
)
.col(ColumnDef::new(ImportJobs::SourceType).string().not_null())
.col(
ColumnDef::new(ImportJobs::Status)
.string()
.not_null()
.default("scanning"),
)
.col(ColumnDef::new(ImportJobs::SummaryJson).string().null())
.col(
ColumnDef::new(ImportJobs::ConflictCount)
.integer()
.not_null()
.default(0),
)
.col(
ColumnDef::new(ImportJobs::CreatedAt)
.string()
.not_null()
.default(Expr::current_timestamp()),
)
.to_owned(),
)
.await?;
manager
.create_index(
Index::create()
.if_not_exists()
.name("idx_import_jobs_status")
.table(ImportJobs::Table)
.col(ImportJobs::Status)
.to_owned(),
)
.await?;
manager
.create_index(
Index::create()
.if_not_exists()
.name("idx_import_jobs_created")
.table(ImportJobs::Table)
.col(ImportJobs::CreatedAt)
.to_owned(),
)
.await?;
// =================================================================
// 28. Program Policies
// =================================================================
manager
.create_table(
Table::create()
.table(ProgramPolicies::Table)
.if_not_exists()
.col(
ColumnDef::new(ProgramPolicies::Id)
.string()
.not_null()
.primary_key(),
)
.col(
ColumnDef::new(ProgramPolicies::ProgramName)
.string()
.not_null()
.unique_key(),
)
.col(
ColumnDef::new(ProgramPolicies::AllowedProviderIdsJson)
.string()
.not_null()
.default("[]"),
)
.col(
ColumnDef::new(ProgramPolicies::AllowedModelIdsJson)
.string()
.not_null()
.default("[]"),
)
.col(
ColumnDef::new(ProgramPolicies::DefaultProviderId)
.string()
.null(),
)
.col(
ColumnDef::new(ProgramPolicies::DefaultModelId)
.string()
.null(),
)
.col(
ColumnDef::new(ProgramPolicies::RateLimitPerMinute)
.integer()
.null(),
)
.to_owned(),
)
.await?;
manager
.create_index(
Index::create()
.if_not_exists()
.name("idx_program_policies_name")
.table(ProgramPolicies::Table)
.col(ProgramPolicies::ProgramName)
.unique()
.to_owned(),
)
.await?;
// =================================================================
// 29. Gateway Diagnostics
// =================================================================
manager
.create_table(
Table::create()
.table(GatewayDiagnostics::Table)
.if_not_exists()
.col(
ColumnDef::new(GatewayDiagnostics::Id)
.string()
.not_null()
.primary_key(),
)
.col(
ColumnDef::new(GatewayDiagnostics::Category)
.string()
.not_null(),
)
.col(
ColumnDef::new(GatewayDiagnostics::Status)
.string()
.not_null()
.default("ok"),
)
.col(
ColumnDef::new(GatewayDiagnostics::Message)
.string()
.not_null(),
)
.col(
ColumnDef::new(GatewayDiagnostics::CreatedAt)
.string()
.not_null()
.default(Expr::current_timestamp()),
)
.to_owned(),
)
.await?;
manager
.create_index(
Index::create()
.if_not_exists()
.name("idx_gateway_diagnostics_cat")
.table(GatewayDiagnostics::Table)
.col(GatewayDiagnostics::Category)
.to_owned(),
)
.await?;
manager
.create_index(
Index::create()
.if_not_exists()
.name("idx_gateway_diagnostics_created")
.table(GatewayDiagnostics::Table)
.col(GatewayDiagnostics::CreatedAt)
.to_owned(),
)
.await?;
// =================================================================
// 30. Desktop State
// =================================================================
manager
.create_table(
Table::create()
.table(DesktopState::Table)
.if_not_exists()
.col(
ColumnDef::new(DesktopState::WindowKey)
.string()
.not_null()
.primary_key(),
)
.col(
ColumnDef::new(DesktopState::Width)
.integer()
.not_null()
.default(1200),
)
.col(
ColumnDef::new(DesktopState::Height)
.integer()
.not_null()
.default(800),
)
.col(ColumnDef::new(DesktopState::X).integer().null())
.col(ColumnDef::new(DesktopState::Y).integer().null())
.col(
ColumnDef::new(DesktopState::Maximized)
.integer()
.not_null()
.default(0),
)
.col(
ColumnDef::new(DesktopState::Visible)
.integer()
.not_null()
.default(1),
)
.to_owned(),
)
.await?;
// =================================================================
// 31. Stored Files
// =================================================================
manager
.create_table(
Table::create()
.table(StoredFiles::Table)
.if_not_exists()
.col(
ColumnDef::new(StoredFiles::Id)
.string()
.not_null()
.primary_key(),
)
.col(ColumnDef::new(StoredFiles::Hash).string().not_null())
.col(
ColumnDef::new(StoredFiles::OriginalName)
.string()
.not_null(),
)
.col(
ColumnDef::new(StoredFiles::MimeType)
.string()
.not_null()
.default("application/octet-stream"),
)
.col(ColumnDef::new(StoredFiles::SizeBytes).integer().not_null())
.col(ColumnDef::new(StoredFiles::StoragePath).string().not_null())
.col(ColumnDef::new(StoredFiles::ConversationId).string().null())
.col(
ColumnDef::new(StoredFiles::CreatedAt)
.string()
.not_null()
.default(Expr::current_timestamp()),
)
.foreign_key(
ForeignKey::create()
.from(StoredFiles::Table, StoredFiles::ConversationId)
.to(Conversations::Table, Conversations::Id)
.on_delete(ForeignKeyAction::SetNull),
)
.to_owned(),
)
.await?;
manager
.create_index(
Index::create()
.if_not_exists()
.name("idx_stored_files_hash")
.table(StoredFiles::Table)
.col(StoredFiles::Hash)
.to_owned(),
)
.await?;
manager
.create_index(
Index::create()
.if_not_exists()
.name("idx_stored_files_conversation")
.table(StoredFiles::Table)
.col(StoredFiles::ConversationId)
.to_owned(),
)
.await?;
// =================================================================
// 32. Gateway Request Logs
// =================================================================
manager
.create_table(
Table::create()
.table(GatewayRequestLogs::Table)
.if_not_exists()
.col(
ColumnDef::new(GatewayRequestLogs::Id)
.string()
.not_null()
.primary_key(),
)
.col(
ColumnDef::new(GatewayRequestLogs::KeyId)
.string()
.not_null(),
)
.col(
ColumnDef::new(GatewayRequestLogs::KeyName)
.string()
.not_null(),
)
.col(
ColumnDef::new(GatewayRequestLogs::Method)
.string()
.not_null(),
)
.col(ColumnDef::new(GatewayRequestLogs::Path).string().not_null())
.col(ColumnDef::new(GatewayRequestLogs::Model).string().null())
.col(
ColumnDef::new(GatewayRequestLogs::ProviderId)
.string()
.null(),
)
.col(
ColumnDef::new(GatewayRequestLogs::StatusCode)
.integer()
.not_null(),
)
.col(
ColumnDef::new(GatewayRequestLogs::DurationMs)
.integer()
.not_null(),
)
.col(
ColumnDef::new(GatewayRequestLogs::RequestTokens)
.integer()
.not_null()
.default(0),
)
.col(
ColumnDef::new(GatewayRequestLogs::ResponseTokens)
.integer()
.not_null()
.default(0),
)
.col(
ColumnDef::new(GatewayRequestLogs::ErrorMessage)
.string()
.null(),
)
.col(
ColumnDef::new(GatewayRequestLogs::CreatedAt)
.integer()
.not_null(),
)
.to_owned(),
)
.await?;
// =================================================================
// FTS5 virtual table and triggers (raw SQL for SQLite-specific features)
// =================================================================
let db = manager.get_connection();
db.execute_unprepared(
"CREATE VIRTUAL TABLE IF NOT EXISTS messages_fts USING fts5(\
content, \
content=messages, \
content_rowid=rowid, \
tokenize='unicode61'\
)",
)
.await?;
db.execute_unprepared(
"CREATE TRIGGER IF NOT EXISTS messages_ai AFTER INSERT ON messages BEGIN \
INSERT INTO messages_fts(rowid, content) VALUES (new.rowid, new.content); \
END",
)
.await?;
db.execute_unprepared(
"CREATE TRIGGER IF NOT EXISTS messages_ad AFTER DELETE ON messages BEGIN \
INSERT INTO messages_fts(messages_fts, rowid, content) VALUES('delete', old.rowid, old.content); \
END",
)
.await?;
db.execute_unprepared(
"CREATE TRIGGER IF NOT EXISTS messages_au AFTER UPDATE OF content ON messages BEGIN \
INSERT INTO messages_fts(messages_fts, rowid, content) VALUES('delete', old.rowid, old.content); \
INSERT INTO messages_fts(rowid, content) VALUES (new.rowid, new.content); \
END",
)
.await?;
Ok(())
}
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
// Drop triggers and FTS5 table first
let db = manager.get_connection();
db.execute_unprepared("DROP TRIGGER IF EXISTS messages_au")
.await?;
db.execute_unprepared("DROP TRIGGER IF EXISTS messages_ad")
.await?;
db.execute_unprepared("DROP TRIGGER IF EXISTS messages_ai")
.await?;
db.execute_unprepared("DROP TABLE IF EXISTS messages_fts")
.await?;
// Drop tables in reverse creation order
macro_rules! drop_tbl {
($t:expr) => {
manager
.drop_table(Table::drop().table($t).if_exists().to_owned())
.await?;
};
}
drop_tbl!(GatewayRequestLogs::Table);
drop_tbl!(StoredFiles::Table);
drop_tbl!(DesktopState::Table);
drop_tbl!(GatewayDiagnostics::Table);
drop_tbl!(ProgramPolicies::Table);
drop_tbl!(ImportJobs::Table);
drop_tbl!(BackupTargets::Table);
drop_tbl!(BackupManifests::Table);
drop_tbl!(ConversationBranches::Table);
drop_tbl!(ContextSources::Table);
drop_tbl!(ContextPacks::Table);
drop_tbl!(Artifacts::Table);
drop_tbl!(MemoryItems::Table);
drop_tbl!(MemoryNamespaces::Table);
drop_tbl!(RetrievalHits::Table);
drop_tbl!(KnowledgeDocuments::Table);
drop_tbl!(KnowledgeBases::Table);
drop_tbl!(ToolExecutions::Table);
drop_tbl!(ToolDescriptors::Table);
drop_tbl!(McpServers::Table);
drop_tbl!(SearchCitations::Table);
drop_tbl!(SearchProviders::Table);
drop_tbl!(Settings::Table);
drop_tbl!(GatewayUsage::Table);
drop_tbl!(GatewayKeys::Table);
drop_tbl!(Apps::Table);
drop_tbl!(Categories::Table);
drop_tbl!(Messages::Table);
drop_tbl!(Conversations::Table);
drop_tbl!(Models::Table);
drop_tbl!(ProviderKeys::Table);
drop_tbl!(Providers::Table);
Ok(())
}
}