diff --git a/Cargo.lock b/Cargo.lock index 40c4050..5198daa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1708,7 +1708,7 @@ dependencies = [ "tokio", "tokio-rustls", "tower-service", - "webpki-roots", + "webpki-roots 1.0.6", ] [[package]] @@ -2225,6 +2225,14 @@ version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +[[package]] +name = "migration" +version = "0.1.0" +dependencies = [ + "async-std", + "sea-orm-migration", +] + [[package]] name = "mime" version = "0.3.17" @@ -2480,11 +2488,12 @@ dependencies = [ "handlebars", "hex", "jsonwebtoken", + "migration", "mockall", "nxmesh-core", "nxmesh-proto", "sea-orm", - "sea-orm-migration 2.0.0-rc.35", + "sea-orm-migration", "serde", "serde_json", "sha2", @@ -3272,7 +3281,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "webpki-roots", + "webpki-roots 1.0.6", ] [[package]] @@ -3593,15 +3602,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "sea-orm-migration" -version = "0.1.0" -dependencies = [ - "async-std", - "sea-orm", - "sea-orm-migration 2.0.0-rc.35", -] - [[package]] name = "sea-orm-migration" version = "2.0.0-rc.35" @@ -3961,6 +3961,7 @@ dependencies = [ "once_cell", "percent-encoding", "rust_decimal", + "rustls", "serde", "serde_json", "sha2", @@ -3972,6 +3973,7 @@ dependencies = [ "tracing", "url", "uuid", + "webpki-roots 0.26.11", ] [[package]] @@ -5075,6 +5077,15 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki-roots" +version = "0.26.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" +dependencies = [ + "webpki-roots 1.0.6", +] + [[package]] name = "webpki-roots" version = "1.0.6" diff --git a/Cargo.toml b/Cargo.toml index a24a136..ba838a7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ members = [ "crates/nxmesh-master", "crates/nxmesh-agent", "crates/nxmesh-cli", - "migrations/sea-orm", + "migration", ] resolver = "3" @@ -36,7 +36,10 @@ tonic = "0.11" prost = "0.12" # Database -sea-orm = { version = "2.0.0-rc", features = ["sqlx-postgres", "runtime-tokio-native-tls"] } +sea-orm = { version = "2.0.0-rc", features = [ + "sqlx-postgres", + "runtime-tokio-native-tls", +] } sea-orm-migration = "2.0.0-rc" # Async @@ -48,7 +51,10 @@ toml = "0.8" config = "0.14" # HTTP client -reqwest = { version = "0.12", default-features = false, features = ["rustls-tls", "json"] } +reqwest = { version = "0.12", default-features = false, features = [ + "rustls-tls", + "json", +] } # Crypto sha2 = "0.10" diff --git a/docs/project-structure.md b/docs/project-structure.md index 90b5f0c..6802d96 100644 --- a/docs/project-structure.md +++ b/docs/project-structure.md @@ -169,14 +169,13 @@ nxmesh/ │ │ ├── utils/ # Utilities │ │ └── styles/ # CSS/Tailwind │ └── public/ -│ +│ │ # Build output (dist/) is embedded into master binary │ # Master serves static files at root path ("/") │ -├── migrations/ # Database migrations -│ └── sea-orm/ -│ ├── Cargo.toml -│ └── src/ +├── migration/ # Database migrations +│ ├── Cargo.toml +│ └── src/ │ ├── tests/ # Integration tests │ ├── integration/ diff --git a/justfile b/justfile index 5a55cbc..c69b1c1 100644 --- a/justfile +++ b/justfile @@ -102,8 +102,8 @@ db-migrate: # Create new database migration db-new-migration name: - @echo "📝 Creating new migration: {{name}}" - sea-orm-cli migrate generate {{name}} + @echo "📝 Creating new migration: {{ name }}" + sea-orm-cli migrate generate {{ name }} # Reset database (drop and recreate) db-reset: @@ -115,6 +115,17 @@ db-console: @echo "🐘 Connecting to PostgreSQL..." psql $DATABASE_URL +# Generate SeaORM entities from database schema +db-generate: + @echo "⚙️ Generating SeaORM entities from database..." + sea-orm-cli generate entity \ + --database-url $DATABASE_URL \ + --output-dir crates/nxmesh-master/src/db/entities \ + --with-serde both \ + --with-copy-enums \ + --date-time-crate chrono + @echo "✅ Entities generated at crates/nxmesh-master/src/db/entities/" + # ============================================================================= # Testing Commands # ============================================================================= @@ -195,9 +206,10 @@ nginx-reload: bash .devcontainer/scripts/nginx-reload.sh # Full nginx control via shared PID namespace + # Usage: just nginx-ctl nginx-ctl cmd="reload": - @bash .devcontainer/scripts/nginx-ctl.sh {{cmd}} + @bash .devcontainer/scripts/nginx-ctl.sh {{ cmd }} # Quick status check nginx-status: @@ -264,3 +276,37 @@ logs: docs: @echo "📚 Generating documentation..." cargo doc --open 2>/dev/null || cargo doc + +# ============================================================================= +# API Client Generation Commands +# ============================================================================= + +# Generate OpenAPI spec from backend (requires backend to be running) +gen-openapi: + @echo "📋 Generating OpenAPI spec from backend..." + @curl -s http://localhost:8080/api/openapi.json > /tmp/openapi.json + @echo "✅ OpenAPI spec saved to /tmp/openapi.json" + +# Generate TypeScript API client from OpenAPI spec +gen-api-client: + @echo "⚡ Generating TypeScript API client..." + @if [ ! -f /tmp/openapi.json ]; then \ + echo "❌ OpenAPI spec not found. Run 'just gen-openapi' first (backend must be running)."; \ + exit 1; \ + fi + cd frontend && bunx openapi-typescript /tmp/openapi.json -o src/api/schema.ts + @echo "✅ API client generated at frontend/src/api/schema.ts" + +# Full API generation workflow (start backend, generate spec, generate client, stop backend) +gen-api: start-backend-temp + @just gen-openapi + @just gen-api-client + @echo "✅ API client generation complete!" + +# Internal: Start backend temporarily for API generation +start-backend-temp: + @echo "🔧 Starting backend temporarily..." + @cargo build --package nxmesh-master + @cargo run --package nxmesh-master & + @sleep 5 + @echo "✅ Backend ready" diff --git a/migration/Cargo.toml b/migration/Cargo.toml new file mode 100644 index 0000000..84ad511 --- /dev/null +++ b/migration/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "migration" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +name = "migration" +path = "src/lib.rs" + +[dependencies] +async-std = { version = "1", features = ["attributes", "tokio1"] } + +[dependencies.sea-orm-migration] +version = "2.0.0-rc" +features = [ + "runtime-tokio-rustls", + "sqlx-postgres", +] diff --git a/migration/README.md b/migration/README.md new file mode 100644 index 0000000..3b438d8 --- /dev/null +++ b/migration/README.md @@ -0,0 +1,41 @@ +# Running Migrator CLI + +- Generate a new migration file + ```sh + cargo run -- generate MIGRATION_NAME + ``` +- Apply all pending migrations + ```sh + cargo run + ``` + ```sh + cargo run -- up + ``` +- Apply first 10 pending migrations + ```sh + cargo run -- up -n 10 + ``` +- Rollback last applied migrations + ```sh + cargo run -- down + ``` +- Rollback last 10 applied migrations + ```sh + cargo run -- down -n 10 + ``` +- Drop all tables from the database, then reapply all migrations + ```sh + cargo run -- fresh + ``` +- Rollback all applied migrations, then reapply all migrations + ```sh + cargo run -- refresh + ``` +- Rollback all applied migrations + ```sh + cargo run -- reset + ``` +- Check the status of all migrations + ```sh + cargo run -- status + ``` diff --git a/migration/src/lib.rs b/migration/src/lib.rs new file mode 100644 index 0000000..110a7bd --- /dev/null +++ b/migration/src/lib.rs @@ -0,0 +1,26 @@ +pub use sea_orm_migration::prelude::*; + +mod m20240301_000001_create_organizations; +mod m20240301_000002_create_workspaces; +mod m20240301_000003_create_agents; +mod m20240301_000004_create_virtual_hosts; +mod m20240301_000005_create_upstreams; +mod m20240301_000006_create_certificates; +mod m20240301_000007_create_users; + +pub struct Migrator; + +#[async_trait::async_trait] +impl MigratorTrait for Migrator { + fn migrations() -> Vec> { + vec![ + Box::new(m20240301_000001_create_organizations::Migration), + Box::new(m20240301_000002_create_workspaces::Migration), + Box::new(m20240301_000003_create_agents::Migration), + Box::new(m20240301_000004_create_virtual_hosts::Migration), + Box::new(m20240301_000005_create_upstreams::Migration), + Box::new(m20240301_000006_create_certificates::Migration), + Box::new(m20240301_000007_create_users::Migration), + ] + } +} diff --git a/migration/src/m20240301_000001_create_organizations.rs b/migration/src/m20240301_000001_create_organizations.rs new file mode 100644 index 0000000..ca831b9 --- /dev/null +++ b/migration/src/m20240301_000001_create_organizations.rs @@ -0,0 +1,59 @@ +use sea_orm_migration::prelude::*; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .create_table( + Table::create() + .table(Organizations::Table) + .if_not_exists() + .col( + ColumnDef::new(Organizations::Id) + .uuid() + .not_null() + .primary_key(), + ) + .col(ColumnDef::new(Organizations::Name).string().not_null()) + .col( + ColumnDef::new(Organizations::Slug) + .string() + .not_null() + .unique_key(), + ) + .col( + ColumnDef::new(Organizations::CreatedAt) + .timestamp_with_time_zone() + .not_null(), + ) + .col( + ColumnDef::new(Organizations::UpdatedAt) + .timestamp_with_time_zone() + .not_null(), + ) + .col(ColumnDef::new(Organizations::Settings).json()) + .to_owned(), + ) + .await + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .drop_table(Table::drop().table(Organizations::Table).to_owned()) + .await + } +} + +#[derive(DeriveIden)] +enum Organizations { + Table, + Id, + Name, + Slug, + CreatedAt, + UpdatedAt, + Settings, +} diff --git a/migration/src/m20240301_000002_create_workspaces.rs b/migration/src/m20240301_000002_create_workspaces.rs new file mode 100644 index 0000000..55c84ab --- /dev/null +++ b/migration/src/m20240301_000002_create_workspaces.rs @@ -0,0 +1,88 @@ +use sea_orm_migration::prelude::*; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .create_table( + Table::create() + .table(Workspaces::Table) + .if_not_exists() + .col( + ColumnDef::new(Workspaces::Id) + .uuid() + .not_null() + .primary_key(), + ) + .col( + ColumnDef::new(Workspaces::OrganizationId) + .uuid() + .not_null(), + ) + .col(ColumnDef::new(Workspaces::Name).string().not_null()) + .col( + ColumnDef::new(Workspaces::Slug) + .string() + .not_null(), + ) + .col( + ColumnDef::new(Workspaces::CreatedAt) + .timestamp_with_time_zone() + .not_null(), + ) + .col( + ColumnDef::new(Workspaces::UpdatedAt) + .timestamp_with_time_zone() + .not_null(), + ) + .foreign_key( + ForeignKey::create() + .name("fk_workspace_organization") + .from(Workspaces::Table, Workspaces::OrganizationId) + .to(Organizations::Table, Organizations::Id) + .on_delete(ForeignKeyAction::Cascade), + ) + .to_owned(), + ) + .await?; + + // Create unique index on organization_id + slug + manager + .create_index( + Index::create() + .unique() + .name("idx_workspaces_org_slug") + .table(Workspaces::Table) + .col(Workspaces::OrganizationId) + .col(Workspaces::Slug) + .to_owned(), + ) + .await + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .drop_table(Table::drop().table(Workspaces::Table).to_owned()) + .await + } +} + +#[derive(DeriveIden)] +enum Workspaces { + Table, + Id, + OrganizationId, + Name, + Slug, + CreatedAt, + UpdatedAt, +} + +#[derive(DeriveIden)] +enum Organizations { + Table, + Id, +} diff --git a/migration/src/m20240301_000003_create_agents.rs b/migration/src/m20240301_000003_create_agents.rs new file mode 100644 index 0000000..cb6d958 --- /dev/null +++ b/migration/src/m20240301_000003_create_agents.rs @@ -0,0 +1,90 @@ +use sea_orm_migration::prelude::*; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .create_table( + Table::create() + .table(Agents::Table) + .if_not_exists() + .col( + ColumnDef::new(Agents::Id) + .uuid() + .not_null() + .primary_key(), + ) + .col( + ColumnDef::new(Agents::WorkspaceId) + .uuid() + .not_null(), + ) + .col(ColumnDef::new(Agents::Name).string().not_null()) + .col(ColumnDef::new(Agents::Hostname).string().not_null()) + .col(ColumnDef::new(Agents::IpAddress).string()) + .col(ColumnDef::new(Agents::Version).string()) + .col(ColumnDef::new(Agents::State).string().not_null()) + .col(ColumnDef::new(Agents::DeploymentMode).string()) + .col( + ColumnDef::new(Agents::LastSeenAt) + .timestamp_with_time_zone(), + ) + .col(ColumnDef::new(Agents::Capabilities).json()) + .col(ColumnDef::new(Agents::Labels).json()) + .col(ColumnDef::new(Agents::TokenHash).string()) + .col( + ColumnDef::new(Agents::CreatedAt) + .timestamp_with_time_zone() + .not_null(), + ) + .col( + ColumnDef::new(Agents::UpdatedAt) + .timestamp_with_time_zone() + .not_null(), + ) + .foreign_key( + ForeignKey::create() + .name("fk_agent_workspace") + .from(Agents::Table, Agents::WorkspaceId) + .to(Workspaces::Table, Workspaces::Id) + .on_delete(ForeignKeyAction::Cascade), + ) + .to_owned(), + ) + .await + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .drop_table(Table::drop().table(Agents::Table).to_owned()) + .await + } +} + +#[derive(DeriveIden)] +enum Agents { + Table, + Id, + WorkspaceId, + Name, + Hostname, + IpAddress, + Version, + State, + DeploymentMode, + LastSeenAt, + Capabilities, + Labels, + TokenHash, + CreatedAt, + UpdatedAt, +} + +#[derive(DeriveIden)] +enum Workspaces { + Table, + Id, +} diff --git a/migration/src/m20240301_000004_create_virtual_hosts.rs b/migration/src/m20240301_000004_create_virtual_hosts.rs new file mode 100644 index 0000000..f480945 --- /dev/null +++ b/migration/src/m20240301_000004_create_virtual_hosts.rs @@ -0,0 +1,115 @@ +use sea_orm_migration::prelude::*; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .create_table( + Table::create() + .table(VirtualHosts::Table) + .if_not_exists() + .col( + ColumnDef::new(VirtualHosts::Id) + .uuid() + .not_null() + .primary_key(), + ) + .col( + ColumnDef::new(VirtualHosts::WorkspaceId) + .uuid() + .not_null(), + ) + .col(ColumnDef::new(VirtualHosts::Name).string().not_null()) + .col( + ColumnDef::new(VirtualHosts::ServerName) + .string() + .not_null(), + ) + .col( + ColumnDef::new(VirtualHosts::ListenPort) + .integer() + .not_null(), + ) + .col( + ColumnDef::new(VirtualHosts::SslEnabled) + .boolean() + .not_null() + .default(false), + ) + .col(ColumnDef::new(VirtualHosts::SslCertificateId).uuid()) + .col(ColumnDef::new(VirtualHosts::Locations).json()) + .col( + ColumnDef::new(VirtualHosts::Http2Enabled) + .boolean() + .not_null() + .default(false), + ) + .col( + ColumnDef::new(VirtualHosts::Http3Enabled) + .boolean() + .not_null() + .default(false), + ) + .col( + ColumnDef::new(VirtualHosts::GzipEnabled) + .boolean() + .not_null() + .default(true), + ) + .col(ColumnDef::new(VirtualHosts::TargetAgents).json()) + .col( + ColumnDef::new(VirtualHosts::CreatedAt) + .timestamp_with_time_zone() + .not_null(), + ) + .col( + ColumnDef::new(VirtualHosts::UpdatedAt) + .timestamp_with_time_zone() + .not_null(), + ) + .foreign_key( + ForeignKey::create() + .name("fk_vh_workspace") + .from(VirtualHosts::Table, VirtualHosts::WorkspaceId) + .to(Workspaces::Table, Workspaces::Id) + .on_delete(ForeignKeyAction::Cascade), + ) + .to_owned(), + ) + .await + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .drop_table(Table::drop().table(VirtualHosts::Table).to_owned()) + .await + } +} + +#[derive(DeriveIden)] +enum VirtualHosts { + Table, + Id, + WorkspaceId, + Name, + ServerName, + ListenPort, + SslEnabled, + SslCertificateId, + Locations, + Http2Enabled, + Http3Enabled, + GzipEnabled, + TargetAgents, + CreatedAt, + UpdatedAt, +} + +#[derive(DeriveIden)] +enum Workspaces { + Table, + Id, +} diff --git a/migration/src/m20240301_000005_create_upstreams.rs b/migration/src/m20240301_000005_create_upstreams.rs new file mode 100644 index 0000000..173edea --- /dev/null +++ b/migration/src/m20240301_000005_create_upstreams.rs @@ -0,0 +1,83 @@ +use sea_orm_migration::prelude::*; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .create_table( + Table::create() + .table(Upstreams::Table) + .if_not_exists() + .col( + ColumnDef::new(Upstreams::Id) + .uuid() + .not_null() + .primary_key(), + ) + .col( + ColumnDef::new(Upstreams::WorkspaceId) + .uuid() + .not_null(), + ) + .col(ColumnDef::new(Upstreams::Name).string().not_null()) + .col( + ColumnDef::new(Upstreams::Algorithm) + .string() + .not_null(), + ) + .col(ColumnDef::new(Upstreams::Servers).json()) + .col(ColumnDef::new(Upstreams::HealthCheck).json()) + .col(ColumnDef::new(Upstreams::KeepaliveConnections).integer()) + .col(ColumnDef::new(Upstreams::KeepaliveTimeout).integer()) + .col( + ColumnDef::new(Upstreams::CreatedAt) + .timestamp_with_time_zone() + .not_null(), + ) + .col( + ColumnDef::new(Upstreams::UpdatedAt) + .timestamp_with_time_zone() + .not_null(), + ) + .foreign_key( + ForeignKey::create() + .name("fk_upstream_workspace") + .from(Upstreams::Table, Upstreams::WorkspaceId) + .to(Workspaces::Table, Workspaces::Id) + .on_delete(ForeignKeyAction::Cascade), + ) + .to_owned(), + ) + .await + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .drop_table(Table::drop().table(Upstreams::Table).to_owned()) + .await + } +} + +#[derive(DeriveIden)] +enum Upstreams { + Table, + Id, + WorkspaceId, + Name, + Algorithm, + Servers, + HealthCheck, + KeepaliveConnections, + KeepaliveTimeout, + CreatedAt, + UpdatedAt, +} + +#[derive(DeriveIden)] +enum Workspaces { + Table, + Id, +} diff --git a/migration/src/m20240301_000006_create_certificates.rs b/migration/src/m20240301_000006_create_certificates.rs new file mode 100644 index 0000000..13939bb --- /dev/null +++ b/migration/src/m20240301_000006_create_certificates.rs @@ -0,0 +1,99 @@ +use sea_orm_migration::prelude::*; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .create_table( + Table::create() + .table(Certificates::Table) + .if_not_exists() + .col( + ColumnDef::new(Certificates::Id) + .uuid() + .not_null() + .primary_key(), + ) + .col( + ColumnDef::new(Certificates::WorkspaceId) + .uuid() + .not_null(), + ) + .col( + ColumnDef::new(Certificates::Domain) + .string() + .not_null(), + ) + .col( + ColumnDef::new(Certificates::IsWildcard) + .boolean() + .not_null() + .default(false), + ) + .col(ColumnDef::new(Certificates::Provider).string()) + .col(ColumnDef::new(Certificates::Status).string()) + .col(ColumnDef::new(Certificates::IssuedAt).timestamp_with_time_zone()) + .col(ColumnDef::new(Certificates::ExpiresAt).timestamp_with_time_zone()) + .col( + ColumnDef::new(Certificates::AutoRenew) + .boolean() + .not_null() + .default(true), + ) + .col(ColumnDef::new(Certificates::CertificatePem).text()) + .col(ColumnDef::new(Certificates::PrivateKeyPem).text()) + .col( + ColumnDef::new(Certificates::CreatedAt) + .timestamp_with_time_zone() + .not_null(), + ) + .col( + ColumnDef::new(Certificates::UpdatedAt) + .timestamp_with_time_zone() + .not_null(), + ) + .foreign_key( + ForeignKey::create() + .name("fk_certificate_workspace") + .from(Certificates::Table, Certificates::WorkspaceId) + .to(Workspaces::Table, Workspaces::Id) + .on_delete(ForeignKeyAction::Cascade), + ) + .to_owned(), + ) + .await + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .drop_table(Table::drop().table(Certificates::Table).to_owned()) + .await + } +} + +#[derive(DeriveIden)] +enum Certificates { + Table, + Id, + WorkspaceId, + Domain, + IsWildcard, + Provider, + Status, + IssuedAt, + ExpiresAt, + AutoRenew, + CertificatePem, + PrivateKeyPem, + CreatedAt, + UpdatedAt, +} + +#[derive(DeriveIden)] +enum Workspaces { + Table, + Id, +} diff --git a/migration/src/m20240301_000007_create_users.rs b/migration/src/m20240301_000007_create_users.rs new file mode 100644 index 0000000..e63f830 --- /dev/null +++ b/migration/src/m20240301_000007_create_users.rs @@ -0,0 +1,74 @@ +use sea_orm_migration::prelude::*; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .create_table( + Table::create() + .table(Users::Table) + .if_not_exists() + .col( + ColumnDef::new(Users::Id) + .uuid() + .not_null() + .primary_key(), + ) + .col(ColumnDef::new(Users::Email).string().not_null().unique_key()) + .col(ColumnDef::new(Users::PasswordHash).string().not_null()) + .col(ColumnDef::new(Users::Name).string()) + .col(ColumnDef::new(Users::Role).string().not_null()) + .col( + ColumnDef::new(Users::OrganizationId) + .uuid(), + ) + .col( + ColumnDef::new(Users::CreatedAt) + .timestamp_with_time_zone() + .not_null(), + ) + .col( + ColumnDef::new(Users::UpdatedAt) + .timestamp_with_time_zone() + .not_null(), + ) + .foreign_key( + ForeignKey::create() + .name("fk_user_organization") + .from(Users::Table, Users::OrganizationId) + .to(Organizations::Table, Organizations::Id) + .on_delete(ForeignKeyAction::SetNull), + ) + .to_owned(), + ) + .await + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .drop_table(Table::drop().table(Users::Table).to_owned()) + .await + } +} + +#[derive(DeriveIden)] +enum Users { + Table, + Id, + Email, + PasswordHash, + Name, + Role, + OrganizationId, + CreatedAt, + UpdatedAt, +} + +#[derive(DeriveIden)] +enum Organizations { + Table, + Id, +} diff --git a/migration/src/main.rs b/migration/src/main.rs new file mode 100644 index 0000000..c6b6e48 --- /dev/null +++ b/migration/src/main.rs @@ -0,0 +1,6 @@ +use sea_orm_migration::prelude::*; + +#[async_std::main] +async fn main() { + cli::run_cli(migration::Migrator).await; +}