diff --git a/Cargo.lock b/Cargo.lock index bc29385..ca263c1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2763,6 +2763,8 @@ dependencies = [ "console", "lazy_static", "linked-hash-map", + "ron", + "serde", "similar", "yaml-rust", ] @@ -3282,7 +3284,9 @@ dependencies = [ "async-trait", "clap 3.2.25", "dotenv", + "insta", "sea-orm-migration", + "sqlx", "tracing", "tracing-subscriber", ] @@ -4592,6 +4596,17 @@ dependencies = [ "serde", ] +[[package]] +name = "ron" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88073939a61e5b7680558e6be56b419e208420c2adb92be54921fa6b72283f1a" +dependencies = [ + "base64 0.13.1", + "bitflags", + "serde", +] + [[package]] name = "rumqttc" version = "0.21.0" diff --git a/migration/Cargo.toml b/migration/Cargo.toml index ff18ef1..31f9134 100644 --- a/migration/Cargo.toml +++ b/migration/Cargo.toml @@ -19,5 +19,10 @@ async-trait = { version = "0.1.68" } [dependencies.sea-orm-migration] version = "0.11.0" features = [ - "sqlx-postgres" + "sqlx-postgres", + 'runtime-tokio-rustls' ] + +[dev-dependencies] +insta = { version = "1.29.0", features = ['ron'] } +sqlx = { version = "0.6.3", features = ['runtime-tokio-rustls'] } diff --git a/migration/src/carts/m20230603_120814_carts.rs b/migration/src/carts/m20230603_120814_carts.rs index e7ebb24..234cee6 100644 --- a/migration/src/carts/m20230603_120814_carts.rs +++ b/migration/src/carts/m20230603_120814_carts.rs @@ -1,31 +1,49 @@ use sea_orm_migration::prelude::*; use sea_query::expr::SimpleExpr; -/// ```sql -/// id character varying NOT NULL, -/// email character varying, -/// billing_address_id character varying, -/// shipping_address_id character varying, -/// region_id character varying NOT NULL, -/// customer_id character varying, -/// payment_id character varying, -/// type public.cart_types DEFAULT 'default'::public.cart_types NOT NULL, -/// completed_at timestamp with time zone, -/// created_at timestamp with time zone DEFAULT now() NOT NULL, -/// updated_at timestamp with time zone DEFAULT now() NOT NULL, -/// deleted_at timestamp with time zone, -/// metadata jsonb, -/// idempotency_key character varying, -/// context jsonb, -/// payment_authorized_at timestamp with time zone, -/// sales_channel_id character varying -/// ``` +use crate::sea_orm::Iterable; +use crate::{ts_def_now_not_null, DropTable, IntoColumnDef}; + #[derive(DeriveMigrationName)] pub struct Migration; #[async_trait::async_trait] impl MigrationTrait for Migration { - async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + async fn up(&self, m: &SchemaManager) -> Result<(), DbErr> { + Self::create_carts(m).await?; + Ok(()) + } + + async fn down(&self, m: &SchemaManager) -> Result<(), DbErr> { + self.drop_table(m, Cart::Carts).await?; + Ok(()) + } +} + +impl Migration { + /// ```sql + /// CREATE TABLE carts + /// ( + /// id uuid NOT NULL, + /// email character varying, + /// billing_address_id uuid, + /// shipping_address_id uuid, + /// region_id uuid NOT NULL, + /// customer_id uuid, + /// payment_id uuid, + /// type cart_types DEFAULT 'default'::cart_types NOT NULL, + /// completed_at timestamp with time zone, + /// created_at timestamp with time zone DEFAULT now() NOT NULL, + /// updated_at timestamp with time zone DEFAULT now() NOT NULL, + /// deleted_at timestamp with time zone, + /// metadata jsonb, + /// idempotency_key character varying, + /// context jsonb, + /// payment_authorized_at timestamp with time zone, + /// sales_channel_id uuid + /// ); + /// ``` + async fn create_carts(manager: &SchemaManager<'_>) -> Result<(), DbErr> { manager .create_table( Table::create() @@ -37,48 +55,35 @@ impl MigrationTrait for Migration { .default(SimpleExpr::Custom("public.uuid_generate_v4()".into())) .primary_key(), ) - .col(ColumnDef::new(Cart::Email).string()) - .col(ColumnDef::new(Cart::BillingAddressId).uuid()) - .col(ColumnDef::new(Cart::ShippingAddressId).uuid()) - .col(ColumnDef::new(Cart::RegionId).uuid().not_null()) - .col(ColumnDef::new(Cart::CustomerId).uuid()) - .col(ColumnDef::new(Cart::PaymentId).uuid()) + .col(Cart::Email.col().string()) + .col(Cart::BillingAddressId.col().uuid()) + .col(Cart::ShippingAddressId.col().uuid()) + .col(Cart::RegionId.col().uuid().not_null()) + .col(Cart::CustomerId.col().uuid()) + .col(Cart::PaymentId.col().uuid()) .col( - ColumnDef::new(Cart::CartType) - .string() - .default(SimpleExpr::Custom("'default'::public.cart_types".into())) + Cart::CartType + .col() + .enumeration( + crate::types::CartType::CartTypes, + crate::types::CartType::iter().skip(1), + ) + .default(crate::types::CartType::Default.to_string()) .not_null(), ) - .col(ColumnDef::new(Cart::CompletedAt).timestamp()) - .col( - ColumnDef::new(Cart::CreatedAt) - .timestamp() - .default(SimpleExpr::Custom("now()".into())) - .not_null(), - ) - .col( - ColumnDef::new(Cart::UpdatedAt) - .timestamp() - .default(SimpleExpr::Custom("now()".into())) - .not_null(), - ) - .col(ColumnDef::new(Cart::DeletedAt).timestamp()) - .col(ColumnDef::new(Cart::Metadata).json_binary()) - .col(ColumnDef::new(Cart::IdempotencyKey).uuid()) - .col(ColumnDef::new(Cart::Context).json_binary()) - .col(ColumnDef::new(Cart::PaymentAuthorizedAt).timestamp()) - .col(ColumnDef::new(Cart::SalesChannelId).uuid()) + .col(Cart::CompletedAt.col().timestamp()) + .col(ts_def_now_not_null!(Cart::CreatedAt)) + .col(ts_def_now_not_null!(Cart::UpdatedAt)) + .col(Cart::DeletedAt.col().timestamp()) + .col(Cart::Metadata.col().json_binary()) + .col(Cart::IdempotencyKey.col().uuid()) + .col(Cart::Context.col().json_binary()) + .col(Cart::PaymentAuthorizedAt.col().timestamp()) + .col(Cart::SalesChannelId.col().uuid()) .to_owned(), ) .await } - - async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { - manager - .drop_table(Table::drop().table(Cart::Carts).to_owned()) - .await?; - Ok(()) - } } #[derive(Iden)] diff --git a/migration/src/carts/m20230603_120815_cart_discounts.rs b/migration/src/carts/m20230603_120815_cart_discounts.rs index f740fe6..ddfefc1 100644 --- a/migration/src/carts/m20230603_120815_cart_discounts.rs +++ b/migration/src/carts/m20230603_120815_cart_discounts.rs @@ -1,5 +1,7 @@ use sea_orm_migration::prelude::*; +use crate::{to_pk2_name, CreateIndexExt, DropTable, IntoColumnDef}; + /// ```sql /// CREATE TABLE cart_discounts /// ( @@ -17,47 +19,94 @@ 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(CartDiscount::CartDiscounts) - .col(ColumnDef::new(CartDiscount::CartId).uuid().not_null()) - .col(ColumnDef::new(CartDiscount::DiscountId).uuid().not_null()) - .to_owned(), - ) - .await?; - manager - .create_table( - Table::create() - .table(CartGiftCard::CartGiftCards) - .col(ColumnDef::new(CartGiftCard::CartId).uuid().not_null()) - .col(ColumnDef::new(CartGiftCard::GiftCardId).uuid().not_null()) - .to_owned(), - ) - .await?; + async fn up(&self, m: &SchemaManager) -> Result<(), DbErr> { + Self::create_cart_discounts(m).await?; + Self::create_cart_gift_cards(m).await?; Ok(()) } - async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { - manager - .drop_table(Table::drop().table(CartDiscount::CartDiscounts).to_owned()) - .await?; - manager - .drop_table(Table::drop().table(CartGiftCard::CartGiftCards).to_owned()) - .await?; + async fn down(&self, m: &SchemaManager) -> Result<(), DbErr> { + self.drop_table(m, CartDiscount::CartDiscounts).await?; + self.drop_table(m, CartGiftCard::CartGiftCards).await?; Ok(()) } } -#[derive(Iden)] +impl Migration { + /// ```sql + /// CREATE TABLE cart_discounts + /// ( + /// cart_id uuid NOT NULL, + /// discount_id uuid NOT NULL + /// ); + /// ``` + async fn create_cart_discounts(m: &SchemaManager<'_>) -> Result<(), DbErr> { + m.create_table( + Table::create() + .if_not_exists() + .table(CartDiscount::CartDiscounts) + .col(CartDiscount::CartId.col().uuid().not_null()) + .col(CartDiscount::DiscountId.col().uuid().not_null()) + .to_owned(), + ) + .await?; + + m.create_2col_idx( + CartDiscount::CartDiscounts, + CartDiscount::CartId, + CartDiscount::DiscountId, + ) + .await?; + + Ok(()) + } + + /// ```sql + /// CREATE TABLE cart_gift_cards + /// ( + /// cart_id uuid NOT NULL, + /// gift_card_id uuid NOT NULL + /// ); + /// ``` + async fn create_cart_gift_cards(m: &SchemaManager<'_>) -> Result<(), DbErr> { + m.create_table( + Table::create() + .table(CartGiftCard::CartGiftCards) + .if_not_exists() + .col(CartGiftCard::CartId.col().uuid().not_null()) + .col(CartGiftCard::GiftCardId.col().uuid().not_null()) + .to_owned(), + ) + .await?; + + m.create_index( + IndexCreateStatement::new() + .table(CartGiftCard::CartGiftCards) + .col(CartGiftCard::CartId) + .col(CartGiftCard::GiftCardId) + .name(&to_pk2_name( + CartGiftCard::CartGiftCards, + CartGiftCard::CartId, + CartGiftCard::GiftCardId, + )) + .primary() + .if_not_exists() + .to_owned(), + ) + .await?; + + Ok(()) + } +} + +#[derive(Iden, Clone, Copy)] enum CartDiscount { CartDiscounts, CartId, DiscountId, } -#[derive(Iden)] +#[derive(Iden, Copy, Clone)] enum CartGiftCard { CartGiftCards, CartId, diff --git a/migration/src/checkouts/m20230603_120814_checkouts.rs b/migration/src/checkouts/m20230603_120814_checkouts.rs index 98a4fd0..dcb4a06 100644 --- a/migration/src/checkouts/m20230603_120814_checkouts.rs +++ b/migration/src/checkouts/m20230603_120814_checkouts.rs @@ -3,7 +3,7 @@ use sea_orm_migration::prelude::*; use crate::sea_orm::Iterable; use crate::{ auto_uuid_not_null, create_type, drop_type, to_fk_name, to_pk2_name, ts_def_now_not_null, - DropTable, IntoColumnDef, + CreateIndexExt, DropTable, IntoColumnDef, }; #[derive(DeriveMigrationName)] @@ -122,9 +122,11 @@ impl Migration { m.create_table( Table::create() .table(Order::Orders) + .if_not_exists() .col(auto_uuid_not_null!(Order::Id)) .col( - ColumnDef::new(Order::Status) + Order::Status + .col() .enumeration( crate::types::OrderStatus::OrderStatuses, crate::types::OrderStatus::iter().skip(1), @@ -133,7 +135,8 @@ impl Migration { .not_null(), ) .col( - ColumnDef::new(Order::FulfillmentStatus) + Order::FulfillmentStatus + .col() .enumeration( crate::types::OrderFulfillmentStatus::OrderFulfillmentStatuses, crate::types::OrderFulfillmentStatus::iter().take(1), @@ -141,31 +144,32 @@ impl Migration { .not_null(), ) .col( - ColumnDef::new(Order::PaymentStatus) + Order::PaymentStatus + .col() .enumeration( crate::types::OrderPaymentStatus::OrderPaymentStatuses, crate::types::OrderPaymentStatus::iter().take(1), ) .not_null(), ) - .col(ColumnDef::new(Order::DisplayId).uuid()) - .col(ColumnDef::new(Order::CartId).uuid()) - .col(ColumnDef::new(Order::CustomerId).uuid()) - .col(ColumnDef::new(Order::Email).uuid()) - .col(ColumnDef::new(Order::BillingAddressId).uuid()) - .col(ColumnDef::new(Order::ShippingAddressId).uuid()) - .col(ColumnDef::new(Order::RegionId).uuid()) - .col(ColumnDef::new(Order::CurrencyCode).uuid()) - .col(ColumnDef::new(Order::TaxRate).uuid()) - .col(ColumnDef::new(Order::CanceledAt).uuid()) - .col(ColumnDef::new(Order::CreatedAt).uuid()) - .col(ColumnDef::new(Order::UpdatedAt).uuid()) - .col(ColumnDef::new(Order::Metadata).uuid()) - .col(ColumnDef::new(Order::IdempotencyKey).uuid()) - .col(ColumnDef::new(Order::DraftOrderId).uuid()) - .col(ColumnDef::new(Order::NoNotification).uuid()) - .col(ColumnDef::new(Order::ExternalId).uuid()) - .col(ColumnDef::new(Order::SalesChannelId).uuid()) + .col(Order::DisplayId.col().uuid()) + .col(Order::CartId.col().uuid()) + .col(Order::CustomerId.col().uuid()) + .col(Order::Email.col().uuid()) + .col(Order::BillingAddressId.col().uuid()) + .col(Order::ShippingAddressId.col().uuid()) + .col(Order::RegionId.col().uuid()) + .col(Order::CurrencyCode.col().uuid()) + .col(Order::TaxRate.col().uuid()) + .col(Order::CanceledAt.col().uuid()) + .col(Order::CreatedAt.col().uuid()) + .col(Order::UpdatedAt.col().uuid()) + .col(Order::Metadata.col().uuid()) + .col(Order::IdempotencyKey.col().uuid()) + .col(Order::DraftOrderId.col().uuid()) + .col(Order::NoNotification.col().uuid()) + .col(Order::ExternalId.col().uuid()) + .col(Order::SalesChannelId.col().uuid()) .to_owned(), ) .await?; @@ -183,11 +187,20 @@ impl Migration { m.create_table( Table::create() .table(OrderDiscount::OrderDiscounts) - .col(ColumnDef::new(OrderDiscount::OrderId).uuid().not_null()) - .col(ColumnDef::new(OrderDiscount::DiscountId).uuid().not_null()) + .if_not_exists() + .col(OrderDiscount::OrderId.col().uuid().not_null()) + .col(OrderDiscount::DiscountId.col().uuid().not_null()) .to_owned(), ) .await?; + + m.create_2col_idx( + OrderDiscount::OrderDiscounts, + OrderDiscount::OrderId, + OrderDiscount::DiscountId, + ) + .await?; + Ok(()) } @@ -216,22 +229,23 @@ impl Migration { m.create_table( Table::create() .table(OrderEdit::OrderEdits) + .if_not_exists() .col(auto_uuid_not_null!(OrderEdit::Id)) .col(ts_def_now_not_null!(OrderEdit::CreatedAt)) .col(ts_def_now_not_null!(OrderEdit::UpdatedAt)) - .col(ColumnDef::new(OrderEdit::OrderId).uuid().not_null()) - .col(ColumnDef::new(OrderEdit::InternalNote).string()) - .col(ColumnDef::new(OrderEdit::CreatedBy).uuid().not_null()) - .col(ColumnDef::new(OrderEdit::RequestedBy).uuid()) - .col(ColumnDef::new(OrderEdit::RequestedAt).timestamp()) - .col(ColumnDef::new(OrderEdit::ConfirmedBy).uuid()) - .col(ColumnDef::new(OrderEdit::ConfirmedAt).timestamp()) - .col(ColumnDef::new(OrderEdit::DeclinedBy).uuid()) - .col(ColumnDef::new(OrderEdit::DeclinedReason).string()) - .col(ColumnDef::new(OrderEdit::DeclinedAt).timestamp()) - .col(ColumnDef::new(OrderEdit::CanceledBy).uuid()) - .col(ColumnDef::new(OrderEdit::CanceledAt).timestamp()) - .col(ColumnDef::new(OrderEdit::PaymentCollectionId).uuid()) + .col(OrderEdit::OrderId.col().uuid().not_null()) + .col(OrderEdit::InternalNote.col().string()) + .col(OrderEdit::CreatedBy.col().uuid().not_null()) + .col(OrderEdit::RequestedBy.col().uuid()) + .col(OrderEdit::RequestedAt.col().timestamp()) + .col(OrderEdit::ConfirmedBy.col().uuid()) + .col(OrderEdit::ConfirmedAt.col().timestamp()) + .col(OrderEdit::DeclinedBy.col().uuid()) + .col(OrderEdit::DeclinedReason.col().string()) + .col(OrderEdit::DeclinedAt.col().timestamp()) + .col(OrderEdit::CanceledBy.col().uuid()) + .col(OrderEdit::CanceledAt.col().timestamp()) + .col(OrderEdit::PaymentCollectionId.col().uuid()) .to_owned(), ) .await?; @@ -249,11 +263,20 @@ impl Migration { m.create_table( Table::create() .table(OrderGiftCard::OrderGiftCards) - .col(ColumnDef::new(OrderGiftCard::OrderId).uuid().not_null()) - .col(ColumnDef::new(OrderGiftCard::GiftCardId).uuid().not_null()) + .if_not_exists() + .col(OrderGiftCard::OrderId.col().uuid().not_null()) + .col(OrderGiftCard::GiftCardId.col().uuid().not_null()) .to_owned(), ) .await?; + + m.create_2col_idx( + OrderGiftCard::OrderGiftCards, + OrderGiftCard::OrderId, + OrderGiftCard::GiftCardId, + ) + .await?; + Ok(()) } @@ -421,31 +444,20 @@ impl Migration { Table::create() .table(PaymentCollectionPayment::PaymentCollectionPayments) .col( - ColumnDef::new(PaymentCollectionPayment::PaymentCollectionId) - .uuid() - .not_null(), - ) - .col( - ColumnDef::new(PaymentCollectionPayment::PaymentId) + PaymentCollectionPayment::PaymentCollectionId + .col() .uuid() .not_null(), ) + .col(PaymentCollectionPayment::PaymentId.col().uuid().not_null()) .to_owned(), ) .await?; - m.create_index( - IndexCreateStatement::new() - .table(PaymentCollectionPayment::PaymentCollectionPayments) - .col(PaymentCollectionPayment::PaymentCollectionId) - .col(PaymentCollectionPayment::PaymentId) - .primary() - .name(&to_pk2_name( - PaymentCollectionPayment::PaymentCollectionPayments, - PaymentCollectionPayment::PaymentCollectionId, - PaymentCollectionPayment::PaymentId, - )) - .to_owned(), + m.create_2col_idx( + PaymentCollectionPayment::PaymentCollectionPayments, + PaymentCollectionPayment::PaymentCollectionId, + PaymentCollectionPayment::PaymentId, ) .await?; @@ -494,18 +506,10 @@ impl Migration { ) .await?; - m.create_index( - IndexCreateStatement::new() - .table(PaymentCollectionSession::PaymentCollectionSessions) - .col(PaymentCollectionSession::PaymentCollectionId) - .col(PaymentCollectionSession::PaymentSessionId) - .primary() - .name(&to_pk2_name( - PaymentCollectionSession::PaymentCollectionSessions, - PaymentCollectionSession::PaymentCollectionId, - PaymentCollectionSession::PaymentSessionId, - )) - .to_owned(), + m.create_2col_idx( + PaymentCollectionSession::PaymentCollectionSessions, + PaymentCollectionSession::PaymentCollectionId, + PaymentCollectionSession::PaymentSessionId, ) .await?; Ok(()) @@ -589,7 +593,7 @@ impl Migration { } } -#[derive(Iden)] +#[derive(Iden, Copy, Clone)] pub enum Order { Orders, Id, @@ -616,14 +620,14 @@ pub enum Order { SalesChannelId, } -#[derive(Iden)] +#[derive(Iden, Copy, Clone)] pub enum OrderDiscount { OrderDiscounts, OrderId, DiscountId, } -#[derive(Iden)] +#[derive(Iden, Copy, Clone)] pub enum OrderEdit { OrderEdits, Id, @@ -644,14 +648,14 @@ pub enum OrderEdit { PaymentCollectionId, } -#[derive(Iden)] +#[derive(Iden, Copy, Clone)] pub enum OrderGiftCard { OrderGiftCards, OrderId, GiftCardId, } -#[derive(Iden)] +#[derive(Iden, Copy, Clone)] pub enum OrderItemChange { OrderItemChanges, Id, @@ -664,7 +668,7 @@ pub enum OrderItemChange { LineItemId, } -#[derive(Iden)] +#[derive(Iden, Copy, Clone)] pub enum Payment { Payments, Id, @@ -702,21 +706,21 @@ pub enum PaymentCollection { CreatedBy, } -#[derive(Iden)] +#[derive(Iden, Copy, Clone)] pub enum PaymentCollectionPayment { PaymentCollectionPayments, PaymentCollectionId, PaymentId, } -#[derive(Iden)] +#[derive(Iden, Copy, Clone)] pub enum PaymentCollectionSession { PaymentCollectionSessions, PaymentCollectionId, PaymentSessionId, } -#[derive(Iden)] +#[derive(Iden, Copy, Clone)] pub enum PaymentProvider { PaymentProviders, Id, diff --git a/migration/src/checkouts/m20230603_120815_items.rs b/migration/src/checkouts/m20230603_120815_items.rs index 1b32817..4426696 100644 --- a/migration/src/checkouts/m20230603_120815_items.rs +++ b/migration/src/checkouts/m20230603_120815_items.rs @@ -2,8 +2,8 @@ use sea_orm_migration::prelude::*; use crate::constraint::Check; use crate::{ - auto_uuid_not_null, create_constraint, create_type, drop_type, to_fk_name, to_pk2_name, - ts_def_now_not_null, AsIden, + auto_uuid_not_null, create_type, drop_type, to_fk_name, to_pk2_name, ts_def_now_not_null, + AsIden, CreateConstraint, }; #[derive(DeriveMigrationName)] @@ -55,20 +55,30 @@ impl Migration { /// ); /// ``` async fn create_line_items(&self, m: &SchemaManager<'_>) -> Result<(), DbErr> { - create_constraint( - m, + m.create_constraint( LineItem::LineItems, - Check::lower_eq(LineItem::ShippedQuantity, LineItem::FulfilledQuantity), + Check::less_eq(LineItem::ShippedQuantity, LineItem::FulfilledQuantity), ) .await?; - create_constraint( - m, + m.create_constraint( LineItem::LineItems, Check::greater(LineItem::Quantity, 0.iden()), ) .await?; + m.create_constraint( + LineItem::LineItems, + Check::less_eq(LineItem::ReturnedQuantity, LineItem::Quantity), + ) + .await?; + + m.create_constraint( + LineItem::LineItems, + Check::less_eq(LineItem::FulfilledQuantity, LineItem::Quantity), + ) + .await?; + Ok(()) } diff --git a/migration/src/discounts/m20230603_120815_gift_carts.rs b/migration/src/discounts/m20230603_120815_gift_carts.rs index c895edc..72985bc 100644 --- a/migration/src/discounts/m20230603_120815_gift_carts.rs +++ b/migration/src/discounts/m20230603_120815_gift_carts.rs @@ -1,6 +1,6 @@ use sea_orm_migration::prelude::*; -use crate::{auto_uuid_not_null, ts_def_now_not_null, IntoColumnDef}; +use crate::{auto_uuid_not_null, ts_def_now_not_null, DropTable, IntoColumnDef}; #[derive(DeriveMigrationName)] pub struct Migration; @@ -14,14 +14,9 @@ impl MigrationTrait for Migration { } async fn down(&self, m: &SchemaManager) -> Result<(), DbErr> { - m.drop_table(Table::drop().table(GiftCard::GiftCards).to_owned()) + self.drop_table(m, GiftCard::GiftCards).await?; + self.drop_table(m, GiftCardTransaction::GiftCardTransactions) .await?; - m.drop_table( - Table::drop() - .table(GiftCardTransaction::GiftCardTransactions) - .to_owned(), - ) - .await?; Ok(()) } diff --git a/migration/src/jobs/m20230603_120814_batch_jobs.rs b/migration/src/jobs/m20230603_120814_batch_jobs.rs index de5bb99..385fc50 100644 --- a/migration/src/jobs/m20230603_120814_batch_jobs.rs +++ b/migration/src/jobs/m20230603_120814_batch_jobs.rs @@ -1,78 +1,68 @@ use sea_orm_migration::prelude::*; use sea_query::expr::SimpleExpr; +use crate::{auto_uuid_not_null, ts_def_now_not_null, DropTable, IntoColumnDef}; + #[derive(DeriveMigrationName)] pub struct Migration; #[async_trait::async_trait] impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + Self::create_batch_jobs(manager).await + } + + async fn down(&self, m: &SchemaManager) -> Result<(), DbErr> { + self.drop_table(m, BatchJob::BatchJobs).await?; + Ok(()) + } +} + +impl Migration { + /// ```sql + /// CREATE TABLE batch_jobs + /// ( + /// id uuid NOT NULL, + /// type text NOT NULL, + /// created_by character varying, + /// context jsonb, + /// result jsonb, + /// dry_run boolean DEFAULT false NOT NULL, + /// created_at timestamp with time zone DEFAULT now() NOT NULL, + /// pre_processed_at timestamp with time zone, + /// confirmed_at timestamp with time zone, + /// processing_at timestamp with time zone, + /// completed_at timestamp with time zone, + /// failed_at timestamp with time zone, + /// canceled_at timestamp with time zone, + /// updated_at timestamp with time zone DEFAULT now() NOT NULL, + /// deleted_at timestamp with time zone + /// ); + /// ``` + async fn create_batch_jobs(manager: &SchemaManager<'_>) -> Result<(), DbErr> { manager .create_table( Table::create() - // id character varying NOT NULL, - // type text NOT NULL, - // created_by character varying, - // context jsonb, - // result jsonb, - // dry_run boolean DEFAULT false NOT NULL, - // created_at timestamp with time zone DEFAULT now() NOT NULL, - // pre_processed_at timestamp with time zone, - // confirmed_at timestamp with time zone, - // processing_at timestamp with time zone, - // completed_at timestamp with time zone, - // failed_at timestamp with time zone, - // canceled_at timestamp with time zone, - // updated_at timestamp with time zone DEFAULT now() NOT NULL, - // deleted_at timestamp with time zone .table(BatchJob::BatchJobs) - .col( - ColumnDef::new(BatchJob::Id) - .uuid() - .not_null() - .default(SimpleExpr::Custom("public.uuid_generate_v4()".into())) - .primary_key(), - ) - .col(ColumnDef::new(BatchJob::BatchType).string().not_null()) - .col(ColumnDef::new(BatchJob::CreatedBy).uuid()) - .col(ColumnDef::new(BatchJob::Context).json_binary()) - .col(ColumnDef::new(BatchJob::Result).json_binary()) - .col( - ColumnDef::new(BatchJob::DryRun) - .boolean() - .default(false) - .not_null(), - ) - .col( - ColumnDef::new(BatchJob::CreatedAt) - .timestamp() - .default(SimpleExpr::Custom("now()".into())) - .not_null(), - ) - .col(ColumnDef::new(BatchJob::PreProcessedAt).timestamp()) - .col(ColumnDef::new(BatchJob::ConfirmedAt).timestamp()) - .col(ColumnDef::new(BatchJob::ProcessingAt).timestamp()) - .col(ColumnDef::new(BatchJob::CompletedAt).timestamp()) - .col(ColumnDef::new(BatchJob::FailedAt).timestamp()) - .col(ColumnDef::new(BatchJob::CanceledAt).timestamp()) - .col( - ColumnDef::new(BatchJob::UpdatedAt) - .timestamp() - .default(SimpleExpr::Custom("now()".into())) - .not_null(), - ) - .col(ColumnDef::new(BatchJob::DeletedAt).timestamp()) + .col(auto_uuid_not_null!(BatchJob::Id)) + .col(BatchJob::BatchType.col().string().not_null()) + .col(BatchJob::CreatedBy.col().uuid()) + .col(BatchJob::Context.col().json_binary()) + .col(BatchJob::Result.col().json_binary()) + .col(BatchJob::DryRun.col().boolean().default(false).not_null()) + .col(ts_def_now_not_null!(BatchJob::CreatedAt)) + .col(BatchJob::PreProcessedAt.col().timestamp()) + .col(BatchJob::ConfirmedAt.col().timestamp()) + .col(BatchJob::ProcessingAt.col().timestamp()) + .col(BatchJob::CompletedAt.col().timestamp()) + .col(BatchJob::FailedAt.col().timestamp()) + .col(BatchJob::CanceledAt.col().timestamp()) + .col(ts_def_now_not_null!(BatchJob::UpdatedAt)) + .col(BatchJob::DeletedAt.col().timestamp()) .to_owned(), ) .await } - - async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { - manager - .drop_table(Table::drop().table(BatchJob::BatchJobs).to_owned()) - .await?; - Ok(()) - } } #[derive(Iden)] diff --git a/migration/src/lib.rs b/migration/src/lib.rs index 8b1d806..72cc00c 100644 --- a/migration/src/lib.rs +++ b/migration/src/lib.rs @@ -1,5 +1,3 @@ -use std::fmt::{Display, Formatter}; - pub use sea_orm_migration::prelude::*; pub mod carts; @@ -16,238 +14,8 @@ pub mod checkouts; pub use checkouts::*; pub mod shipping; pub use shipping::*; +mod sea_ext; +pub use sea_ext::*; pub mod types; pub use types::*; - -use crate::constraint::Check; - -#[macro_export] -macro_rules! ts_def_now_not_null { - ($identifier: expr) => { - ColumnDef::new($identifier) - .timestamp() - .default(SimpleExpr::Custom("now()".into())) - .not_null() - }; -} - -#[macro_export] -macro_rules! auto_uuid_not_null { - ($identifier: expr) => { - ColumnDef::new($identifier) - .uuid() - .not_null() - .default(SimpleExpr::Custom("public.uuid_generate_v4()".into())) - .primary_key() - }; -} - -// #[macro_export] -// macro_rules! col { -// ($name: expr $ty: expr) => { -// ColumnDef::new($name).$ty() -// }; -// ($name: expr auto uuid) => { -// $crate::auto_uuid_not_null!($name) -// }; -// ($name: expr $ty: expr default $value: expr) => { -// $crate::col!($name $ty).default($value) -// }; -// } - -#[async_trait::async_trait] -pub trait DropTable { - async fn drop_table( - &self, - manager: &SchemaManager<'_>, - identifier: I, - ) -> Result<(), DbErr> { - manager - .drop_table(Table::drop().table(identifier).to_owned()) - .await - } -} - -impl DropTable for T {} - -pub trait IntoColumnDef: IntoIden { - fn col(self) -> ColumnDef - where - Self: Sized, - { - ColumnDef::new(self) - } -} - -impl IntoColumnDef for T {} - -pub fn to_pk2_name(t1: T1, c1: C1, c2: C2) -> String { - let t1 = to_snake(t1.into_iden().to_string()); - let c1 = to_snake(c1.into_iden().to_string()); - let c2 = to_snake(c2.into_iden().to_string()); - - format!("pk_{}_{}_{}", t1, c1, c2) -} - -pub fn to_fk_name( - t1: T1, - c1: C1, - t2: T2, - c2: C2, -) -> String { - let t1 = to_snake(t1.into_iden().to_string()); - let c1 = to_snake(c1.into_iden().to_string()); - let t2 = to_snake(t2.into_iden().to_string()); - let c2 = to_snake(c2.into_iden().to_string()); - - format!("fk_{}_{}_{}_{}", t1, c1, t2, c2) -} - -fn to_snake(s: String) -> String { - s.chars().fold(String::new(), |mut m, c| { - if c.is_ascii_uppercase() { - m.push('_'); - } - m.push(c.to_ascii_lowercase()); - m - }) -} - -pub mod constraint { - use std::fmt::{Display, Formatter}; - - use crate::{Constraint, Iden}; - - pub enum Check { - Lower(Box, Box), - LowerEq(Box, Box), - Eq(Box, Box), - GreaterEq(Box, Box), - Greater(Box, Box), - Or(Box, Box), - } - - impl Check { - pub fn or(self, r: Check) -> Check { - Check::Or(Box::new(self), Box::new(r)) - } - - pub fn constraint(self) -> Constraint { - Constraint::Check(self) - } - - pub fn to_name(&self) -> String { - match self { - Check::Lower(l, r) => format!("{}_lw_{}", l.to_string(), r.to_string()), - Check::LowerEq(l, r) => format!("{}_le_{}", l.to_string(), r.to_string()), - Check::Eq(l, r) => format!("{}_eq_{}", l.to_string(), r.to_string()), - Check::GreaterEq(l, r) => format!("{}_ge_{}", l.to_string(), r.to_string()), - Check::Greater(l, r) => format!("{}_g_{}", l.to_string(), r.to_string()), - Check::Or(l, r) => format!("{}_or_{}", l.to_name(), r.to_name()), - } - } - } - - impl Display for Check { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - Check::Lower(l, r) => { - f.write_fmt(format_args!("{} < {}", l.to_string(), r.to_string())) - } - Check::LowerEq(l, r) => { - f.write_fmt(format_args!("{} <= {}", l.to_string(), r.to_string())) - } - Check::Eq(l, r) => { - f.write_fmt(format_args!("{} = {}", l.to_string(), r.to_string())) - } - Check::GreaterEq(l, r) => { - f.write_fmt(format_args!("{} >= {}", l.to_string(), r.to_string())) - } - Check::Greater(l, r) => { - f.write_fmt(format_args!("{} > {}", l.to_string(), r.to_string())) - } - Check::Or(l, r) => f.write_fmt(format_args!("({l}) OR ({r})")), - } - } - } - - impl Check { - pub fn lower(l: L, r: R) -> Check { - Check::Lower(Box::new(l), Box::new(r)) - } - pub fn lower_eq(l: L, r: R) -> Check { - Check::LowerEq(Box::new(l), Box::new(r)) - } - pub fn eq(l: L, r: R) -> Check { - Check::Eq(Box::new(l), Box::new(r)) - } - pub fn greater_eq(l: L, r: R) -> Check { - Check::GreaterEq(Box::new(l), Box::new(r)) - } - pub fn greater(l: L, r: R) -> Check { - Check::Greater(Box::new(l), Box::new(r)) - } - } -} - -pub struct CheckUSize(usize); - -impl Iden for CheckUSize { - fn unquoted(&self, s: &mut dyn Write) { - s.write_str(&format!("{}", self.0)).ok(); - } -} - -pub trait AsIden { - fn iden(self) -> I; -} - -impl AsIden for usize { - fn iden(self) -> CheckUSize { - CheckUSize(self) - } -} - -pub enum Constraint { - Check(Check), -} - -impl Constraint { - fn to_name(&self) -> String { - match self { - Constraint::Check(check) => format!("ck_{}", check.to_name()), - } - } -} - -impl From for Constraint { - fn from(value: Check) -> Self { - Self::Check(value) - } -} - -impl Display for Constraint { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - Constraint::Check(check) => f.write_fmt(format_args!("CHECK {check}")), - } - } -} - -async fn create_constraint>( - m: &SchemaManager<'_>, - table: T, - c: C, -) -> Result<(), DbErr> { - let c = c.into(); - m.get_connection() - .execute_unprepared(&format!( - "ALTER TABLE {} ADD CONSTRAINT {} {}", - table.to_string(), - c.to_name(), - c - )) - .await?; - Ok(()) -} diff --git a/migration/src/public/m20230603_120814_addresses.rs b/migration/src/public/m20230603_120814_addresses.rs index 7e91017..f9a1388 100644 --- a/migration/src/public/m20230603_120814_addresses.rs +++ b/migration/src/public/m20230603_120814_addresses.rs @@ -3,149 +3,123 @@ use sea_orm_migration::sea_orm::Statement; use sea_query::expr::SimpleExpr; use crate::sea_orm::DatabaseBackend; +use crate::{auto_uuid_not_null, ts_def_now_not_null, DropTable, IntoColumnDef}; #[derive(DeriveMigrationName)] pub struct Migration; #[async_trait::async_trait] impl MigrationTrait for Migration { - async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { - manager - .get_connection() + async fn up(&self, m: &SchemaManager) -> Result<(), DbErr> { + m.get_connection() .execute(Statement::from_string( DatabaseBackend::Postgres, "CREATE EXTENSION \"uuid-ossp\"".to_string(), )) .await?; - manager - .create_table( - Table::create() - // id character varying NOT NULL, - // created_at timestamp with time zone DEFAULT now() NOT NULL, - // updated_at timestamp with time zone DEFAULT now() NOT NULL, - // deleted_at timestamp with time zone, - // user_id character varying NOT NULL, - // opt_out boolean DEFAULT false NOT NULL, - // anonymize boolean DEFAULT false NOT NULL - .table(AnalyticsConfig::AnalyticsConfigs) - .col( - ColumnDef::new(AnalyticsConfig::Id) - .uuid() - .not_null() - .default(SimpleExpr::Custom("uuid_generate_v4()".into())) - .primary_key(), - ) - .col( - ColumnDef::new(AnalyticsConfig::CreatedAt) - .timestamp() - .default(SimpleExpr::Custom("now()".into())) - .not_null(), - ) - .col( - ColumnDef::new(AnalyticsConfig::UpdatedAt) - .timestamp() - .default(SimpleExpr::Custom("now()".into())) - .not_null(), - ) - .col(ColumnDef::new(AnalyticsConfig::DeletedAt).timestamp()) - .col(ColumnDef::new(AnalyticsConfig::UserId).uuid().not_null()) - .col( - ColumnDef::new(AnalyticsConfig::OptOut) - .boolean() - .default(false) - .not_null(), - ) - .col( - ColumnDef::new(AnalyticsConfig::Anonymize) - .boolean() - .default(false) - .not_null(), - ) - .to_owned(), - ) - .await?; - manager - .create_table( - Table::create() - // id character varying NOT NULL, - // customer_id character varying, - // company character varying, - // first_name character varying, - // last_name character varying, - // address_1 character varying, - // address_2 character varying, - // city character varying, - // country_code character varying, - // province character varying, - // postal_code character varying, - // phone character varying, - // created_at timestamp with time zone DEFAULT now() NOT NULL, - // updated_at timestamp with time zone DEFAULT now() NOT NULL, - // deleted_at timestamp with time zone, - // metadata jsonb - .table(Address::Addresses) - .if_not_exists() - .col( - ColumnDef::new(Address::Id) - .uuid() - .not_null() - .default(SimpleExpr::Custom("uuid_generate_v4()".into())) - .primary_key(), - ) - .col(ColumnDef::new(Address::CustomerId).uuid()) - .col(ColumnDef::new(Address::Company).string()) - .col(ColumnDef::new(Address::FirstName).string()) - .col(ColumnDef::new(Address::LastName).string()) - .col(ColumnDef::new(Address::Address1).string()) - .col(ColumnDef::new(Address::Address2).string()) - .col(ColumnDef::new(Address::City).string()) - .col(ColumnDef::new(Address::CountryCode).string()) - .col(ColumnDef::new(Address::Province).string()) - .col(ColumnDef::new(Address::PostalCode).string()) - .col(ColumnDef::new(Address::Phone).string()) - .col( - ColumnDef::new(Address::CreatedAt) - .timestamp() - .default(SimpleExpr::Custom("now()".into())) - .not_null(), - ) - .col( - ColumnDef::new(Address::UpdatedAt) - .timestamp() - .default(SimpleExpr::Custom("now()".into())) - .not_null(), - ) - .col(ColumnDef::new(Address::DeletedAt).timestamp()) - .col(ColumnDef::new(Address::Metadata).json_binary()) - .to_owned(), - ) - .await + Self::create_analytics_configs(m).await?; + Self::create_addresses(m).await?; + Ok(()) } - async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { - manager - .drop_table(Table::drop().table(Address::Addresses).to_owned()) - .await?; - manager - .drop_table( - Table::drop() - .table(AnalyticsConfig::AnalyticsConfigs) - .to_owned(), - ) - .await?; - - manager - .get_connection() - .execute(Statement::from_string( - DatabaseBackend::Postgres, - "DROP SCHEMA jobs".to_string(), - )) + async fn down(&self, m: &SchemaManager) -> Result<(), DbErr> { + self.drop_table(m, Address::Addresses).await?; + self.drop_table(m, AnalyticsConfig::AnalyticsConfigs) .await?; Ok(()) } } +impl Migration { + /// ```sql + /// CREATE TABLE analytics_configs + /// ( + /// id uuid NOT NULL, + /// created_at timestamp with time zone DEFAULT now() NOT NULL, + /// updated_at timestamp with time zone DEFAULT now() NOT NULL, + /// deleted_at timestamp with time zone, + /// user_id uuid NOT NULL, + /// opt_out boolean DEFAULT false NOT NULL, + /// anonymize boolean DEFAULT false NOT NULL + /// ); + /// ``` + async fn create_analytics_configs(m: &SchemaManager<'_>) -> Result<(), DbErr> { + m.create_table( + Table::create() + .table(AnalyticsConfig::AnalyticsConfigs) + .col(auto_uuid_not_null!(AnalyticsConfig::Id)) + .col(ts_def_now_not_null!(AnalyticsConfig::CreatedAt)) + .col(ts_def_now_not_null!(AnalyticsConfig::UpdatedAt)) + .col(AnalyticsConfig::DeletedAt.col().timestamp()) + .col(ColumnDef::new(AnalyticsConfig::UserId).uuid().not_null()) + .col( + AnalyticsConfig::OptOut + .col() + .boolean() + .default(false) + .not_null(), + ) + .col( + AnalyticsConfig::Anonymize + .col() + .boolean() + .default(false) + .not_null(), + ) + .to_owned(), + ) + .await + } + + /// ```sql + /// CREATE TABLE addresses + /// ( + /// id uuid NOT NULL, + /// customer_id uuid, + /// company character varying, + /// first_name character varying, + /// last_name character varying, + /// address_1 character varying, + /// address_2 character varying, + /// city character varying, + /// country_code character varying, + /// province character varying, + /// postal_code character varying, + /// phone character varying, + /// created_at timestamp with time zone DEFAULT now() NOT NULL, + /// updated_at timestamp with time zone DEFAULT now() NOT NULL, + /// deleted_at timestamp with time zone, + /// metadata jsonb + /// ); + /// ``` + async fn create_addresses(m: &SchemaManager<'_>) -> Result<(), DbErr> { + m.create_table( + Table::create() + .table(Address::Addresses) + .if_not_exists() + .col(auto_uuid_not_null!(Address::Id)) + .col(Address::CustomerId.col().uuid()) + .col(Address::Company.col().string()) + .col(Address::FirstName.col().string()) + .col(Address::LastName.col().string()) + .col(Address::Address1.col().string()) + .col(Address::Address2.col().string()) + .col(Address::City.col().string()) + .col(Address::CountryCode.col().string()) + .col(Address::Province.col().string()) + .col(Address::PostalCode.col().string()) + .col(Address::Phone.col().string()) + .col(ts_def_now_not_null!(Address::CreatedAt)) + .col(ts_def_now_not_null!(Address::UpdatedAt)) + .col(Address::DeletedAt.col().timestamp()) + .col(Address::Metadata.col().json_binary()) + .to_owned(), + ) + .await + } +} + #[derive(Iden)] enum AnalyticsConfig { AnalyticsConfigs, diff --git a/migration/src/public/m20230603_120815_claims.rs b/migration/src/public/m20230603_120815_claims.rs index 4d62aeb..8ea7635 100644 --- a/migration/src/public/m20230603_120815_claims.rs +++ b/migration/src/public/m20230603_120815_claims.rs @@ -5,7 +5,7 @@ use sea_query::expr::SimpleExpr; use crate::types::{ ClaimItemReason, ClaimOrderFulfillmentStatus, ClaimOrderPaymentStatus, ClaimOrderType, }; -use crate::{auto_uuid_not_null, ts_def_now_not_null}; +use crate::{auto_uuid_not_null, ts_def_now_not_null, DropTable, IntoColumnDef}; /// ```sql /// CREATE TABLE claim_images @@ -83,22 +83,12 @@ impl MigrationTrait for Migration { Ok(()) } - async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { - manager - .drop_table(Table::drop().table(ClaimImage::ClaimImages).to_owned()) - .await?; - manager - .drop_table(Table::drop().table(ClaimItem::ClaimItems).to_owned()) - .await?; - manager - .drop_table(Table::drop().table(ClaimItemTag::ClaimItemTags).to_owned()) - .await?; - manager - .drop_table(Table::drop().table(ClaimOrder::ClaimOrders).to_owned()) - .await?; - manager - .drop_table(Table::drop().table(ClaimTag::ClaimTags).to_owned()) - .await?; + async fn down(&self, m: &SchemaManager) -> Result<(), DbErr> { + self.drop_table(m, ClaimImage::ClaimImages).await?; + self.drop_table(m, ClaimItem::ClaimItems).await?; + self.drop_table(m, ClaimItemTag::ClaimItemTags).await?; + self.drop_table(m, ClaimOrder::ClaimOrders).await?; + self.drop_table(m, ClaimTag::ClaimTags).await?; Ok(()) } } @@ -116,11 +106,19 @@ impl Migration { .create_table( Table::create() .table(ClaimItemTag::ClaimItemTags) - .col(ColumnDef::new(ClaimItemTag::ItemId).uuid().not_null()) - .col(ColumnDef::new(ClaimItemTag::TagId).uuid().not_null()) + .col(ClaimItemTag::ItemId.col().uuid().not_null()) + .col(ClaimItemTag::TagId.col().uuid().not_null()) + .primary_key( + IndexCreateStatement::new() + .table(ClaimItemTag::ClaimItemTags) + .col(ClaimItemTag::ItemId) + .col(ClaimItemTag::TagId) + .primary(), + ) .to_owned(), ) - .await + .await?; + Ok(()) } /// ```sql @@ -145,23 +143,24 @@ impl Migration { Table::create() .table(ClaimItem::ClaimItems) .col(auto_uuid_not_null!(ClaimItem::Id)) - .col(ColumnDef::new(ClaimItem::ClaimOrderId).uuid().not_null()) - .col(ColumnDef::new(ClaimItem::ItemId).uuid().not_null()) - .col(ColumnDef::new(ClaimItem::VariantId).uuid().not_null()) + .col(ClaimItem::ClaimOrderId.col().uuid().not_null()) + .col(ClaimItem::ItemId.col().uuid().not_null()) + .col(ClaimItem::VariantId.col().uuid().not_null()) .col( - ColumnDef::new(ClaimItem::Reason) + ClaimItem::Reason + .col() .enumeration( ClaimItemReason::ClaimItemReasons, ClaimItemReason::iter().skip(1), ) .not_null(), ) - .col(ColumnDef::new(ClaimItem::Note).string()) - .col(ColumnDef::new(ClaimItem::Quantity).integer().not_null()) + .col(ClaimItem::Note.col().string()) + .col(ClaimItem::Quantity.col().integer().not_null()) .col(ts_def_now_not_null!(ClaimItem::CreatedAt)) .col(ts_def_now_not_null!(ClaimItem::UpdatedAt)) - .col(ColumnDef::new(ClaimItem::DeletedAt).timestamp()) - .col(ColumnDef::new(ClaimItem::Metadata).json_binary()) + .col(ClaimItem::DeletedAt.col().timestamp()) + .col(ClaimItem::Metadata.col().json_binary()) .to_owned(), ) .await @@ -185,12 +184,12 @@ impl Migration { Table::create() .table(ClaimImage::ClaimImages) .col(auto_uuid_not_null!(ClaimImage::Id)) - .col(ColumnDef::new(ClaimImage::ClaimItemId).uuid().not_null()) - .col(ColumnDef::new(ClaimImage::Url).string().not_null()) + .col(ClaimImage::ClaimItemId.col().uuid().not_null()) + .col(ClaimImage::Url.col().string().not_null()) .col(ts_def_now_not_null!(ClaimImage::CreatedAt)) .col(ts_def_now_not_null!(ClaimImage::UpdatedAt)) - .col(ColumnDef::new(ClaimImage::DeletedAt).string()) - .col(ColumnDef::new(ClaimImage::Metadata).json_binary()) + .col(ClaimImage::DeletedAt.col().string()) + .col(ClaimImage::Metadata.col().json_binary()) .to_owned(), ) .await @@ -222,7 +221,8 @@ impl Migration { .table(ClaimOrder::ClaimOrders) .col(auto_uuid_not_null!(ClaimOrder::Id)) .col( - ColumnDef::new(ClaimOrder::PaymentStatus) + ClaimOrder::PaymentStatus + .col() .enumeration( ClaimOrderPaymentStatus::ClaimOrderPaymentStatuses, ClaimOrderPaymentStatus::iter().skip(1), @@ -231,7 +231,8 @@ impl Migration { .not_null(), ) .col( - ColumnDef::new(ClaimOrder::FulfillmentStatus) + ClaimOrder::FulfillmentStatus + .col() .enumeration( ClaimOrderFulfillmentStatus::ClaimOrderFulfillmentStatuses, ClaimOrderFulfillmentStatus::iter().skip(1), @@ -240,23 +241,24 @@ impl Migration { .not_null(), ) .col( - ColumnDef::new(ClaimOrder::ClaimOrderType) + ClaimOrder::ClaimOrderType + .col() .enumeration( ClaimOrderType::ClaimOrderTypes, ClaimOrderType::iter().skip(1), ) .not_null(), ) - .col(ColumnDef::new(ClaimOrder::OrderId).uuid().not_null()) - .col(ColumnDef::new(ClaimOrder::ShippingAddressId).uuid()) - .col(ColumnDef::new(ClaimOrder::RefundAmount).integer()) - .col(ColumnDef::new(ClaimOrder::CanceledAt).timestamp()) + .col(ClaimOrder::OrderId.col().uuid().not_null()) + .col(ClaimOrder::ShippingAddressId.col().uuid()) + .col(ClaimOrder::RefundAmount.col().integer()) + .col(ClaimOrder::CanceledAt.col().timestamp()) .col(ts_def_now_not_null!(ClaimOrder::CreatedAt)) .col(ts_def_now_not_null!(ClaimOrder::UpdatedAt)) - .col(ColumnDef::new(ClaimOrder::DeletedAt).timestamp()) - .col(ColumnDef::new(ClaimOrder::Metadata).json_binary()) - .col(ColumnDef::new(ClaimOrder::IdempotencyKey).uuid()) - .col(ColumnDef::new(ClaimOrder::NoNotification).boolean()) + .col(ClaimOrder::DeletedAt.col().timestamp()) + .col(ClaimOrder::Metadata.col().json_binary()) + .col(ClaimOrder::IdempotencyKey.col().uuid()) + .col(ClaimOrder::NoNotification.col().boolean()) .to_owned(), ) .await @@ -279,11 +281,11 @@ impl Migration { Table::create() .table(ClaimTag::ClaimTags) .col(auto_uuid_not_null!(ClaimTag::Id)) - .col(ColumnDef::new(ClaimTag::Value).string()) + .col(ClaimTag::Value.col().string()) .col(ts_def_now_not_null!(ClaimTag::CreatedAt)) .col(ts_def_now_not_null!(ClaimTag::UpdatedAt)) - .col(ColumnDef::new(ClaimTag::DeletedAt).timestamp()) - .col(ColumnDef::new(ClaimTag::Metadata).json_binary()) + .col(ClaimTag::DeletedAt.col().timestamp()) + .col(ClaimTag::Metadata.col().json_binary()) .to_owned(), ) .await diff --git a/migration/src/public/m20230603_120816_geolocation.rs b/migration/src/public/m20230603_120816_geolocation.rs index 6eef285..8bef1df 100644 --- a/migration/src/public/m20230603_120816_geolocation.rs +++ b/migration/src/public/m20230603_120816_geolocation.rs @@ -1,7 +1,7 @@ use sea_orm_migration::prelude::*; use sea_query::expr::SimpleExpr; -use crate::{auto_uuid_not_null, ts_def_now_not_null}; +use crate::{auto_uuid_not_null, ts_def_now_not_null, DropTable, IntoColumnDef}; #[derive(DeriveMigrationName)] pub struct Migration; @@ -16,12 +16,9 @@ impl MigrationTrait for Migration { } async fn down(&self, m: &SchemaManager) -> Result<(), DbErr> { - m.drop_table(Table::drop().table(Country::Countries).to_owned()) - .await?; - m.drop_table(Table::drop().table(Currency::Currencies).to_owned()) - .await?; - m.drop_table(Table::drop().table(Currency::Currencies).to_owned()) - .await?; + self.drop_table(m, Country::Countries).await?; + self.drop_table(m, Currency::Currencies).await?; + self.drop_table(m, Currency::Currencies).await?; Ok(()) } } @@ -44,12 +41,12 @@ impl Migration { Table::create() .table(Country::Countries) .col(auto_uuid_not_null!(Country::Id)) - .col(ColumnDef::new(Country::Iso2).char_len(2).not_null()) - .col(ColumnDef::new(Country::Iso3).char_len(3).not_null()) - .col(ColumnDef::new(Country::NumCode).integer().not_null()) - .col(ColumnDef::new(Country::Name).string().not_null()) - .col(ColumnDef::new(Country::DisplayName).string().not_null()) - .col(ColumnDef::new(Country::RegionId).uuid()) + .col(Country::Iso2.col().char_len(2).not_null()) + .col(Country::Iso3.col().char_len(3).not_null()) + .col(Country::NumCode.col().integer().not_null()) + .col(Country::Name.col().string().not_null()) + .col(Country::DisplayName.col().string().not_null()) + .col(Country::RegionId.col().uuid()) .to_owned(), ) .await?; @@ -83,10 +80,10 @@ impl Migration { m.create_table( Table::create() .table(Currency::Currencies) - .col(ColumnDef::new(Currency::Code).string().not_null()) - .col(ColumnDef::new(Currency::Symbol).string().not_null()) - .col(ColumnDef::new(Currency::SymbolNative).string().not_null()) - .col(ColumnDef::new(Currency::Name).string().not_null()) + .col(Currency::Code.col().string().not_null()) + .col(Currency::Symbol.col().string().not_null()) + .col(Currency::SymbolNative.col().string().not_null()) + .col(Currency::Name.col().string().not_null()) .to_owned(), ) .await?; @@ -94,6 +91,7 @@ impl Migration { IndexCreateStatement::new() .table(Currency::Currencies) .col(Currency::Code) + .primary() .to_owned(), ) .await?; @@ -120,27 +118,29 @@ impl Migration { Table::create() .table(Region::Regions) .col(auto_uuid_not_null!(Region::Id)) - .col(ColumnDef::new(Region::Name).string().not_null()) - .col(ColumnDef::new(Region::CurrencyCode).string().not_null()) - .col(ColumnDef::new(Region::TaxRate).double().not_null()) - .col(ColumnDef::new(Region::TaxCode).string().not_null()) + .col(Region::Name.col().string().not_null()) + .col(Region::CurrencyCode.col().string().not_null()) + .col(Region::TaxRate.col().double().not_null()) + .col(Region::TaxCode.col().string().not_null()) .col(ts_def_now_not_null!(Region::CreatedAt)) .col(ts_def_now_not_null!(Region::UpdatedAt)) - .col(ColumnDef::new(Region::DeletedAt).timestamp()) - .col(ColumnDef::new(Region::Metadata).json_binary()) + .col(Region::DeletedAt.col().timestamp()) + .col(Region::Metadata.col().json_binary()) .col( - ColumnDef::new(Region::GiftCardsTaxable) + Region::GiftCardsTaxable + .col() .boolean() .default(true) .not_null(), ) .col( - ColumnDef::new(Region::AutomaticTaxes) + Region::AutomaticTaxes + .col() .boolean() .default(true) .not_null(), ) - .col(ColumnDef::new(Region::TaxProviderId).uuid().not_null()) + .col(Region::TaxProviderId.col().uuid().not_null()) .to_owned(), ) .await?; diff --git a/migration/src/sea_ext/mod.rs b/migration/src/sea_ext/mod.rs new file mode 100644 index 0000000..66a5f8a --- /dev/null +++ b/migration/src/sea_ext/mod.rs @@ -0,0 +1,338 @@ +use std::fmt::{Display, Formatter}; + +use async_trait::async_trait; +use sea_orm_migration::prelude::*; + +use crate::constraint::Check; +use crate::DbErr; + +#[macro_export] +macro_rules! ts_def_now_not_null { + ($identifier: expr) => { + ColumnDef::new($identifier) + .timestamp() + .default(SimpleExpr::Custom("now()".into())) + .not_null() + }; +} + +#[macro_export] +macro_rules! auto_uuid_not_null { + ($identifier: expr) => { + ColumnDef::new($identifier) + .uuid() + .not_null() + .default(SimpleExpr::Custom("public.uuid_generate_v4()".into())) + .primary_key() + }; +} + +#[async_trait::async_trait] +pub trait DropTable { + async fn drop_table( + &self, + manager: &SchemaManager<'_>, + identifier: I, + ) -> Result<(), DbErr> { + manager + .drop_table(Table::drop().table(identifier).if_exists().to_owned()) + .await + } +} + +impl DropTable for T {} + +pub trait IntoColumnDef: IntoIden { + fn col(self) -> ColumnDef + where + Self: Sized, + { + ColumnDef::new(self) + } +} + +impl IntoColumnDef for T {} + +#[async_trait] +pub trait CreateIndexExt { + async fn create_2col_idx(&self, table: T, col1: C1, col2: C2) -> Result<(), DbErr> + where + T: IntoIden + Copy + 'static + Sync + Send, + C1: IntoIden + Copy + 'static + Sync + Send, + C2: IntoIden + Copy + 'static + Sync + Send; +} + +#[async_trait] +impl CreateIndexExt for SchemaManager<'_> { + async fn create_2col_idx(&self, table: T, col1: C1, col2: C2) -> Result<(), DbErr> + where + T: IntoIden + Copy + 'static + Sync + Send, + C1: IntoIden + Copy + 'static + Sync + Send, + C2: IntoIden + Copy + 'static + Sync + Send, + { + self.create_index( + IndexCreateStatement::new() + .table(table) + .col(col1) + .col(col2) + .name(&to_pk2_name(table, col1, col2)) + .primary() + .if_not_exists() + .to_owned(), + ) + .await + } +} + +pub fn to_pk2_name(t1: T1, c1: C1, c2: C2) -> String { + let t1 = to_snake(t1.into_iden().to_string()); + let c1 = to_snake(c1.into_iden().to_string()); + let c2 = to_snake(c2.into_iden().to_string()); + + format!("pk_{}_{}_{}", t1, c1, c2) +} + +pub fn to_fk_name( + t1: T1, + c1: C1, + t2: T2, + c2: C2, +) -> String { + let t1 = to_snake(t1.into_iden().to_string()); + let c1 = to_snake(c1.into_iden().to_string()); + let t2 = to_snake(t2.into_iden().to_string()); + let c2 = to_snake(c2.into_iden().to_string()); + + format!("fk_{}_{}_{}_{}", t1, c1, t2, c2) +} + +fn to_snake(s: String) -> String { + s.chars().fold(String::new(), |mut m, c| { + if c.is_ascii_uppercase() { + m.push('_'); + } + m.push(c.to_ascii_lowercase()); + m + }) +} + +pub mod constraint { + use std::fmt::{Display, Formatter}; + + use crate::{Constraint, Iden}; + + pub enum Check { + Lower(Box, Box), + LowerEq(Box, Box), + Eq(Box, Box), + GreaterEq(Box, Box), + Greater(Box, Box), + NotNull(Box), + Or(Box, Box), + } + + impl std::ops::BitOr for Check { + type Output = Check; + + fn bitor(self, rhs: Self) -> Self::Output { + self.or(rhs) + } + } + + impl Check { + pub fn or(self, r: Check) -> Check { + Check::Or(Box::new(self), Box::new(r)) + } + + pub fn constraint(self) -> Constraint { + Constraint::Check(self) + } + + pub fn to_name(&self) -> String { + match self { + Check::Lower(l, r) => format!("{}_lw_{}", l.to_string(), r.to_string()), + Check::LowerEq(l, r) => format!("{}_le_{}", l.to_string(), r.to_string()), + Check::Eq(l, r) => format!("{}_eq_{}", l.to_string(), r.to_string()), + Check::GreaterEq(l, r) => format!("{}_ge_{}", l.to_string(), r.to_string()), + Check::Greater(l, r) => format!("{}_g_{}", l.to_string(), r.to_string()), + Check::Or(l, r) => format!("{}_or_{}", l.to_name(), r.to_name()), + Check::NotNull(l) => format!("{}_inn", l.to_string()), + } + } + } + + impl Display for Check { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Check::Lower(l, r) => { + f.write_fmt(format_args!("{} < {}", l.to_string(), r.to_string())) + } + Check::LowerEq(l, r) => { + f.write_fmt(format_args!("{} <= {}", l.to_string(), r.to_string())) + } + Check::Eq(l, r) => { + f.write_fmt(format_args!("{} = {}", l.to_string(), r.to_string())) + } + Check::GreaterEq(l, r) => { + f.write_fmt(format_args!("{} >= {}", l.to_string(), r.to_string())) + } + Check::Greater(l, r) => { + f.write_fmt(format_args!("{} > {}", l.to_string(), r.to_string())) + } + Check::Or(l, r) => f.write_fmt(format_args!("({l}) OR ({r})")), + Check::NotNull(l) => f.write_fmt(format_args!("{} IS NOT NULL", l.to_string())), + } + } + } + + impl Check { + pub fn less(l: L, r: R) -> Check { + Check::Lower(Box::new(l), Box::new(r)) + } + pub fn less_eq(l: L, r: R) -> Check { + Check::LowerEq(Box::new(l), Box::new(r)) + } + pub fn eq(l: L, r: R) -> Check { + Check::Eq(Box::new(l), Box::new(r)) + } + pub fn greater_eq(l: L, r: R) -> Check { + Check::GreaterEq(Box::new(l), Box::new(r)) + } + pub fn greater(l: L, r: R) -> Check { + Check::Greater(Box::new(l), Box::new(r)) + } + pub fn not_null(l: L) -> Check { + Check::NotNull(Box::new(l)) + } + } +} + +pub struct CheckUSize(usize); + +impl Iden for CheckUSize { + fn unquoted(&self, s: &mut dyn Write) { + s.write_str(&format!("{}", self.0)).ok(); + } +} + +pub trait AsIden { + fn iden(self) -> I; +} + +impl AsIden for usize { + fn iden(self) -> CheckUSize { + CheckUSize(self) + } +} + +pub enum Constraint { + Check(Check), +} + +impl Constraint { + fn to_name(&self) -> String { + match self { + Constraint::Check(check) => format!("ck_{}", check.to_name()), + } + } +} + +impl From for Constraint { + fn from(value: Check) -> Self { + Self::Check(value) + } +} + +impl Display for Constraint { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Constraint::Check(check) => f.write_fmt(format_args!("CHECK {check}")), + } + } +} + +#[async_trait(?Send)] +pub trait CreateConstraint { + async fn create_constraint>( + &self, + table: T, + c: C, + ) -> Result<(), DbErr>; +} + +#[async_trait(?Send)] +impl CreateConstraint for SchemaManager<'_> { + async fn create_constraint(&self, table: T, c: C) -> Result<(), DbErr> + where + C: Into, + { + let c = c.into(); + self.get_connection() + .execute_unprepared(&format!( + "ALTER TABLE {} ADD CONSTRAINT {} {}", + table.to_string(), + c.to_name(), + c + )) + .await?; + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use insta::assert_ron_snapshot; + use sea_orm_migration::prelude::*; + + use crate::constraint::Check; + + #[derive(Iden)] + pub enum ShippingMethod { + ShippingMethods, + Id, + ShippingOptionId, + OrderId, + CartId, + SwapId, + ReturnId, + Price, + Data, + ClaimOrderId, + } + + fn complex_or_check() -> Check { + Check::not_null(ShippingMethod::ClaimOrderId) + | Check::not_null(ShippingMethod::OrderId) + | Check::not_null(ShippingMethod::CartId) + | Check::not_null(ShippingMethod::SwapId) + | Check::not_null(ShippingMethod::ReturnId) + } + + #[test] + fn combine_not_null_or_sql() { + let s = complex_or_check(); + + assert_ron_snapshot!(s.to_string()); + } + + #[test] + fn combine_not_null_or_name() { + let s = complex_or_check(); + + assert_ron_snapshot!(s.to_name()); + } + + #[test] + fn constraint_combine_not_null_or_sql() { + let c = complex_or_check().constraint(); + + assert_ron_snapshot!(c.to_string()); + } + + #[test] + fn constraint_combine_not_null_or_name() { + let c = complex_or_check().constraint(); + + assert_ron_snapshot!(c.to_name()); + } +} diff --git a/migration/src/sea_ext/snapshots/migration__sea_ext__tests__combine_not_null_or_name.snap b/migration/src/sea_ext/snapshots/migration__sea_ext__tests__combine_not_null_or_name.snap new file mode 100644 index 0000000..b86e0b3 --- /dev/null +++ b/migration/src/sea_ext/snapshots/migration__sea_ext__tests__combine_not_null_or_name.snap @@ -0,0 +1,5 @@ +--- +source: migration/src/sea_ext/mod.rs +expression: s.to_name() +--- +"claim_order_id_inn_or_order_id_inn_or_cart_id_inn_or_swap_id_inn_or_return_id_inn" diff --git a/migration/src/sea_ext/snapshots/migration__sea_ext__tests__combine_not_null_or_sql.snap b/migration/src/sea_ext/snapshots/migration__sea_ext__tests__combine_not_null_or_sql.snap new file mode 100644 index 0000000..43dccd9 --- /dev/null +++ b/migration/src/sea_ext/snapshots/migration__sea_ext__tests__combine_not_null_or_sql.snap @@ -0,0 +1,5 @@ +--- +source: migration/src/sea_ext/mod.rs +expression: s.to_string() +--- +"((((claim_order_id IS NOT NULL) OR (order_id IS NOT NULL)) OR (cart_id IS NOT NULL)) OR (swap_id IS NOT NULL)) OR (return_id IS NOT NULL)" diff --git a/migration/src/sea_ext/snapshots/migration__sea_ext__tests__constraint_combine_not_null_or_name.snap b/migration/src/sea_ext/snapshots/migration__sea_ext__tests__constraint_combine_not_null_or_name.snap new file mode 100644 index 0000000..d10acf4 --- /dev/null +++ b/migration/src/sea_ext/snapshots/migration__sea_ext__tests__constraint_combine_not_null_or_name.snap @@ -0,0 +1,5 @@ +--- +source: migration/src/sea_ext/mod.rs +expression: c.to_name() +--- +"ck_claim_order_id_inn_or_order_id_inn_or_cart_id_inn_or_swap_id_inn_or_return_id_inn" diff --git a/migration/src/sea_ext/snapshots/migration__sea_ext__tests__constraint_combine_not_null_or_sql.snap b/migration/src/sea_ext/snapshots/migration__sea_ext__tests__constraint_combine_not_null_or_sql.snap new file mode 100644 index 0000000..a066ba4 --- /dev/null +++ b/migration/src/sea_ext/snapshots/migration__sea_ext__tests__constraint_combine_not_null_or_sql.snap @@ -0,0 +1,5 @@ +--- +source: migration/src/sea_ext/mod.rs +expression: c.to_string() +--- +"CHECK ((((claim_order_id IS NOT NULL) OR (order_id IS NOT NULL)) OR (cart_id IS NOT NULL)) OR (swap_id IS NOT NULL)) OR (return_id IS NOT NULL)" diff --git a/migration/src/shipping/m20230603_120810_shipping.rs b/migration/src/shipping/m20230603_120810_shipping.rs index ca5381b..adeb3c2 100644 --- a/migration/src/shipping/m20230603_120810_shipping.rs +++ b/migration/src/shipping/m20230603_120810_shipping.rs @@ -1,8 +1,10 @@ use sea_orm_migration::prelude::*; +use crate::constraint::Check; use crate::sea_orm::Iterable; use crate::{ - auto_uuid_not_null, create_type, drop_type, ts_def_now_not_null, DropTable, IntoColumnDef, + auto_uuid_not_null, create_type, drop_type, ts_def_now_not_null, AsIden, CreateConstraint, + DropTable, IntoColumnDef, }; #[derive(DeriveMigrationName)] @@ -114,12 +116,33 @@ impl Migration { /// data jsonb NOT NULL, /// claim_order_id uuid, /// CONSTRAINT "CHK_64c6812fe7815be30d688df513" CHECK ((price >= 0)), - /// CONSTRAINT "CHK_a7020b08665bbd64d84a6641cf" CHECK (((claim_order_id - /// IS NOT NULL) OR (order_id IS NOT NULL) OR - /// (cart_id IS NOT NULL) OR (swap_id IS NOT NULL) OR - /// (return_id IS NOT NULL))) ); + /// CONSTRAINT "CHK_a7020b08665bbd64d84a6641cf" CHECK ( + /// ( + /// (claim_order_id IS NOT NULL) + /// OR (order_id IS NOT NULL) + /// OR (cart_id IS NOT NULL) + /// OR (swap_id IS NOT NULL) + /// OR (return_id IS NOT NULL) + /// ) + /// ) + /// ); /// ``` async fn create_shipping_methods(m: &SchemaManager<'_>) -> Result<(), DbErr> { + m.create_constraint( + ShippingMethod::ShippingMethods, + Check::greater_eq(ShippingMethod::Price, 0.iden()), + ) + .await?; + + m.create_constraint( + ShippingMethod::ShippingMethods, + Check::not_null(ShippingMethod::ClaimOrderId) + | Check::not_null(ShippingMethod::OrderId) + | Check::not_null(ShippingMethod::CartId) + | Check::not_null(ShippingMethod::SwapId) + | Check::not_null(ShippingMethod::ReturnId), + ) + .await?; Ok(()) } @@ -161,6 +184,11 @@ impl Migration { /// ); /// ```` async fn create_shipping_options(m: &SchemaManager<'_>) -> Result<(), DbErr> { + m.create_constraint( + ShippingOption::ShippingOptions, + Check::greater_eq(ShippingOption::Amount, 0.iden()), + ) + .await?; Ok(()) }