barycenter/migration/src/m20250101_000001_initial_schema.rs
Till Wegmueller b6bf4ceee0
feat: migrate from raw SQL to SeaORM migrations
Replace raw SQL CREATE TABLE statements with proper SeaORM migration
system. This eliminates verbose SQL logs on startup and provides
proper migration tracking and rollback support.

Changes:
- Add sea-orm-migration dependency and migration crate
- Create initial migration (m20250101_000001) with all 8 tables
- Update storage::init() to only connect to database
- Run migrations automatically in main.rs on startup
- Remove unused detect_backend() function and imports

The migration system properly handles both SQLite and PostgreSQL
backends with appropriate type handling (e.g., BIGSERIAL vs INTEGER
for auto-increment columns).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-02 21:42:58 +01:00

393 lines
12 KiB
Rust

use sea_orm_migration::{prelude::*, schema::*};
#[derive(DeriveMigrationName)]
pub struct Migration;
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
// Enable foreign keys for SQLite
if manager.get_database_backend() == sea_orm::DatabaseBackend::Sqlite {
manager
.get_connection()
.execute_unprepared("PRAGMA foreign_keys = ON")
.await?;
}
// Create clients table
manager
.create_table(
Table::create()
.table(Clients::Table)
.if_not_exists()
.col(
ColumnDef::new(Clients::ClientId)
.string()
.not_null()
.primary_key(),
)
.col(string(Clients::ClientSecret))
.col(string_null(Clients::ClientName))
.col(string(Clients::RedirectUris))
.col(big_integer(Clients::CreatedAt))
.to_owned(),
)
.await?;
// Create properties table
manager
.create_table(
Table::create()
.table(Properties::Table)
.if_not_exists()
.col(string(Properties::Owner))
.col(string(Properties::Key))
.col(string(Properties::Value))
.col(big_integer(Properties::UpdatedAt))
.primary_key(Index::create().col(Properties::Owner).col(Properties::Key))
.to_owned(),
)
.await?;
// Create auth_codes table
manager
.create_table(
Table::create()
.table(AuthCodes::Table)
.if_not_exists()
.col(
ColumnDef::new(AuthCodes::Code)
.string()
.not_null()
.primary_key(),
)
.col(string(AuthCodes::ClientId))
.col(string(AuthCodes::RedirectUri))
.col(string(AuthCodes::Scope))
.col(string(AuthCodes::Subject))
.col(string_null(AuthCodes::Nonce))
.col(string(AuthCodes::CodeChallenge))
.col(string(AuthCodes::CodeChallengeMethod))
.col(big_integer(AuthCodes::CreatedAt))
.col(big_integer(AuthCodes::ExpiresAt))
.col(
ColumnDef::new(AuthCodes::Consumed)
.big_integer()
.not_null()
.default(0),
)
.col(big_integer_null(AuthCodes::AuthTime))
.to_owned(),
)
.await?;
// Create access_tokens table
manager
.create_table(
Table::create()
.table(AccessTokens::Table)
.if_not_exists()
.col(
ColumnDef::new(AccessTokens::Token)
.string()
.not_null()
.primary_key(),
)
.col(string(AccessTokens::ClientId))
.col(string(AccessTokens::Subject))
.col(string(AccessTokens::Scope))
.col(big_integer(AccessTokens::CreatedAt))
.col(big_integer(AccessTokens::ExpiresAt))
.col(
ColumnDef::new(AccessTokens::Revoked)
.big_integer()
.not_null()
.default(0),
)
.to_owned(),
)
.await?;
// Create users table
manager
.create_table(
Table::create()
.table(Users::Table)
.if_not_exists()
.col(
ColumnDef::new(Users::Subject)
.string()
.not_null()
.primary_key(),
)
.col(
ColumnDef::new(Users::Username)
.string()
.not_null()
.unique_key(),
)
.col(string(Users::PasswordHash))
.col(string_null(Users::Email))
.col(
ColumnDef::new(Users::EmailVerified)
.big_integer()
.not_null()
.default(0),
)
.col(big_integer(Users::CreatedAt))
.col(
ColumnDef::new(Users::Enabled)
.big_integer()
.not_null()
.default(1),
)
.to_owned(),
)
.await?;
// Create sessions table
manager
.create_table(
Table::create()
.table(Sessions::Table)
.if_not_exists()
.col(
ColumnDef::new(Sessions::SessionId)
.string()
.not_null()
.primary_key(),
)
.col(string(Sessions::Subject))
.col(big_integer(Sessions::AuthTime))
.col(big_integer(Sessions::CreatedAt))
.col(big_integer(Sessions::ExpiresAt))
.col(string_null(Sessions::UserAgent))
.col(string_null(Sessions::IpAddress))
.to_owned(),
)
.await?;
// Create index on sessions.expires_at
manager
.create_index(
Index::create()
.if_not_exists()
.name("idx_sessions_expires")
.table(Sessions::Table)
.col(Sessions::ExpiresAt)
.to_owned(),
)
.await?;
// Create refresh_tokens table
manager
.create_table(
Table::create()
.table(RefreshTokens::Table)
.if_not_exists()
.col(
ColumnDef::new(RefreshTokens::Token)
.string()
.not_null()
.primary_key(),
)
.col(string(RefreshTokens::ClientId))
.col(string(RefreshTokens::Subject))
.col(string(RefreshTokens::Scope))
.col(big_integer(RefreshTokens::CreatedAt))
.col(big_integer(RefreshTokens::ExpiresAt))
.col(
ColumnDef::new(RefreshTokens::Revoked)
.big_integer()
.not_null()
.default(0),
)
.col(string_null(RefreshTokens::ParentToken))
.to_owned(),
)
.await?;
// Create index on refresh_tokens.expires_at
manager
.create_index(
Index::create()
.if_not_exists()
.name("idx_refresh_tokens_expires")
.table(RefreshTokens::Table)
.col(RefreshTokens::ExpiresAt)
.to_owned(),
)
.await?;
// Create job_executions table with backend-specific ID type
let id_col = match manager.get_database_backend() {
sea_orm::DatabaseBackend::Postgres => ColumnDef::new(JobExecutions::Id)
.big_integer()
.not_null()
.auto_increment()
.primary_key()
.to_owned(),
_ => ColumnDef::new(JobExecutions::Id)
.integer()
.not_null()
.auto_increment()
.primary_key()
.to_owned(),
};
manager
.create_table(
Table::create()
.table(JobExecutions::Table)
.if_not_exists()
.col(id_col)
.col(string(JobExecutions::JobName))
.col(big_integer(JobExecutions::StartedAt))
.col(big_integer_null(JobExecutions::CompletedAt))
.col(big_integer_null(JobExecutions::Success))
.col(string_null(JobExecutions::ErrorMessage))
.col(big_integer_null(JobExecutions::RecordsProcessed))
.to_owned(),
)
.await?;
// Create index on job_executions.started_at
manager
.create_index(
Index::create()
.if_not_exists()
.name("idx_job_executions_started")
.table(JobExecutions::Table)
.col(JobExecutions::StartedAt)
.to_owned(),
)
.await?;
Ok(())
}
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.drop_table(Table::drop().table(JobExecutions::Table).to_owned())
.await?;
manager
.drop_table(Table::drop().table(RefreshTokens::Table).to_owned())
.await?;
manager
.drop_table(Table::drop().table(Sessions::Table).to_owned())
.await?;
manager
.drop_table(Table::drop().table(Users::Table).to_owned())
.await?;
manager
.drop_table(Table::drop().table(AccessTokens::Table).to_owned())
.await?;
manager
.drop_table(Table::drop().table(AuthCodes::Table).to_owned())
.await?;
manager
.drop_table(Table::drop().table(Properties::Table).to_owned())
.await?;
manager
.drop_table(Table::drop().table(Clients::Table).to_owned())
.await?;
Ok(())
}
}
#[derive(DeriveIden)]
enum Clients {
Table,
ClientId,
ClientSecret,
ClientName,
RedirectUris,
CreatedAt,
}
#[derive(DeriveIden)]
enum Properties {
Table,
Owner,
Key,
Value,
UpdatedAt,
}
#[derive(DeriveIden)]
enum AuthCodes {
Table,
Code,
ClientId,
RedirectUri,
Scope,
Subject,
Nonce,
CodeChallenge,
CodeChallengeMethod,
CreatedAt,
ExpiresAt,
Consumed,
AuthTime,
}
#[derive(DeriveIden)]
enum AccessTokens {
Table,
Token,
ClientId,
Subject,
Scope,
CreatedAt,
ExpiresAt,
Revoked,
}
#[derive(DeriveIden)]
enum Users {
Table,
Subject,
Username,
PasswordHash,
Email,
EmailVerified,
CreatedAt,
Enabled,
}
#[derive(DeriveIden)]
enum Sessions {
Table,
SessionId,
Subject,
AuthTime,
CreatedAt,
ExpiresAt,
UserAgent,
IpAddress,
}
#[derive(DeriveIden)]
enum RefreshTokens {
Table,
Token,
ClientId,
Subject,
Scope,
CreatedAt,
ExpiresAt,
Revoked,
ParentToken,
}
#[derive(DeriveIden)]
enum JobExecutions {
Table,
Id,
JobName,
StartedAt,
CompletedAt,
Success,
ErrorMessage,
RecordsProcessed,
}