├── .github
└── workflows
│ ├── _commit.yml
│ ├── crm.yml
│ ├── infra.yml
│ ├── inventory.yml
│ ├── invoicing.yml
│ ├── ordering.yml
│ ├── payments.yml
│ ├── pricing.yml
│ ├── product_catalog.yml
│ ├── rails_application.yml
│ ├── shipping.yml
│ ├── taxes.yml
│ └── todo.yml
├── .gitignore
├── LICENSE
├── Makefile
├── README.md
├── build_events_catalog.rb
├── build_events_catalog.sh
├── ecommerce
├── authentication
│ ├── .mutant.yml
│ ├── Gemfile
│ ├── Gemfile.lock
│ ├── Makefile
│ ├── README.md
│ ├── lib
│ │ ├── authentication.rb
│ │ └── authentication
│ │ │ ├── account.rb
│ │ │ ├── account_service.rb
│ │ │ ├── commands
│ │ │ ├── connect_account_to_client.rb
│ │ │ ├── register_account.rb
│ │ │ ├── set_login.rb
│ │ │ └── set_password_hash.rb
│ │ │ └── events
│ │ │ ├── account_connected_to_client.rb
│ │ │ ├── account_registered.rb
│ │ │ ├── login_set.rb
│ │ │ └── password_hash_set.rb
│ └── test
│ │ ├── connect_account_to_client_test.rb
│ │ ├── register_account_test.rb
│ │ ├── set_login_test.rb
│ │ ├── set_password_hash_test.rb
│ │ └── test_helper.rb
├── configuration.rb
├── crm
│ ├── .mutant.yml
│ ├── Gemfile
│ ├── Gemfile.lock
│ ├── Makefile
│ ├── README.md
│ ├── lib
│ │ ├── crm.rb
│ │ └── crm
│ │ │ ├── commands
│ │ │ ├── assign_customer_to_order.rb
│ │ │ ├── promote_customer_to_vip.rb
│ │ │ └── register_customer.rb
│ │ │ ├── customer.rb
│ │ │ ├── customer_service.rb
│ │ │ ├── events
│ │ │ ├── customer_assigned_to_order.rb
│ │ │ ├── customer_promoted_to_vip.rb
│ │ │ └── customer_registered.rb
│ │ │ ├── order.rb
│ │ │ └── order_service.rb
│ └── test
│ │ ├── assign_customer_to_order_test.rb
│ │ ├── promote_client_to_vip_test.rb
│ │ ├── registration_test.rb
│ │ └── test_helper.rb
├── fulfillment
│ ├── .mutant.yml
│ ├── Gemfile
│ ├── Gemfile.lock
│ ├── Makefile
│ ├── lib
│ │ ├── fulfillment.rb
│ │ └── fulfillment
│ │ │ ├── commands
│ │ │ ├── cancel_order.rb
│ │ │ ├── confirm_order.rb
│ │ │ └── register_order.rb
│ │ │ ├── events
│ │ │ ├── order_cancelled.rb
│ │ │ ├── order_confirmed.rb
│ │ │ └── order_registered.rb
│ │ │ ├── fake_number_generator.rb
│ │ │ ├── number_generator.rb
│ │ │ ├── on_cancel_order.rb
│ │ │ ├── on_confirm_order.rb
│ │ │ ├── on_register_order.rb
│ │ │ └── order.rb
│ └── test
│ │ ├── cancel_order_test.rb
│ │ ├── confirm_order_test.rb
│ │ ├── number_generator_test.rb
│ │ ├── register_order_test.rb
│ │ └── test_helper.rb
├── inventory
│ ├── .mutant.yml
│ ├── Gemfile
│ ├── Gemfile.lock
│ ├── Makefile
│ ├── README.md
│ ├── lib
│ │ ├── inventory.rb
│ │ └── inventory
│ │ │ ├── commands
│ │ │ ├── dispatch.rb
│ │ │ ├── release.rb
│ │ │ ├── reserve.rb
│ │ │ └── supply.rb
│ │ │ ├── events
│ │ │ ├── availability_changed.rb
│ │ │ ├── stock_level_changed.rb
│ │ │ ├── stock_released.rb
│ │ │ └── stock_reserved.rb
│ │ │ ├── inventory_entry.rb
│ │ │ └── inventory_entry_service.rb
│ └── test
│ │ ├── dispatch_test.rb
│ │ ├── release_test.rb
│ │ ├── reserve_test.rb
│ │ ├── supply_test.rb
│ │ └── test_helper.rb
├── invoicing
│ ├── .mutant.yml
│ ├── Gemfile
│ ├── Gemfile.lock
│ ├── Makefile
│ ├── README.md
│ ├── lib
│ │ ├── invoicing.rb
│ │ └── invoicing
│ │ │ ├── commands.rb
│ │ │ ├── events.rb
│ │ │ ├── fake_concurrent_invoice_number_generator.rb
│ │ │ ├── invoice.rb
│ │ │ ├── invoice_item_title_catalog.rb
│ │ │ ├── invoice_number_generator.rb
│ │ │ ├── product.rb
│ │ │ └── services.rb
│ └── test
│ │ ├── fake_concurrent_invoice_number_generator_test.rb
│ │ ├── invoice_item_test.rb
│ │ ├── invoice_number_generator_test.rb
│ │ ├── invoice_service_test.rb
│ │ ├── setting_product_displayed_name_test.rb
│ │ └── test_helper.rb
├── ordering
│ ├── .mutant.yml
│ ├── Gemfile
│ ├── Gemfile.lock
│ ├── Makefile
│ ├── README.md
│ ├── lib
│ │ ├── ordering.rb
│ │ └── ordering
│ │ │ ├── commands
│ │ │ ├── add_item_to_refund.rb
│ │ │ ├── create_draft_refund.rb
│ │ │ └── remove_item_from_refund.rb
│ │ │ ├── events
│ │ │ ├── draft_refund_created.rb
│ │ │ ├── item_added_to_refund.rb
│ │ │ └── item_removed_from_refund.rb
│ │ │ ├── refund.rb
│ │ │ ├── refundable_products.rb
│ │ │ └── service.rb
│ └── test
│ │ ├── add_item_to_refund_test.rb
│ │ ├── create_draft_refund_test.rb
│ │ ├── refund_items_list_test.rb
│ │ ├── refundable_products_test.rb
│ │ ├── remove_item_from_refund_test.rb
│ │ └── test_helper.rb
├── payments
│ ├── .mutant.yml
│ ├── Gemfile
│ ├── Gemfile.lock
│ ├── Makefile
│ ├── README.md
│ ├── lib
│ │ ├── payments.rb
│ │ └── payments
│ │ │ ├── commands.rb
│ │ │ ├── events.rb
│ │ │ ├── fake_gateway.rb
│ │ │ ├── on_authorize_payment.rb
│ │ │ ├── on_capture_payment.rb
│ │ │ ├── on_release_payment.rb
│ │ │ ├── on_set_payment_amount.rb
│ │ │ └── payment.rb
│ └── test
│ │ ├── fake_gateway_test.rb
│ │ ├── on_capture_payment_test.rb
│ │ ├── on_release_payment_test.rb
│ │ ├── payment_test.rb
│ │ └── test_helper.rb
├── pricing
│ ├── .mutant.yml
│ ├── Gemfile
│ ├── Gemfile.lock
│ ├── Makefile
│ ├── README.md
│ ├── lib
│ │ ├── pricing.rb
│ │ └── pricing
│ │ │ ├── apply_time_promotion.rb
│ │ │ ├── calculate_order_sub_amounts_value.rb
│ │ │ ├── calculate_order_total_value.rb
│ │ │ ├── commands.rb
│ │ │ ├── coupon.rb
│ │ │ ├── discounts.rb
│ │ │ ├── events.rb
│ │ │ ├── offer.rb
│ │ │ ├── price_change.rb
│ │ │ ├── promotions_calendar.rb
│ │ │ ├── services.rb
│ │ │ └── time_promotion.rb
│ └── test
│ │ ├── apply_time_promotion_test.rb
│ │ ├── coupons_test.rb
│ │ ├── discounts_test.rb
│ │ ├── free_products_test.rb
│ │ ├── offer_lifecycle_test.rb
│ │ ├── pricing_test.rb
│ │ ├── product_test.rb
│ │ ├── promotions_calendar_test.rb
│ │ ├── simple_offer_test.rb
│ │ ├── subamounts_test.rb
│ │ ├── test_helper.rb
│ │ ├── time_promotion_test.rb
│ │ └── use_coupon_test.rb
├── product_catalog
│ ├── .mutant.yml
│ ├── Gemfile
│ ├── Gemfile.lock
│ ├── Makefile
│ ├── README.md
│ ├── lib
│ │ ├── product_catalog.rb
│ │ └── product_catalog
│ │ │ ├── commands.rb
│ │ │ ├── events.rb
│ │ │ ├── naming.rb
│ │ │ └── registration.rb
│ └── test
│ │ ├── naming_test.rb
│ │ ├── registration_test.rb
│ │ └── test_helper.rb
├── shipping
│ ├── .mutant.yml
│ ├── Gemfile
│ ├── Gemfile.lock
│ ├── Makefile
│ ├── README.md
│ ├── lib
│ │ ├── shipping.rb
│ │ └── shipping
│ │ │ ├── commands
│ │ │ ├── add_item_to_shipment_picking_list.rb
│ │ │ ├── add_shipping_address_to_shipment.rb
│ │ │ ├── authorize_shipment.rb
│ │ │ ├── remove_item_from_shipment_picking_list.rb
│ │ │ └── submit_shipment.rb
│ │ │ ├── events
│ │ │ ├── item_added_to_shipment_picking_list.rb
│ │ │ ├── item_removed_from_shipment_picking_list.rb
│ │ │ ├── shipment_authorized.rb
│ │ │ ├── shipment_submitted.rb
│ │ │ └── shipping_address_added_to_shipment.rb
│ │ │ ├── picking_list.rb
│ │ │ ├── picking_list_item.rb
│ │ │ ├── services
│ │ │ ├── on_add_item_to_shipment_picking_list.rb
│ │ │ ├── on_add_shipping_address_to_shipment.rb
│ │ │ ├── on_authorize_shipment.rb
│ │ │ ├── on_remove_item_from_shipment_picking_list.rb
│ │ │ └── on_submit_shipment.rb
│ │ │ └── shipment.rb
│ └── test
│ │ ├── on_add_item_to_shipment_picking_list_test.rb
│ │ ├── on_add_shipping_address_to_shipment_test.rb
│ │ ├── on_authorize_shipment_test.rb
│ │ ├── on_remove_item_from_shipment_picking_list_test.rb
│ │ ├── on_submit_shipment_test.rb
│ │ ├── picking_list_item_test.rb
│ │ ├── picking_list_test.rb
│ │ ├── shipment_test.rb
│ │ └── test_helper.rb
└── taxes
│ ├── .mutant.yml
│ ├── Gemfile
│ ├── Gemfile.lock
│ ├── Makefile
│ ├── README.md
│ ├── lib
│ ├── taxes.rb
│ └── taxes
│ │ ├── commands.rb
│ │ ├── events.rb
│ │ ├── product.rb
│ │ ├── services.rb
│ │ └── vat_rate_catalog.rb
│ └── test
│ ├── taxes_test.rb
│ ├── test_helper.rb
│ └── vat_rate_catalog_test.rb
├── eventcatalog.config.js
├── hanami_application
└── README.md
├── infra
├── .mutant.yml
├── Gemfile
├── Gemfile.lock
├── Gemfile.test
├── Makefile
├── infra.gemspec
├── lib
│ ├── infra.rb
│ └── infra
│ │ ├── aggregate_root_repository.rb
│ │ ├── command.rb
│ │ ├── command_bus.rb
│ │ ├── event.rb
│ │ ├── event_store.rb
│ │ ├── process.rb
│ │ ├── process_manager.rb
│ │ ├── retry.rb
│ │ ├── testing.rb
│ │ └── types.rb
└── test
│ ├── retry_test.rb
│ ├── test_helper.rb
│ └── vat_rate_test.rb
├── pricing_catalog_rails_app
├── .dockerignore
├── .gitignore
├── Dockerfile
├── Gemfile
├── Gemfile.lock
├── README.md
├── Rakefile
├── app
│ ├── admin
│ │ ├── read_models
│ │ │ └── admin_catalog
│ │ │ │ ├── admin_catalog.rb
│ │ │ │ └── index.html.erb
│ │ └── register_product.rb
│ ├── assets
│ │ ├── config
│ │ │ └── manifest.js
│ │ ├── images
│ │ │ └── .keep
│ │ └── stylesheets
│ │ │ └── application.css
│ ├── channels
│ │ └── application_cable
│ │ │ ├── channel.rb
│ │ │ └── connection.rb
│ ├── controllers
│ │ ├── admin
│ │ │ └── catalog_controller.rb
│ │ ├── application_controller.rb
│ │ ├── catalog_controller.rb
│ │ └── concerns
│ │ │ └── .keep
│ ├── helpers
│ │ └── application_helper.rb
│ ├── javascript
│ │ ├── application.js
│ │ └── controllers
│ │ │ ├── application.js
│ │ │ ├── hello_controller.js
│ │ │ └── index.js
│ ├── jobs
│ │ └── application_job.rb
│ ├── models
│ │ ├── application_record.rb
│ │ └── concerns
│ │ │ └── .keep
│ ├── public
│ │ └── read_models
│ │ │ └── public_catalog
│ │ │ ├── index.html.erb
│ │ │ └── public_catalog.rb
│ └── views
│ │ └── layouts
│ │ └── application.html.erb
├── bin
│ ├── bundle
│ ├── docker-entrypoint
│ ├── importmap
│ ├── rails
│ ├── rake
│ └── setup
├── config.ru
├── config
│ ├── application.rb
│ ├── boot.rb
│ ├── cable.yml
│ ├── credentials.yml.enc
│ ├── database.yml
│ ├── environment.rb
│ ├── environments
│ │ ├── development.rb
│ │ ├── production.rb
│ │ └── test.rb
│ ├── importmap.rb
│ ├── initializers
│ │ ├── content_security_policy.rb
│ │ ├── filter_parameter_logging.rb
│ │ ├── inflections.rb
│ │ ├── permissions_policy.rb
│ │ └── rails_event_store.rb
│ ├── locales
│ │ └── en.yml
│ ├── master.key
│ ├── puma.rb
│ └── routes.rb
├── db
│ ├── migrate
│ │ ├── 20240130110320_create_event_store_events.rb
│ │ ├── 20240201132941_enable_pg_crypto.rb
│ │ ├── 20240201133038_create_products_table.rb
│ │ └── 20240201154705_create_admin_products.rb
│ ├── schema.rb
│ └── seeds.rb
├── lib
│ ├── assets
│ │ └── .keep
│ └── tasks
│ │ └── .keep
├── log
│ └── .keep
├── public
│ ├── 404.html
│ ├── 422.html
│ ├── 500.html
│ ├── apple-touch-icon-precomposed.png
│ ├── apple-touch-icon.png
│ ├── favicon.ico
│ └── robots.txt
├── storage
│ └── .keep
├── test
│ ├── application_system_test_case.rb
│ ├── channels
│ │ └── application_cable
│ │ │ └── connection_test.rb
│ ├── controllers
│ │ └── .keep
│ ├── fixtures
│ │ └── files
│ │ │ └── .keep
│ ├── helpers
│ │ └── .keep
│ ├── integration
│ │ └── .keep
│ ├── models
│ │ └── .keep
│ ├── system
│ │ └── .keep
│ └── test_helper.rb
├── tmp
│ ├── .keep
│ ├── local_secret.txt
│ ├── pids
│ │ └── .keep
│ └── storage
│ │ └── .keep
└── vendor
│ ├── .keep
│ └── javascript
│ └── .keep
├── productivity
└── todo
│ ├── .mutant.yml
│ ├── Gemfile
│ ├── Gemfile.lock
│ ├── Makefile
│ ├── README.md
│ ├── lib
│ ├── todo.rb
│ └── todo
│ │ └── todo.rb
│ └── test
│ ├── test_helper.rb
│ └── todo_test.rb
└── rails_application
├── .env.development
├── .env.test
├── .gitignore
├── .mutant.yml
├── Gemfile
├── Gemfile.lock
├── Makefile
├── Procfile
├── README.md
├── Rakefile
├── app
├── assets
│ ├── builds
│ │ └── .keep
│ ├── config
│ │ └── manifest.js
│ ├── images
│ │ └── .keep
│ └── stylesheets
│ │ ├── application.css
│ │ └── application.tailwind.css
├── channels
│ └── application_cable
│ │ ├── channel.rb
│ │ └── connection.rb
├── client_panel
│ └── login.rb
├── controllers
│ ├── application_controller.rb
│ ├── available_vat_rates_controller.rb
│ ├── billing_addresses_controller.rb
│ ├── client
│ │ ├── base_controller.rb
│ │ ├── clients_controller.rb
│ │ ├── inbox_controller.rb
│ │ ├── orders_controller.rb
│ │ └── products_controller.rb
│ ├── coupons_controller.rb
│ ├── customers_controller.rb
│ ├── events_catalog_controller.rb
│ ├── invoices_controller.rb
│ ├── orders_controller.rb
│ ├── product
│ │ └── future_price_controller.rb
│ ├── products_controller.rb
│ ├── refunds_controller.rb
│ ├── shipments_controller.rb
│ ├── shipping_addresses_controller.rb
│ ├── supplies_controller.rb
│ └── time_promotions_controller.rb
├── helpers
│ └── application_helper.rb
├── javascript
│ ├── application.js
│ ├── channels
│ │ ├── consumer.js
│ │ └── index.js
│ └── controllers
│ │ ├── application.js
│ │ ├── index.js
│ │ ├── timezone_controller.js
│ │ └── turbo_modal_controller.js
├── models
│ └── application_record.rb
├── processes
│ └── processes
│ │ ├── configuration.rb
│ │ ├── confirm_order_on_payment_captured.rb
│ │ ├── determine_vat_rates_on_order_placed.rb
│ │ ├── notify_payments_about_order_value.rb
│ │ ├── order_item_invoicing_process.rb
│ │ ├── release_payment_process.rb
│ │ ├── reservation_process.rb
│ │ ├── shipment_process.rb
│ │ ├── sync_shipment_from_pricing.rb
│ │ ├── three_plus_one_free.rb
│ │ └── welcome_message_process.rb
├── read_models
│ ├── availability
│ │ └── configuration.rb
│ ├── client_authentication
│ │ ├── configuration.rb
│ │ ├── create_account.rb
│ │ └── set_password.rb
│ ├── client_inbox
│ │ ├── configuration.rb
│ │ └── rendering
│ │ │ └── inbox_list.rb
│ ├── client_orders
│ │ ├── add_item_to_order.rb
│ │ ├── configuration.rb
│ │ ├── create_customer.rb
│ │ ├── order_handlers.rb
│ │ ├── product_handlers.rb
│ │ ├── remove_item_from_order.rb
│ │ └── rendering
│ │ │ ├── edit_order.rb
│ │ │ ├── orders_list.rb
│ │ │ └── show_order.rb
│ ├── coupons
│ │ ├── configuration.rb
│ │ └── register_coupon.rb
│ ├── customers
│ │ ├── configuration.rb
│ │ ├── connect_account.rb
│ │ ├── promote_to_vip.rb
│ │ ├── register_customer.rb
│ │ └── update_paid_orders_summary.rb
│ ├── invoices
│ │ ├── configuration.rb
│ │ ├── create_invoice_item.rb
│ │ ├── mark_as_issued.rb
│ │ ├── mark_order_placed.rb
│ │ ├── set_billing_address.rb
│ │ └── set_payment_date.rb
│ ├── orders
│ │ ├── add_item_to_order.rb
│ │ ├── assign_customer_to_order.rb
│ │ ├── broadcaster.rb
│ │ ├── cancel_order.rb
│ │ ├── change_product_name.rb
│ │ ├── change_product_price.rb
│ │ ├── configuration.rb
│ │ ├── confirm_order.rb
│ │ ├── create_customer.rb
│ │ ├── expire_order.rb
│ │ ├── register_product.rb
│ │ ├── remove_discount.rb
│ │ ├── remove_item_from_order.rb
│ │ ├── remove_time_promotion_discount.rb
│ │ ├── submit_order.rb
│ │ ├── update_discount.rb
│ │ ├── update_order_total_value.rb
│ │ └── update_time_promotion_discount_value.rb
│ ├── products
│ │ ├── configuration.rb
│ │ └── refresh_future_prices_calendar.rb
│ ├── public_offer
│ │ ├── configuration.rb
│ │ ├── product.rb
│ │ └── register_lowest_price.rb
│ ├── refunds
│ │ ├── add_item_to_refund.rb
│ │ ├── configuration.rb
│ │ ├── create_draft_refund.rb
│ │ └── remove_item_from_refund.rb
│ ├── shipments
│ │ ├── add_item_to_shipment.rb
│ │ ├── configuration.rb
│ │ ├── mark_order_placed.rb
│ │ ├── remove_item_from_shipment.rb
│ │ └── set_shipping_address.rb
│ ├── single_table_read_model.rb
│ ├── time_promotions
│ │ ├── broadcaster.rb
│ │ ├── configuration.rb
│ │ └── create_time_promotion.rb
│ └── vat_rates
│ │ ├── add_available_vat_rate.rb
│ │ ├── configuration.rb
│ │ └── remove_available_vat_rate.rb
├── services
│ ├── client
│ │ └── orders
│ │ │ └── submit_service.rb
│ └── orders
│ │ └── submit_service.rb
└── views
│ ├── application
│ ├── _client_top_navigation.html.erb
│ ├── _footer.html.erb
│ ├── _top_navigation.html.erb
│ └── _tracking.html.erb
│ ├── available_vat_rates
│ ├── index.html.erb
│ └── new.html.erb
│ ├── billing_addresses
│ └── edit.erb
│ ├── coupons
│ ├── index.html.erb
│ └── new.html.erb
│ ├── customers
│ ├── index.html.erb
│ ├── new.html.erb
│ └── show.html.erb
│ ├── invoices
│ └── show.html.erb
│ ├── kaminari
│ ├── _gap.html.erb
│ ├── _next_page.html.erb
│ ├── _page.html.erb
│ ├── _paginator.html.erb
│ └── _prev_page.html.erb
│ ├── layouts
│ ├── application.html.erb
│ └── client_panel.erb
│ ├── orders
│ ├── edit.html.erb
│ ├── edit_discount.html.erb
│ ├── index.html.erb
│ ├── new.html.erb
│ └── show.html.erb
│ ├── products
│ ├── _future_price.html.erb
│ ├── edit.html.erb
│ ├── index.html.erb
│ ├── new.html.erb
│ └── show.html.erb
│ ├── refunds
│ └── edit.html.erb
│ ├── shipments
│ ├── index.html.erb
│ └── show.html.erb
│ ├── shipping_addresses
│ └── edit.html.erb
│ ├── supplies
│ └── new.html.erb
│ └── time_promotions
│ ├── index.html.erb
│ └── new.html.erb
├── bin
├── bundle
├── dev
├── importmap
├── puma
├── pumactl
├── rails
├── rake
├── reset_heroku_db.sh
└── setup
├── config.ru
├── config
├── application.rb
├── boot.rb
├── cable.yml
├── credentials.yml.enc
├── database.yml
├── environment.rb
├── environments
│ ├── development.rb
│ ├── production.rb
│ └── test.rb
├── importmap.rb
├── initializers
│ ├── application_controller_renderer.rb
│ ├── assets.rb
│ ├── backtrace_silencers.rb
│ ├── content_security_policy.rb
│ ├── cookies_serializer.rb
│ ├── filter_parameter_logging.rb
│ ├── inflections.rb
│ ├── mime_types.rb
│ ├── permissions_policy.rb
│ ├── rails_event_store.rb
│ ├── session_store.rb
│ └── wrap_parameters.rb
├── locales
│ └── en.yml
├── puma.rb
├── routes.rb
├── secrets.yml
├── sidekiq.yml
└── tailwind.config.js
├── db
├── migrate
│ ├── 20150429224522_create_orders.rb
│ ├── 20150429224621_create_order_lines.rb
│ ├── 20150429224628_create_customers.rb
│ ├── 20150429224746_create_products.rb
│ ├── 20181102132612_create_event_store_events.rb
│ ├── 20181123154324_index_by_event_type.rb
│ ├── 20181123155503_limit_for_event_id.rb
│ ├── 20181207145051_binary_data_and_metadata.rb
│ ├── 20210102110315_harmonize_schema.rb
│ ├── 20210102182818_binary_to_jsonb.rb
│ ├── 20210103114304_created_at_precision.rb
│ ├── 20210103114309_add_valid_at.rb
│ ├── 20210103114314_no_global_stream_entries.rb
│ ├── 20210103114315_add_foreign_key_constraint_to_active_storage_attachments_for_blob_id.active_storage.rb
│ ├── 20210306222803_create_active_admin_comments.rb
│ ├── 20210318093812_disable_pg_crypto.rb
│ ├── 20210505162028_add_price_to_products.rb
│ ├── 20210505163254_add_price_to_order_lines.rb
│ ├── 20210617230722_add_uid_to_product.rb
│ ├── 20210626171021_add_uid_to_customer.rb
│ ├── 20210626210851_migrate_from_id_to_uuid.rb
│ ├── 20210719172643_add_discount_and_total_value_and_discount_value_to_orders.rb
│ ├── 20210824130811_add_stock_level_to_product.rb
│ ├── 20210830164940_drop_active_admin_comments.rb
│ ├── 20210907115224_string_to_native_uuids.rb
│ ├── 20210916191138_customers_add_registered_at.rb
│ ├── 20210918000940_products_add_registered_at.rb
│ ├── 20210918003625_products_drop_timestamps.rb
│ ├── 20211003170051_create_shipments.rb
│ ├── 20211116135113_create_products_in_orders_read_model.rb
│ ├── 20211116154857_create_customers_in_orders_read_model.rb
│ ├── 20211124220955_add_vat_rate_code_to_product.rb
│ ├── 20220109162627_create_invoice_read_model.rb
│ ├── 20220109175702_create_orders_in_invoices_read_model.rb
│ ├── 20220109175753_create_orders_in_shipments_read_model.rb
│ ├── 20220121131334_add_vip_to_customer.rb
│ ├── 20220219130650_create_clients.rb
│ ├── 20220219162101_create_client_orders.rb
│ ├── 20220519094635_create_coupons.rb
│ ├── 20220528213429_create_happy_hours.rb
│ ├── 20220607201447_add_happy_hour_value_to_orders.rb
│ ├── 20220703122711_drop_happy_hours.rb
│ ├── 20220703123059_create_time_promotions.rb
│ ├── 20220703133325_add_active_to_time_promotions.rb
│ ├── 20220703133431_add_unique_constraint_on_code_on_time_promotions.rb
│ ├── 20220723133454_remove_code_column_from_time_promotions.rb
│ ├── 20220829155333_create_public_offer_products.rb
│ ├── 20221104111403_create_client_order_lines_read_model.rb
│ ├── 20221104152434_create_client_order_products_read_model.rb
│ ├── 20221107085656_add_client_column_to_order.rb
│ ├── 20221109141019_fix_client_order_lines_product_id_column_type.rb
│ ├── 20221109150819_add_values_to_client_order.rb
│ ├── 20221124200914_add_prices_chart_to_product.rb
│ ├── 20221208185429_rename_future_prices_column.rb
│ ├── 20221214202855_add_uniq_uid_index_to_orders_order.rb
│ ├── 20221215214459_rename_future_prices_calendar_future_prices_calendar.rb
│ ├── 20221216113438_add_order_total_value_updated_at_to_orders_table.rb
│ ├── 20221216115301_add_discount_updated_at_to_orders_table.rb
│ ├── 20230110235838_add_lowest_recent_price_to_client_order_products.rb
│ ├── 20230118170447_create_availability_products.rb
│ ├── 20230123202350_add_paid_products_summary_to_clients.rb
│ ├── 20230124134558_add_paid_products_summary_to_customers.rb
│ ├── 20230124195547_remove_price_column_from_products.rb
│ ├── 20230126174907_add_accounting_column_to_customer.rb
│ ├── 20230127125135_create_table_accounts.rb
│ ├── 20230128110104_move_lowest_recent_price_to_public_offer.rb
│ ├── 20230130172454_rename_accounts_table.rb
│ ├── 20230130172623_add_account_id_to_accounts.rb
│ ├── 20230202175228_remove_lowest_recent_price_from_client_order_product.rb
│ ├── 20230906105023_add_event_id_index_to_event_store_events_in_streams.rb
│ ├── 20230906105317_add_foreign_key_on_event_id_to_event_store_events_in_streams.rb
│ ├── 20230906105318_validate_add_foreign_key_on_event_id_to_event_store_events_in_streams.rb
│ ├── 20240812080357_create_available_vat_rates.rb
│ ├── 20240826132237_add_available_to_products.rb
│ ├── 20240827090619_add_available_to_client_order_products.rb
│ ├── 20241002130622_rename_happy_hour_value_to_time_promotion_discount_value.rb
│ ├── 20241017115056_create_shipment_items.rb
│ ├── 20241018113912_change_order_uid_to_uuid_in_shipments.rb
│ ├── 20241129122521_add_time_promotion_discount_to_client_orders.rb
│ ├── 20241209100544_create_refunds.rb
│ ├── 20241209102208_create_refund_items.rb
│ └── 20250507182202_create_client_inbox_messages.rb
├── schema.rb
└── seeds.rb
├── docker-compose.yml
├── lib
└── configuration.rb
├── public
├── 404.html
├── 422.html
├── 500.html
├── apple-touch-icon-precomposed.png
├── apple-touch-icon.png
├── favicon.ico
└── robots.txt
├── script
└── big_picture.rb
├── test
├── availability
│ └── update_availability_test.rb
├── client_authentication
│ ├── create_test.rb
│ └── set_password_test.rb
├── client_orders
│ ├── customer_registered_test.rb
│ ├── discount_test.rb
│ ├── item_added_to_basket_test.rb
│ ├── item_removed_from_basket_test.rb
│ ├── order_cancelled_test.rb
│ ├── order_expired_test.rb
│ ├── order_line_test.rb
│ ├── order_paid_test.rb
│ ├── order_placed_test.rb
│ ├── product_price_changed_test.rb
│ ├── product_registered_test.rb
│ ├── time_promotion_discount_test.rb
│ ├── update_order_total_value_test.rb
│ ├── update_paid_orders_summary_test.rb
│ └── update_product_availability_test.rb
├── customers
│ ├── connect_account_test.rb
│ ├── customer_promoted_to_vip_test.rb
│ └── update_paid_orders_summary_test.rb
├── integration
│ ├── available_vat_rates_test.rb
│ ├── client_inbox_test.rb
│ ├── client_orders_test.rb
│ ├── coupons_test.rb
│ ├── customers_test.rb
│ ├── discount_test.rb
│ ├── login_test.rb
│ ├── orders_test.rb
│ ├── products_test.rb
│ ├── public_offer_test.rb
│ ├── refunds_test.rb
│ ├── routing_404_test.rb
│ ├── shipments_test.rb
│ ├── supplies_test.rb
│ └── time_promotions_test.rb
├── invoices
│ └── invoices_test.rb
├── orders
│ ├── assign_customer_first_test.rb
│ ├── broadcast_test.rb
│ ├── customer_registered_test.rb
│ ├── discount_test.rb
│ ├── item_added_to_basket_test.rb
│ ├── item_added_to_refund_test.rb
│ ├── item_removed_from_basket_test.rb
│ ├── item_removed_from_refund_test.rb
│ ├── order_cancelled_test.rb
│ ├── order_expired_test.rb
│ ├── order_line_test.rb
│ ├── order_paid_test.rb
│ ├── order_placed_test.rb
│ ├── product_price_changed_test.rb
│ ├── register_product_test.rb
│ ├── remove_time_promotion_discount_test.rb
│ ├── update_order_total_value_test.rb
│ └── update_time_promotion_discount_value_test.rb
├── processes
│ ├── determine_vat_rate_test.rb
│ ├── money_splitter_test.rb
│ ├── order_confirmation_test.rb
│ ├── order_item_invoicing_process_test.rb
│ ├── release_payment_process_test.rb
│ ├── reservation_process_test.rb
│ ├── shipment_process_test.rb
│ └── three_plus_one_free_test.rb
├── products
│ ├── product_test.rb
│ └── update_future_prices_calenar_test.rb
├── public_offer
│ └── product_price_changed_test.rb
├── read_model_handler_test.rb
├── services
│ ├── client
│ │ └── orders
│ │ │ └── submit_service_test.rb
│ └── orders
│ │ └── submit_service_test.rb
├── shipments
│ ├── item_added_to_shipment_test.rb
│ └── item_removed_from_shipment_test.rb
├── test_helper.rb
└── vat_rates
│ └── available_vat_rate_added_test.rb
└── vendor
└── javascript
└── .keep
/.gitignore:
--------------------------------------------------------------------------------
1 | .ruby-version
2 | .tool-versions
3 | /events_catalog/
4 |
--------------------------------------------------------------------------------
/build_events_catalog.sh:
--------------------------------------------------------------------------------
1 | rm -rf events_catalog
2 | npx @eventcatalog/create-eventcatalog@3.0.2 --organization-name "RailsEventStore" --empty events_catalog
3 | ruby -r "./build_events_catalog.rb" -e "BuildEventsCatalog.new.call"
4 | cd events_catalog
5 | npm run build
6 |
--------------------------------------------------------------------------------
/ecommerce/authentication/.mutant.yml:
--------------------------------------------------------------------------------
1 | requires:
2 | - ./test/test_helper
3 | integration: minitest
4 | usage: opensource
5 | coverage_criteria:
6 | process_abort: true
7 | matcher:
8 | subjects:
9 | - Authentication*
10 | ignore:
11 | - Authentication::Configuration#call
12 |
--------------------------------------------------------------------------------
/ecommerce/authentication/Gemfile:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 |
3 | eval_gemfile "../../infra/Gemfile.test"
4 | gem "infra", path: "../../infra"
--------------------------------------------------------------------------------
/ecommerce/authentication/Makefile:
--------------------------------------------------------------------------------
1 | install:
2 | @bundle install
3 |
4 | test:
5 | @bundle exec ruby -e "require \"rake/rake_test_loader\"" test/*_test.rb
6 |
7 | mutate:
8 | @RAILS_ENV=test bundle exec mutant run
9 |
10 | .PHONY: install test mutate
11 |
--------------------------------------------------------------------------------
/ecommerce/authentication/README.md:
--------------------------------------------------------------------------------
1 | # Authentication
2 |
3 | #### Up and running
4 |
5 | ```
6 | make install test mutate
7 | ```
8 |
--------------------------------------------------------------------------------
/ecommerce/authentication/lib/authentication/commands/connect_account_to_client.rb:
--------------------------------------------------------------------------------
1 | module Authentication
2 | class ConnectAccountToClient < Infra::Command
3 | attribute :account_id, Infra::Types::UUID
4 | attribute :client_id, Infra::Types::UUID
5 | alias aggregate_id account_id
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/ecommerce/authentication/lib/authentication/commands/register_account.rb:
--------------------------------------------------------------------------------
1 | module Authentication
2 | class RegisterAccount < Infra::Command
3 | attribute :account_id, Infra::Types::UUID
4 | alias aggregate_id account_id
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/ecommerce/authentication/lib/authentication/commands/set_login.rb:
--------------------------------------------------------------------------------
1 | module Authentication
2 | class SetLogin < Infra::Command
3 | attribute :account_id, Infra::Types::UUID
4 | attribute :login, Infra::Types::String
5 | alias aggregate_id account_id
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/ecommerce/authentication/lib/authentication/commands/set_password_hash.rb:
--------------------------------------------------------------------------------
1 | module Authentication
2 | class SetPasswordHash < Infra::Command
3 | attribute :account_id, Infra::Types::UUID
4 | attribute :password_hash, Infra::Types::String
5 | alias aggregate_id account_id
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/ecommerce/authentication/lib/authentication/events/account_connected_to_client.rb:
--------------------------------------------------------------------------------
1 | module Authentication
2 | class AccountConnectedToClient < Infra::Event
3 | attribute :account_id, Infra::Types::UUID
4 | attribute :client_id, Infra::Types::UUID
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/ecommerce/authentication/lib/authentication/events/account_registered.rb:
--------------------------------------------------------------------------------
1 | module Authentication
2 | class AccountRegistered < Infra::Event
3 | attribute :account_id, Infra::Types::UUID
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/ecommerce/authentication/lib/authentication/events/login_set.rb:
--------------------------------------------------------------------------------
1 | module Authentication
2 | class LoginSet < Infra::Event
3 | attribute :account_id, Infra::Types::UUID
4 | attribute :login, Infra::Types::String
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/ecommerce/authentication/lib/authentication/events/password_hash_set.rb:
--------------------------------------------------------------------------------
1 | module Authentication
2 | class PasswordHashSet < Infra::Event
3 | attribute :account_id, Infra::Types::UUID
4 | attribute :password_hash, Infra::Types::String
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/ecommerce/authentication/test/connect_account_to_client_test.rb:
--------------------------------------------------------------------------------
1 | require_relative "test_helper"
2 |
3 | module Authentication
4 | class ConnectAccountToClientTest < Test
5 | cover "Authentication*"
6 |
7 | def test_client_id_should_get_set
8 | account_id = SecureRandom.uuid
9 | client_id = SecureRandom.uuid
10 |
11 | act(RegisterAccount.new(account_id: account_id))
12 |
13 | account_connected_to_client = AccountConnectedToClient.new(data: { account_id: account_id, client_id: client_id })
14 |
15 | assert_events("Authentication::Account$#{account_id}", account_connected_to_client) do
16 | run_command(ConnectAccountToClient.new(account_id: account_id, client_id: client_id))
17 | end
18 | end
19 | end
20 | end
21 |
--------------------------------------------------------------------------------
/ecommerce/authentication/test/set_login_test.rb:
--------------------------------------------------------------------------------
1 | require_relative "test_helper"
2 |
3 | module Authentication
4 | class SetLoginTest < Test
5 | cover "Authentication*"
6 |
7 | def test_login_should_get_set
8 | account_id = SecureRandom.uuid
9 |
10 | act(RegisterAccount.new(account_id: account_id))
11 |
12 | login_set = LoginSet.new(data: { account_id: account_id, login: fake_login })
13 |
14 | assert_events("Authentication::Account$#{account_id}", login_set) do
15 | run_command(SetLogin.new(account_id: account_id, login: fake_login))
16 | end
17 | end
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/ecommerce/authentication/test/set_password_hash_test.rb:
--------------------------------------------------------------------------------
1 | require_relative "test_helper"
2 |
3 | module Authentication
4 | class SetPasswordHashTest < Test
5 | cover "Authentication*"
6 |
7 | def test_password_hash_should_get_set
8 | account_id = SecureRandom.uuid
9 | password_hash = SecureRandom.hex(10)
10 |
11 | act(RegisterAccount.new(account_id: account_id))
12 |
13 | password_hash_set = PasswordHashSet.new(data: { account_id: account_id, password_hash: password_hash })
14 |
15 | assert_events("Authentication::Account$#{account_id}", password_hash_set) do
16 | run_command(SetPasswordHash.new(account_id: account_id, password_hash: password_hash))
17 | end
18 | end
19 | end
20 | end
21 |
--------------------------------------------------------------------------------
/ecommerce/authentication/test/test_helper.rb:
--------------------------------------------------------------------------------
1 | require "minitest/autorun"
2 | require "mutant/minitest/coverage"
3 |
4 | require_relative "../lib/authentication"
5 |
6 | module Authentication
7 | class Test < Infra::InMemoryTest
8 | def before_setup
9 | super()
10 | Configuration.new.call(event_store, command_bus)
11 | end
12 |
13 | private
14 |
15 | def fake_login
16 | "fake_login"
17 | end
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/ecommerce/crm/.mutant.yml:
--------------------------------------------------------------------------------
1 | requires:
2 | - ./test/test_helper
3 | integration: minitest
4 | usage: opensource
5 | coverage_criteria:
6 | process_abort: true
7 | matcher:
8 | subjects:
9 | - Crm*
10 | ignore:
11 | - Crm::Configuration#call
12 |
13 |
--------------------------------------------------------------------------------
/ecommerce/crm/Gemfile:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 |
3 | eval_gemfile "../../infra/Gemfile.test"
4 | gem "infra", path: "../../infra"
--------------------------------------------------------------------------------
/ecommerce/crm/Makefile:
--------------------------------------------------------------------------------
1 | install:
2 | @bundle install
3 |
4 | test:
5 | @bundle exec ruby -e "require \"rake/rake_test_loader\"" test/*_test.rb
6 |
7 | mutate:
8 | @RAILS_ENV=test bundle exec mutant run
9 |
10 | .PHONY: install test mutate
11 |
--------------------------------------------------------------------------------
/ecommerce/crm/README.md:
--------------------------------------------------------------------------------
1 | # CRM
2 |
3 | [](https://github.com/RailsEventStore/cqrs-es-sample-with-res/actions/workflows/crm.yml)
4 |
5 | #### Up and running
6 |
7 | ```
8 | make install test mutate
9 | ```
10 |
--------------------------------------------------------------------------------
/ecommerce/crm/lib/crm/commands/assign_customer_to_order.rb:
--------------------------------------------------------------------------------
1 | module Crm
2 | class AssignCustomerToOrder < Infra::Command
3 | attribute :customer_id, Infra::Types::UUID
4 | attribute :order_id, Infra::Types::UUID
5 | alias aggregate_id order_id
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/ecommerce/crm/lib/crm/commands/promote_customer_to_vip.rb:
--------------------------------------------------------------------------------
1 | module Crm
2 | class PromoteCustomerToVip < Infra::Command
3 | attribute :customer_id, Infra::Types::UUID
4 | alias aggregate_id customer_id
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/ecommerce/crm/lib/crm/commands/register_customer.rb:
--------------------------------------------------------------------------------
1 | module Crm
2 | class RegisterCustomer < Infra::Command
3 | attribute :customer_id, Infra::Types::UUID
4 | attribute :name, Infra::Types::String
5 | alias aggregate_id customer_id
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/ecommerce/crm/lib/crm/customer_service.rb:
--------------------------------------------------------------------------------
1 | module Crm
2 |
3 | class OnRegistration
4 | def initialize(event_store)
5 | @repository = Infra::AggregateRootRepository.new(event_store)
6 | end
7 |
8 | def call(command)
9 | @repository.with_aggregate(Customer, command.aggregate_id) do |customer|
10 | customer.register(command.name)
11 | end
12 | end
13 | end
14 |
15 | class OnPromoteCustomerToVip
16 | def initialize(event_store)
17 | @repository = Infra::AggregateRootRepository.new(event_store)
18 | end
19 |
20 | def call(command)
21 | @repository.with_aggregate(Customer, command.aggregate_id) do |customer|
22 | customer.promote_to_vip
23 | end
24 | end
25 | end
26 |
27 | end
--------------------------------------------------------------------------------
/ecommerce/crm/lib/crm/events/customer_assigned_to_order.rb:
--------------------------------------------------------------------------------
1 | module Crm
2 | class CustomerAssignedToOrder < Infra::Event
3 | attribute :customer_id, Infra::Types::UUID
4 | attribute :order_id, Infra::Types::UUID
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/ecommerce/crm/lib/crm/events/customer_promoted_to_vip.rb:
--------------------------------------------------------------------------------
1 | module Crm
2 | class CustomerPromotedToVip < Infra::Event
3 | attribute :customer_id, Infra::Types::UUID
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/ecommerce/crm/lib/crm/events/customer_registered.rb:
--------------------------------------------------------------------------------
1 | module Crm
2 | class CustomerRegistered < Infra::Event
3 | attribute :customer_id, Infra::Types::UUID
4 | attribute :name, Infra::Types::String
5 | end
6 | end
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ecommerce/crm/lib/crm/order.rb:
--------------------------------------------------------------------------------
1 | module Crm
2 | class Order
3 | include AggregateRoot
4 |
5 | def initialize(id)
6 | @id = id
7 | end
8 |
9 | def set_customer(customer_id)
10 | return if @customer_id == customer_id
11 | apply CustomerAssignedToOrder.new(data: { order_id: @id, customer_id: customer_id })
12 | end
13 |
14 | private
15 |
16 | on CustomerAssignedToOrder do |event|
17 | @customer_id = event.data[:customer_id]
18 | end
19 | end
20 | end
21 |
--------------------------------------------------------------------------------
/ecommerce/crm/lib/crm/order_service.rb:
--------------------------------------------------------------------------------
1 | module Crm
2 | class OnSetCustomer
3 | def initialize(event_store)
4 | @repository = Infra::AggregateRootRepository.new(event_store)
5 | @event_store = event_store
6 | end
7 |
8 | def call(command)
9 | raise Customer::NotExists unless customer_exists?(command.customer_id)
10 | @repository.with_aggregate(Order, command.aggregate_id) do |order|
11 | order.set_customer(command.customer_id)
12 | end
13 | end
14 |
15 | private
16 |
17 | def customer_exists?(customer_id)
18 | customer_stream = @repository.stream_name(Customer, customer_id)
19 | !@event_store.read.stream(customer_stream).count.eql?(0)
20 | end
21 | end
22 | end
--------------------------------------------------------------------------------
/ecommerce/crm/test/test_helper.rb:
--------------------------------------------------------------------------------
1 | require "minitest/autorun"
2 | require "mutant/minitest/coverage"
3 |
4 | require_relative "../lib/crm"
5 |
6 | module Crm
7 | class Test < Infra::InMemoryTest
8 | def before_setup
9 | super()
10 | Configuration.new.call(event_store, command_bus)
11 | end
12 |
13 | private
14 |
15 | def register_customer(uid, name)
16 | run_command(RegisterCustomer.new(customer_id: uid, name: name))
17 | end
18 |
19 | def fake_name
20 | "Fake name"
21 | end
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/ecommerce/fulfillment/.mutant.yml:
--------------------------------------------------------------------------------
1 | requires:
2 | - ./test/test_helper
3 | integration: minitest
4 | usage: opensource
5 | coverage_criteria:
6 | process_abort: true
7 | matcher:
8 | subjects:
9 | - Fulfillment*
10 | ignore:
11 | - Fulfillment::Test*
12 | - Fulfillment::NumberGenerator*
13 | - Fulfillment::Configuration#call
14 | - Fulfillment::Configuration#initialize
15 |
--------------------------------------------------------------------------------
/ecommerce/fulfillment/Gemfile:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 |
3 | eval_gemfile "../../infra/Gemfile.test"
4 | gem "infra", path: "../../infra"
--------------------------------------------------------------------------------
/ecommerce/fulfillment/Makefile:
--------------------------------------------------------------------------------
1 | install:
2 | @bundle install
3 |
4 | test:
5 | @bundle exec ruby -e "require \"rake/rake_test_loader\"" test/*_test.rb
6 |
7 | mutate:
8 | @RAILS_ENV=test bundle exec mutant run
9 |
10 | .PHONY: install test mutate
11 |
--------------------------------------------------------------------------------
/ecommerce/fulfillment/lib/fulfillment/commands/cancel_order.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Fulfillment
4 | class CancelOrder < Infra::Command
5 | attribute :order_id, Infra::Types::UUID
6 |
7 | alias aggregate_id order_id
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/ecommerce/fulfillment/lib/fulfillment/commands/confirm_order.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Fulfillment
4 | class ConfirmOrder < Infra::Command
5 | attribute :order_id, Infra::Types::UUID
6 |
7 | alias aggregate_id order_id
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/ecommerce/fulfillment/lib/fulfillment/commands/register_order.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Fulfillment
4 | class RegisterOrder < Infra::Command
5 | attribute :order_id, Infra::Types::UUID
6 |
7 | alias aggregate_id order_id
8 | end
9 | end
--------------------------------------------------------------------------------
/ecommerce/fulfillment/lib/fulfillment/events/order_cancelled.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Fulfillment
4 | class OrderCancelled < Infra::Event
5 | attribute :order_id, Infra::Types::UUID
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/ecommerce/fulfillment/lib/fulfillment/events/order_confirmed.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Fulfillment
4 | class OrderConfirmed < Infra::Event
5 | attribute :order_id, Infra::Types::UUID
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/ecommerce/fulfillment/lib/fulfillment/events/order_registered.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Fulfillment
4 | class OrderRegistered < Infra::Event
5 | attribute :order_id, Infra::Types::UUID
6 | attribute :order_number, Infra::Types::OrderNumber
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/ecommerce/fulfillment/lib/fulfillment/fake_number_generator.rb:
--------------------------------------------------------------------------------
1 | module Fulfillment
2 | class FakeNumberGenerator
3 | FAKE_NUMBER = "2019/01/60".freeze
4 | def call
5 | FAKE_NUMBER
6 | end
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/ecommerce/fulfillment/lib/fulfillment/number_generator.rb:
--------------------------------------------------------------------------------
1 | module Fulfillment
2 | class NumberGenerator
3 | def call
4 | Time.now.strftime("%Y/%m/#{random_number}")
5 | end
6 |
7 | private
8 |
9 | def random_number
10 | SecureRandom.random_number(100)
11 | end
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/ecommerce/fulfillment/lib/fulfillment/on_cancel_order.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Fulfillment
4 | class OnCancelOrder
5 | def initialize(event_store)
6 | @repository = Infra::AggregateRootRepository.new(event_store)
7 | end
8 |
9 | def call(command)
10 | @repository.with_aggregate(Order, command.aggregate_id) do |order|
11 | order.cancel
12 | end
13 | end
14 | end
15 | end
--------------------------------------------------------------------------------
/ecommerce/fulfillment/lib/fulfillment/on_confirm_order.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Fulfillment
4 | class OnConfirmOrder
5 | def initialize(event_store)
6 | @repository = Infra::AggregateRootRepository.new(event_store)
7 | end
8 |
9 | def call(command)
10 | @repository.with_aggregate(Order, command.aggregate_id) do |order|
11 | order.confirm
12 | end
13 | end
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/ecommerce/fulfillment/lib/fulfillment/on_register_order.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Fulfillment
4 | class OnRegisterOrder
5 | def initialize(event_store, number_generator)
6 | @repository = Infra::AggregateRootRepository.new(event_store)
7 | @number_generator = number_generator
8 | end
9 |
10 | def call(command)
11 | @repository.with_aggregate(Order, command.aggregate_id) do |order|
12 | order_number = @number_generator.call
13 | order.register(order_number)
14 | end
15 | end
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/ecommerce/fulfillment/test/number_generator_test.rb:
--------------------------------------------------------------------------------
1 | require_relative "test_helper"
2 |
3 | module Fulfillment
4 | class NumberGeneratorTest < Test
5 | def test_includes_year
6 | assert_includes(NumberGenerator.new.call, "#{Time.now.year}")
7 | end
8 |
9 | def test_includes_month
10 | assert_includes(NumberGenerator.new.call, "#{Time.now.month}")
11 | end
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/ecommerce/fulfillment/test/test_helper.rb:
--------------------------------------------------------------------------------
1 | require "minitest/autorun"
2 | require "mutant/minitest/coverage"
3 |
4 | require_relative "../lib/fulfillment"
5 |
6 | module Fulfillment
7 | class Test < Infra::InMemoryTest
8 | def before_setup
9 | super
10 | generator = -> { FakeNumberGenerator.new }
11 | Configuration.new(generator).call(event_store, command_bus)
12 | end
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/ecommerce/inventory/.mutant.yml:
--------------------------------------------------------------------------------
1 | requires:
2 | - ./test/test_helper
3 | integration: minitest
4 | usage: opensource
5 | coverage_criteria:
6 | process_abort: true
7 | matcher:
8 | subjects:
9 | - Inventory*
10 | ignore:
11 | - Inventory::Test*
12 | - Inventory::Configuration#initialize
13 | - Inventory::Configuration#call
--------------------------------------------------------------------------------
/ecommerce/inventory/Gemfile:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 |
3 | eval_gemfile "../../infra/Gemfile.test"
4 | gem "infra", path: "../../infra"
--------------------------------------------------------------------------------
/ecommerce/inventory/Makefile:
--------------------------------------------------------------------------------
1 | install:
2 | @bundle install
3 |
4 | test:
5 | @bundle exec ruby -e "require \"rake/rake_test_loader\"" test/*_test.rb
6 |
7 | mutate:
8 | @RAILS_ENV=test bundle exec mutant run
9 |
10 | .PHONY: install test mutate
11 |
--------------------------------------------------------------------------------
/ecommerce/inventory/README.md:
--------------------------------------------------------------------------------
1 | # Inventory
2 |
3 | [](https://github.com/RailsEventStore/cqrs-es-sample-with-res/actions/workflows/inventory.yml)
4 |
5 | #### Up and running
6 |
7 | ```
8 | make install test mutate
9 | ```
10 |
--------------------------------------------------------------------------------
/ecommerce/inventory/lib/inventory/commands/dispatch.rb:
--------------------------------------------------------------------------------
1 | module Inventory
2 | class Dispatch < Infra::Command
3 | attribute :product_id, Infra::Types::UUID
4 | attribute :quantity, Infra::Types::Coercible::Integer.constrained(gteq: 1)
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/ecommerce/inventory/lib/inventory/commands/release.rb:
--------------------------------------------------------------------------------
1 | module Inventory
2 | class Release < Infra::Command
3 | attribute :product_id, Infra::Types::UUID
4 | attribute :quantity, Infra::Types::Coercible::Integer.constrained(gteq: 1)
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/ecommerce/inventory/lib/inventory/commands/reserve.rb:
--------------------------------------------------------------------------------
1 | module Inventory
2 | class Reserve < Infra::Command
3 | attribute :product_id, Infra::Types::UUID
4 | attribute :quantity, Infra::Types::Coercible::Integer.constrained(gteq: 1)
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/ecommerce/inventory/lib/inventory/commands/supply.rb:
--------------------------------------------------------------------------------
1 | module Inventory
2 | class Supply < Infra::Command
3 | attribute :product_id, Infra::Types::UUID
4 | attribute :quantity, Infra::Types::Coercible::Integer.constrained(gteq: 1)
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/ecommerce/inventory/lib/inventory/events/availability_changed.rb:
--------------------------------------------------------------------------------
1 | module Inventory
2 | class AvailabilityChanged < Infra::Event
3 | end
4 | end
5 |
--------------------------------------------------------------------------------
/ecommerce/inventory/lib/inventory/events/stock_level_changed.rb:
--------------------------------------------------------------------------------
1 | module Inventory
2 | class StockLevelChanged < Infra::Event
3 | end
4 | end
5 |
--------------------------------------------------------------------------------
/ecommerce/inventory/lib/inventory/events/stock_released.rb:
--------------------------------------------------------------------------------
1 | module Inventory
2 | class StockReleased < Infra::Event
3 | end
4 | end
5 |
--------------------------------------------------------------------------------
/ecommerce/inventory/lib/inventory/events/stock_reserved.rb:
--------------------------------------------------------------------------------
1 | module Inventory
2 | class StockReserved < Infra::Event
3 | end
4 | end
5 |
--------------------------------------------------------------------------------
/ecommerce/invoicing/.mutant.yml:
--------------------------------------------------------------------------------
1 | requires:
2 | - ./test/test_helper
3 | integration: minitest
4 | usage: opensource
5 | coverage_criteria:
6 | process_abort: true
7 | matcher:
8 | subjects:
9 | - Invoicing*
10 | ignore:
11 | - Invoicing::Configuration*
12 | - Invoicing::Test*
13 | - Invoicing::InvoiceItemTitleCatalog*
14 | - Invoicing::InvoiceService#initialize
15 | - Invoicing::FakeConcurrentInvoiceNumberGenerator#next_number
--------------------------------------------------------------------------------
/ecommerce/invoicing/Gemfile:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 |
3 | eval_gemfile "../../infra/Gemfile.test"
4 | gem "infra", path: "../../infra"
--------------------------------------------------------------------------------
/ecommerce/invoicing/Makefile:
--------------------------------------------------------------------------------
1 | install:
2 | @bundle install
3 |
4 | test:
5 | @bundle exec ruby -e "require \"rake/rake_test_loader\"" test/*_test.rb
6 |
7 | mutate:
8 | @RAILS_ENV=test bundle exec mutant run
9 |
10 | .PHONY: install test mutate
11 |
--------------------------------------------------------------------------------
/ecommerce/invoicing/README.md:
--------------------------------------------------------------------------------
1 | # Invoicing
2 |
3 | [](https://github.com/RailsEventStore/cqrs-es-sample-with-res/actions/workflows/invoicing.yml)
4 |
5 |
6 | #### Up and running
7 |
8 | ```
9 | make install test mutate
10 | ```
11 |
12 |
13 | ### Domain knowledge
14 |
15 | [Steps for successful invoicing](https://www.xero.com/guides/invoicing/)
--------------------------------------------------------------------------------
/ecommerce/invoicing/lib/invoicing/fake_concurrent_invoice_number_generator.rb:
--------------------------------------------------------------------------------
1 | module Invoicing
2 | class FakeConcurrentInvoiceNumberGenerator
3 | def initialize
4 | @counter = 0
5 | end
6 |
7 | def call(issue_date)
8 | issue_date.strftime("#{next_number}/%m/%Y")
9 | end
10 |
11 | private
12 |
13 | def next_number
14 | @counter += 1
15 | return 1 if @counter < 2
16 | @counter - 1
17 | end
18 | end
19 | end
--------------------------------------------------------------------------------
/ecommerce/invoicing/lib/invoicing/invoice_item_title_catalog.rb:
--------------------------------------------------------------------------------
1 | module Invoicing
2 | class InvoiceItemTitleCatalog
3 | def initialize(event_store)
4 | @event_store = event_store
5 | end
6 |
7 | def invoice_item_title_for_product(product_id)
8 | @event_store
9 | .read
10 | .of_type(ProductNameDisplayedSet)
11 | .to_a
12 | .filter { |e| e.data.fetch(:product_id).eql?(product_id) }
13 | .last
14 | .data
15 | .fetch(:name_displayed)
16 | end
17 | end
18 | end
--------------------------------------------------------------------------------
/ecommerce/invoicing/lib/invoicing/invoice_number_generator.rb:
--------------------------------------------------------------------------------
1 | module Invoicing
2 | class InvoiceNumberGenerator
3 | def initialize(event_store)
4 | @event_store = event_store
5 | end
6 |
7 | def call(issue_date)
8 | issue_date.strftime("#{next_number(issue_date)}/%m/%Y")
9 | end
10 |
11 | private
12 |
13 | def next_number(issue_date)
14 | stream_name = "InvoiceIssued$#{issue_date.strftime("%Y-%m")}"
15 | @event_store.read.stream(stream_name).count + 1
16 | end
17 | end
18 | end
--------------------------------------------------------------------------------
/ecommerce/invoicing/lib/invoicing/product.rb:
--------------------------------------------------------------------------------
1 | module Invoicing
2 | class Product
3 | include AggregateRoot
4 |
5 | def initialize(product_id)
6 | @product_id = product_id
7 | end
8 |
9 | def set_name_displayed(name)
10 | apply(
11 | ProductNameDisplayedSet.new(
12 | data: {
13 | product_id: @product_id,
14 | name_displayed: name
15 | }
16 | )
17 | )
18 | end
19 |
20 | private
21 |
22 | on ProductNameDisplayedSet do |event|
23 | @name_displayed = event.data[:name_displayed]
24 | end
25 | end
26 | end
--------------------------------------------------------------------------------
/ecommerce/invoicing/test/fake_concurrent_invoice_number_generator_test.rb:
--------------------------------------------------------------------------------
1 | require_relative "test_helper"
2 |
3 | module Invoicing
4 | class FakeConcurrentInvoiceNumberGeneratorTest < Test
5 | cover "Invoicing::FakeInvoiceNumberGenerator"
6 |
7 | def test_fetching_next_number
8 | issue_date = Date.new(2022, 1, 5)
9 | number_generator = FakeConcurrentInvoiceNumberGenerator.new
10 | assert_equal("1/01/2022", number_generator.call(issue_date))
11 | assert_equal("1/01/2022", number_generator.call(issue_date))
12 | assert_equal("2/01/2022", number_generator.call(issue_date))
13 | end
14 | end
15 | end
--------------------------------------------------------------------------------
/ecommerce/invoicing/test/invoice_item_test.rb:
--------------------------------------------------------------------------------
1 | require_relative "test_helper"
2 |
3 | module Invoicing
4 | class InvoiceItemTest < Test
5 | cover "Invoicing::Invoice"
6 |
7 | def test_initializer
8 | product_id = SecureRandom.uuid
9 | vat_rate = Infra::Types::VatRate.new(rate: 20, code: "20")
10 | unit_price = 100.to_d
11 | quantity = 20
12 | title = 'test'
13 | item = InvoiceItem.new(product_id, title, unit_price, vat_rate, quantity)
14 |
15 | assert_equal product_id, item.product_id
16 | assert_equal title, item.title
17 | assert_equal vat_rate, item.vat_rate
18 | assert_equal unit_price, item.unit_price
19 | assert_equal quantity, item.quantity
20 | end
21 | end
22 | end
--------------------------------------------------------------------------------
/ecommerce/invoicing/test/setting_product_displayed_name_test.rb:
--------------------------------------------------------------------------------
1 | require_relative "test_helper"
2 |
3 | module Invoicing
4 | class SettingProductDisplayedNameTest < Test
5 | cover "Invoicing::SetProductNameDisplayedOnInvoiceHandler"
6 |
7 | def test_adding_to_invoice
8 | product_id = SecureRandom.uuid
9 | name_displayed = 'test'
10 | stream = "Invoicing::Product$#{product_id}"
11 |
12 | assert_events(
13 | stream,
14 | ProductNameDisplayedSet.new(
15 | data: {
16 | product_id: product_id,
17 | name_displayed: name_displayed,
18 | }
19 | )
20 | ) { set_product_name_displayed(product_id, name_displayed) }
21 | end
22 | end
23 | end
--------------------------------------------------------------------------------
/ecommerce/ordering/.mutant.yml:
--------------------------------------------------------------------------------
1 | requires:
2 | - ./test/test_helper
3 | integration: minitest
4 | usage: opensource
5 | coverage_criteria:
6 | process_abort: true
7 | matcher:
8 | subjects:
9 | - Ordering*
10 | ignore:
11 | - Ordering::Test*
12 | - Ordering::Configuration#call
13 | - Ordering::Configuration#initialize
14 | - Ordering::RefundableProducts#call
15 |
--------------------------------------------------------------------------------
/ecommerce/ordering/Gemfile:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 |
3 | eval_gemfile "../../infra/Gemfile.test"
4 | gem "infra", path: "../../infra"
--------------------------------------------------------------------------------
/ecommerce/ordering/Makefile:
--------------------------------------------------------------------------------
1 | install:
2 | @bundle install
3 |
4 | test:
5 | @bundle exec ruby -e "require \"rake/rake_test_loader\"" test/*_test.rb
6 |
7 | mutate:
8 | @RAILS_ENV=test bundle exec mutant run
9 |
10 | .PHONY: install test mutate
11 |
--------------------------------------------------------------------------------
/ecommerce/ordering/lib/ordering/commands/add_item_to_refund.rb:
--------------------------------------------------------------------------------
1 | module Ordering
2 | class AddItemToRefund < Infra::Command
3 | attribute :refund_id, Infra::Types::UUID
4 | attribute :order_id, Infra::Types::UUID
5 | attribute :product_id, Infra::Types::UUID
6 |
7 | alias aggregate_id refund_id
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/ecommerce/ordering/lib/ordering/commands/create_draft_refund.rb:
--------------------------------------------------------------------------------
1 | module Ordering
2 | class CreateDraftRefund < Infra::Command
3 | attribute :refund_id, Infra::Types::UUID
4 | attribute :order_id, Infra::Types::UUID
5 |
6 | alias aggregate_id refund_id
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/ecommerce/ordering/lib/ordering/commands/remove_item_from_refund.rb:
--------------------------------------------------------------------------------
1 | module Ordering
2 | class RemoveItemFromRefund < Infra::Command
3 | attribute :refund_id, Infra::Types::UUID
4 | attribute :order_id, Infra::Types::UUID
5 | attribute :product_id, Infra::Types::UUID
6 |
7 | alias aggregate_id refund_id
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/ecommerce/ordering/lib/ordering/events/draft_refund_created.rb:
--------------------------------------------------------------------------------
1 | module Ordering
2 | class DraftRefundCreated < Infra::Event
3 | attribute :refund_id, Infra::Types::UUID
4 | attribute :order_id, Infra::Types::UUID
5 | attribute :refundable_products, Infra::Types::Array.of(
6 | Infra::Types::Hash.schema(
7 | product_id: Infra::Types::UUID,
8 | quantity: Infra::Types::Integer
9 | )
10 | )
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/ecommerce/ordering/lib/ordering/events/item_added_to_refund.rb:
--------------------------------------------------------------------------------
1 | module Ordering
2 | class ItemAddedToRefund < Infra::Event
3 | attribute :refund_id, Infra::Types::UUID
4 | attribute :order_id, Infra::Types::UUID
5 | attribute :product_id, Infra::Types::UUID
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/ecommerce/ordering/lib/ordering/events/item_removed_from_refund.rb:
--------------------------------------------------------------------------------
1 | module Ordering
2 | class ItemRemovedFromRefund < Infra::Event
3 | attribute :refund_id, Infra::Types::UUID
4 | attribute :order_id, Infra::Types::UUID
5 | attribute :product_id, Infra::Types::UUID
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/ecommerce/ordering/lib/ordering/refundable_products.rb:
--------------------------------------------------------------------------------
1 | module Ordering
2 | class RefundableProducts
3 | def call(event_store, order_id)
4 | accepted_event = event_store
5 | .read.backward
6 | .stream("Pricing::Offer$#{order_id}")
7 | .of_type(Pricing::OfferAccepted)
8 | .first
9 |
10 | placed_event = event_store
11 | .read
12 | .stream("Fulfillment::Order$#{order_id}")
13 | .first
14 |
15 | accepted_event.data.fetch(:order_lines) if placed_event
16 | end
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/ecommerce/ordering/test/test_helper.rb:
--------------------------------------------------------------------------------
1 | require "minitest/autorun"
2 | require "mutant/minitest/coverage"
3 |
4 | require_relative "../lib/ordering"
5 |
6 | require_relative "../../pricing/lib/pricing"
7 | require_relative "../../fulfillment/lib/fulfillment"
8 |
9 | module Ordering
10 | class Test < Infra::InMemoryTest
11 | def before_setup
12 | generator = -> { Fulfillment::FakeNumberGenerator.new }
13 | super
14 | Configuration.new.call(event_store, command_bus)
15 | Pricing::Configuration.new.call(event_store, command_bus)
16 | Fulfillment::Configuration.new(generator).call(event_store, command_bus)
17 | end
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/ecommerce/payments/.mutant.yml:
--------------------------------------------------------------------------------
1 | requires:
2 | - ./test/test_helper
3 | integration: minitest
4 | usage: opensource
5 | coverage_criteria:
6 | process_abort: true
7 | matcher:
8 | subjects:
9 | - Payments*
10 | ignore:
11 | - Payments::Payment#authorized?
12 | - Payments::Test*
13 | - Payments::Configuration#call
14 |
--------------------------------------------------------------------------------
/ecommerce/payments/Gemfile:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 |
3 | eval_gemfile "../../infra/Gemfile.test"
4 | gem "infra", path: "../../infra"
5 |
--------------------------------------------------------------------------------
/ecommerce/payments/Makefile:
--------------------------------------------------------------------------------
1 | install:
2 | @bundle install
3 |
4 | test:
5 | @bundle exec ruby -e "require \"rake/rake_test_loader\"" test/*_test.rb
6 |
7 | mutate:
8 | @RAILS_ENV=test bundle exec mutant run
9 |
10 | .PHONY: install test mutate
11 |
--------------------------------------------------------------------------------
/ecommerce/payments/README.md:
--------------------------------------------------------------------------------
1 | # Payments
2 |
3 | [](https://github.com/RailsEventStore/cqrs-es-sample-with-res/actions/workflows/payments.yml)
4 |
5 | The `Payments::Payment` aggregate manages the following states:
6 |
7 | - authorized
8 | - captured
9 | - released
10 |
11 | This Payment object is fully event sourced.
12 |
13 | #### Up and running
14 |
15 | ```
16 | make install test mutate
17 | ```
18 |
--------------------------------------------------------------------------------
/ecommerce/payments/lib/payments/commands.rb:
--------------------------------------------------------------------------------
1 | module Payments
2 | class SetPaymentAmount < Infra::Command
3 | attribute :order_id, Infra::Types::UUID
4 | attribute :amount, Infra::Types::Nominal::Decimal
5 | end
6 |
7 | class AuthorizePayment < Infra::Command
8 | attribute :order_id, Infra::Types::UUID
9 | end
10 |
11 | class CapturePayment < Infra::Command
12 | attribute :order_id, Infra::Types::UUID
13 | end
14 |
15 | class ReleasePayment < Infra::Command
16 | attribute :order_id, Infra::Types::UUID
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/ecommerce/payments/lib/payments/events.rb:
--------------------------------------------------------------------------------
1 | module Payments
2 | class PaymentAmountSet < Infra::Event
3 | attribute :order_id, Infra::Types::UUID
4 | attribute :amount, Infra::Types::Nominal::Decimal
5 | end
6 |
7 | class PaymentAuthorized < Infra::Event
8 | attribute :order_id, Infra::Types::UUID
9 | end
10 |
11 | class PaymentCaptured < Infra::Event
12 | attribute :order_id, Infra::Types::UUID
13 | end
14 |
15 | class PaymentReleased < Infra::Event
16 | attribute :order_id, Infra::Types::UUID
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/ecommerce/payments/lib/payments/fake_gateway.rb:
--------------------------------------------------------------------------------
1 | module Payments
2 | class FakeGateway
3 | def initialize
4 | @authorized_transactions = []
5 | end
6 |
7 | def reset
8 | @authorized_transactions = []
9 | end
10 |
11 | def authorize_transaction(transaction_id, amount)
12 | authorized_transactions << [transaction_id, amount]
13 | end
14 |
15 | def authorized_transactions
16 | @authorized_transactions
17 | end
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/ecommerce/payments/lib/payments/on_authorize_payment.rb:
--------------------------------------------------------------------------------
1 | module Payments
2 | class OnAuthorizePayment
3 | def initialize(event_store, gateway)
4 | @repository = AggregateRoot::Repository.new(event_store)
5 | @gateway = gateway
6 | end
7 |
8 | def call(command)
9 | @repository.with_aggregate(
10 | Payment.new,
11 | "Payments::Payment$#{command.order_id}"
12 | ) { |payment| payment.authorize(command.order_id, @gateway.call) }
13 | end
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/ecommerce/payments/lib/payments/on_capture_payment.rb:
--------------------------------------------------------------------------------
1 | module Payments
2 | class OnCapturePayment
3 | def initialize(event_store)
4 | @repository = AggregateRoot::Repository.new(event_store)
5 | end
6 |
7 | def call(command)
8 | @repository.with_aggregate(
9 | Payment.new,
10 | "Payments::Payment$#{command.order_id}"
11 | ) { |payment| payment.capture }
12 | end
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/ecommerce/payments/lib/payments/on_release_payment.rb:
--------------------------------------------------------------------------------
1 | module Payments
2 | class OnReleasePayment
3 | def initialize(event_store)
4 | @repository = AggregateRoot::Repository.new(event_store)
5 | end
6 |
7 | def call(command)
8 | @repository.with_aggregate(
9 | Payment.new,
10 | "Payments::Payment$#{command.order_id}"
11 | ) { |payment| payment.release }
12 | end
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/ecommerce/payments/lib/payments/on_set_payment_amount.rb:
--------------------------------------------------------------------------------
1 | module Payments
2 | class OnSetPaymentAmount
3 | def initialize(event_store)
4 | @repository = AggregateRoot::Repository.new(event_store)
5 | end
6 |
7 | def call(command)
8 | @repository.with_aggregate(
9 | Payment.new,
10 | "Payments::Payment$#{command.order_id}"
11 | ) { |payment| payment.set_amount(command.order_id, command.amount) }
12 | end
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/ecommerce/payments/test/fake_gateway_test.rb:
--------------------------------------------------------------------------------
1 | require_relative "test_helper"
2 |
3 | module Payments
4 | class FakeGatewayTest < Test
5 | cover "Payments::FakeGateway*"
6 |
7 | def test_happy_path
8 | gateway = FakeGateway.new
9 | gateway.authorize_transaction("12", 20)
10 | assert_equal([["12", 20]], gateway.authorized_transactions)
11 | gateway.reset
12 | assert_equal([], gateway.authorized_transactions)
13 | end
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/ecommerce/payments/test/on_capture_payment_test.rb:
--------------------------------------------------------------------------------
1 | require_relative "test_helper"
2 |
3 | module Payments
4 | class OnCapturePaymentTest < Test
5 | cover "Payments::OnCapturePayment*"
6 |
7 | def test_capture_payment
8 | order_id = SecureRandom.uuid
9 | stream = "Payments::Payment$#{order_id}"
10 |
11 | arrange(
12 | SetPaymentAmount.new(order_id: order_id, amount: 20),
13 | AuthorizePayment.new(order_id: order_id)
14 | )
15 |
16 | assert_equal(20, payment_gateway.authorized_transactions[0][1])
17 |
18 | assert_events(
19 | stream,
20 | PaymentCaptured.new(data: { order_id: order_id })
21 | ) { act(CapturePayment.new(order_id: order_id)) }
22 | end
23 | end
24 | end
25 |
--------------------------------------------------------------------------------
/ecommerce/payments/test/on_release_payment_test.rb:
--------------------------------------------------------------------------------
1 | require_relative "test_helper"
2 |
3 | module Payments
4 | class OnReleasePaymentTest < Test
5 | cover "Payments::OnReleasePayment*"
6 |
7 | def test_capture_payment
8 | order_id = SecureRandom.uuid
9 | stream = "Payments::Payment$#{order_id}"
10 |
11 | arrange(
12 | AuthorizePayment.new(order_id: order_id)
13 | )
14 |
15 | assert_events(
16 | stream,
17 | PaymentReleased.new(data: { order_id: order_id })
18 | ) { act(ReleasePayment.new(order_id: order_id)) }
19 | end
20 | end
21 | end
22 |
--------------------------------------------------------------------------------
/ecommerce/payments/test/test_helper.rb:
--------------------------------------------------------------------------------
1 | require "minitest/autorun"
2 | require "mutant/minitest/coverage"
3 |
4 | require_relative "../lib/payments"
5 |
6 | module Payments
7 | class Test < Infra::InMemoryTest
8 | attr_reader :payment_gateway
9 |
10 | def before_setup
11 | super
12 | @payment_gateway = FakeGateway.new
13 | Configuration.new(-> { @payment_gateway }).call(event_store, command_bus)
14 | end
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/ecommerce/pricing/.mutant.yml:
--------------------------------------------------------------------------------
1 | requires:
2 | - ./test/test_helper
3 | integration: minitest
4 | usage: opensource
5 | coverage_criteria:
6 | process_abort: true
7 | matcher:
8 | subjects:
9 | - Pricing*
10 | ignore:
11 | - Pricing::Configuration*
12 | - Pricing::Test*
13 | - Pricing::OnCalculateTotalValue#call
14 | - Pricing::OnCalculateTotalValue#calculate_sub_amounts
15 | - Pricing::Offer#make_free_product
16 | - Pricing::Offer#remove_free_product
17 | - Pricing::Offer::List*
18 | - Pricing::SetFuturePriceHandler#call
19 |
--------------------------------------------------------------------------------
/ecommerce/pricing/Gemfile:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 |
3 | eval_gemfile "../../infra/Gemfile.test"
4 | gem "infra", path: "../../infra"
5 |
6 | group :test do
7 | gem "timecop"
8 | end
9 |
--------------------------------------------------------------------------------
/ecommerce/pricing/README.md:
--------------------------------------------------------------------------------
1 | # Pricing
2 |
3 | [](https://github.com/RailsEventStore/cqrs-es-sample-with-res/actions/workflows/pricing.yml)
4 |
5 | The `Pricing::Coupon` aggregate manages the creation and usage of a coupon. This includes as of now:
6 |
7 | - registration
8 |
9 | After each successful action an appropriate event is published in the Coupon stream.
10 |
11 | | Command | Event | Service used to apply |
12 | |:---------------:|:-----:|:----------------------|
13 | | RegisterCoupon | CouponRegistered | OnCouponRegister |
14 |
15 | #### Up and running
16 |
17 | ```
18 | make install test mutate
19 | ```
20 |
--------------------------------------------------------------------------------
/ecommerce/pricing/lib/pricing/apply_time_promotion.rb:
--------------------------------------------------------------------------------
1 | module Pricing
2 | class ApplyTimePromotion
3 | def call(event)
4 | discount = PromotionsCalendar.new(event_store).current_time_promotions_discount
5 |
6 | if discount.exists?
7 | command_bus.(SetTimePromotionDiscount.new(order_id: event.data.fetch(:order_id), amount: discount.value))
8 | else
9 | command_bus.(RemoveTimePromotionDiscount.new(order_id: event.data.fetch(:order_id)))
10 | end
11 |
12 | rescue NotPossibleToAssignDiscountTwice, NotPossibleToRemoveWithoutDiscount
13 | end
14 |
15 | private
16 |
17 | def command_bus
18 | Pricing.command_bus
19 | end
20 |
21 | def event_store
22 | Pricing.event_store
23 | end
24 | end
25 | end
26 |
--------------------------------------------------------------------------------
/ecommerce/pricing/lib/pricing/calculate_order_sub_amounts_value.rb:
--------------------------------------------------------------------------------
1 | module Pricing
2 | class CalculateOrderTotalSubAmountsValue
3 | def call(event)
4 | command_bus.(CalculateSubAmounts.new(order_id: event.data.fetch(:order_id)))
5 | end
6 |
7 | private
8 |
9 | def command_bus
10 | Pricing.command_bus
11 | end
12 | end
13 | end
14 |
15 |
--------------------------------------------------------------------------------
/ecommerce/pricing/lib/pricing/calculate_order_total_value.rb:
--------------------------------------------------------------------------------
1 | module Pricing
2 | class CalculateOrderTotalValue
3 | def call(event)
4 | command_bus.(CalculateTotalValue.new(order_id: event.data.fetch(:order_id)))
5 | end
6 |
7 | private
8 |
9 | def command_bus
10 | Pricing.command_bus
11 | end
12 | end
13 | end
14 |
15 |
--------------------------------------------------------------------------------
/ecommerce/pricing/lib/pricing/coupon.rb:
--------------------------------------------------------------------------------
1 | require_relative 'events'
2 |
3 | module Pricing
4 | class Coupon
5 | include AggregateRoot
6 |
7 | AlreadyRegistered = Class.new(StandardError)
8 |
9 | def initialize(id)
10 | @id = id
11 | end
12 |
13 | def register(name, code, discount)
14 | raise AlreadyRegistered if @registered
15 |
16 | apply CouponRegistered.new(
17 | data: {
18 | coupon_id: @id,
19 | name: name,
20 | code: code,
21 | discount: discount
22 | }
23 | )
24 | end
25 |
26 | on CouponRegistered do |event|
27 | @registered = true
28 | end
29 | end
30 | end
31 |
32 |
--------------------------------------------------------------------------------
/ecommerce/pricing/lib/pricing/price_change.rb:
--------------------------------------------------------------------------------
1 | module Pricing
2 | class PriceChange
3 | include AggregateRoot
4 |
5 | def initialize(product_id)
6 | @product_id = product_id
7 | end
8 |
9 | def set_price(price)
10 | apply(PriceSet.new(data: { product_id: @product_id, price: price }))
11 | end
12 |
13 | private
14 |
15 | on(PriceSet) { |_| }
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/ecommerce/pricing/lib/pricing/time_promotion.rb:
--------------------------------------------------------------------------------
1 | module Pricing
2 | class TimePromotion
3 | include AggregateRoot
4 | AlreadyCreated = Class.new(StandardError)
5 |
6 | def initialize(id)
7 | @id = id
8 | end
9 |
10 | def create(discount, start_time, end_time, label)
11 | raise AlreadyCreated if @created
12 | apply TimePromotionCreated.new(
13 | data: {
14 | time_promotion_id: @id,
15 | discount: discount,
16 | start_time: start_time,
17 | end_time: end_time,
18 | label: label
19 | }
20 | )
21 | end
22 |
23 | private
24 |
25 | on TimePromotionCreated do |_|
26 | @created = true
27 | end
28 |
29 | end
30 | end
31 |
--------------------------------------------------------------------------------
/ecommerce/product_catalog/.mutant.yml:
--------------------------------------------------------------------------------
1 | requires:
2 | - ./test/test_helper
3 | integration: minitest
4 | usage: opensource
5 | coverage_criteria:
6 | process_abort: true
7 | matcher:
8 | subjects:
9 | - ProductCatalog*
10 |
--------------------------------------------------------------------------------
/ecommerce/product_catalog/Gemfile:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 |
3 | eval_gemfile "../../infra/Gemfile.test"
4 | gem "infra", path: "../../infra"
5 |
--------------------------------------------------------------------------------
/ecommerce/product_catalog/Makefile:
--------------------------------------------------------------------------------
1 | install:
2 | @bundle install
3 |
4 | test:
5 | @bundle exec ruby -e "require \"rake/rake_test_loader\"" test/*_test.rb
6 |
7 | mutate:
8 | @RAILS_ENV=test bundle exec mutant run
9 |
10 | .PHONY: install test mutate
11 |
--------------------------------------------------------------------------------
/ecommerce/product_catalog/README.md:
--------------------------------------------------------------------------------
1 | # Product Catalog
2 |
3 | [](https://github.com/RailsEventStore/cqrs-es-sample-with-res/actions/workflows/product_catalog.yml)
4 |
5 | We implement this domain as a CRUD-based bounded context. The goal is to present
6 | how to deal with such CRUD-ish domains and to show how to integrate it with
7 | parts of the system.
8 |
9 | It's just a single ActiveRecord `Product` class.
10 |
11 | We wrap it with a `ProductCatalog` namespace to explicitly set its boundaries.
12 |
13 | This Bounded Context has both - the write part and the read part as the
14 | same model. You can say it's not really CQRS - which is true for many CRUDish
15 | bounded contexts.
16 |
17 | #### Up and running
18 |
19 | ```
20 | make install test mutate
21 | ```
22 |
--------------------------------------------------------------------------------
/ecommerce/product_catalog/lib/product_catalog.rb:
--------------------------------------------------------------------------------
1 | require "infra"
2 | require_relative "product_catalog/commands"
3 | require_relative "product_catalog/events"
4 | require_relative "product_catalog/registration"
5 | require_relative "product_catalog/naming"
6 |
7 | module ProductCatalog
8 |
9 | class Configuration
10 | def call(event_store, command_bus)
11 | command_bus.register(RegisterProduct, Registration.new(event_store))
12 | command_bus.register(NameProduct, Naming.new(event_store))
13 | end
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/ecommerce/product_catalog/lib/product_catalog/commands.rb:
--------------------------------------------------------------------------------
1 | module ProductCatalog
2 | class RegisterProduct < Infra::Command
3 | attribute :product_id, Infra::Types::UUID
4 | end
5 |
6 | class NameProduct < Infra::Command
7 | attribute :product_id, Infra::Types::UUID
8 | attribute :name, Infra::Types::String
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/ecommerce/product_catalog/lib/product_catalog/events.rb:
--------------------------------------------------------------------------------
1 | module ProductCatalog
2 |
3 | class ProductRegistered < Infra::Event
4 | attribute :product_id, Infra::Types::UUID
5 | end
6 |
7 | class ProductNamed < Infra::Event
8 | attribute :product_id, Infra::Types::String
9 | end
10 |
11 | end
12 |
--------------------------------------------------------------------------------
/ecommerce/product_catalog/lib/product_catalog/naming.rb:
--------------------------------------------------------------------------------
1 | module ProductCatalog
2 |
3 | class Naming
4 | def initialize(event_store)
5 | @event_store = event_store
6 | end
7 |
8 | def call(cmd)
9 | @event_store.publish(product_named_event(cmd), stream_name: stream_name(cmd))
10 | end
11 |
12 | private
13 |
14 | def product_named_event(cmd)
15 | ProductNamed.new(
16 | data: {
17 | product_id: cmd.product_id,
18 | name: cmd.name
19 | }
20 | )
21 | end
22 |
23 | def stream_name(cmd)
24 | "Catalog::ProductName$#{cmd.product_id}"
25 | end
26 | end
27 | end
28 |
--------------------------------------------------------------------------------
/ecommerce/product_catalog/test/naming_test.rb:
--------------------------------------------------------------------------------
1 | require_relative 'test_helper'
2 | module ProductCatalog
3 | class NamingTest < Test
4 | cover "ProductCatalog*"
5 |
6 | def test_should_publish_event
7 | uid = SecureRandom.uuid
8 | product_named = ProductCatalog::ProductNamed.new(data: {product_id: uid, name: fake_name})
9 | assert_events("Catalog::ProductName$#{uid}", product_named) do
10 | name_product(uid, fake_name)
11 | end
12 | end
13 |
14 | private
15 |
16 | def name_product(uid, name)
17 | run_command(NameProduct.new(product_id: uid, name: name))
18 | end
19 |
20 | def fake_name
21 | "Fake name"
22 | end
23 | end
24 | end
25 |
--------------------------------------------------------------------------------
/ecommerce/product_catalog/test/test_helper.rb:
--------------------------------------------------------------------------------
1 | require "minitest/autorun"
2 | require "mutant/minitest/coverage"
3 |
4 | require_relative "../lib/product_catalog"
5 |
6 | module ProductCatalog
7 | class Test < Infra::InMemoryTest
8 |
9 | def before_setup
10 | super()
11 | Configuration.new.call(event_store, command_bus)
12 | end
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/ecommerce/shipping/.mutant.yml:
--------------------------------------------------------------------------------
1 | requires:
2 | - ./test/test_helper
3 | integration: minitest
4 | usage: opensource
5 | coverage_criteria:
6 | process_abort: true
7 | matcher:
8 | subjects:
9 | - Shipping*
10 | ignore:
11 | - Shipping::Test*
12 | - Shipping::Configuration#initialize
13 | - Shipping::Configuration#call
14 |
--------------------------------------------------------------------------------
/ecommerce/shipping/Gemfile:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 |
3 | eval_gemfile "../../infra/Gemfile.test"
4 | gem "infra", path: "../../infra"
--------------------------------------------------------------------------------
/ecommerce/shipping/Makefile:
--------------------------------------------------------------------------------
1 | install:
2 | @bundle install
3 |
4 | test:
5 | @bundle exec ruby -e "require \"rake/rake_test_loader\"" test/*_test.rb
6 |
7 | mutate:
8 | @RAILS_ENV=test bundle exec mutant run
9 |
10 | .PHONY: install test mutate
11 |
--------------------------------------------------------------------------------
/ecommerce/shipping/README.md:
--------------------------------------------------------------------------------
1 | # Shipping
2 |
3 | [](https://github.com/RailsEventStore/cqrs-es-sample-with-res/actions/workflows/shipping.yml)
4 |
5 | #### Up and running
6 |
7 | ```
8 | make install test mutate
9 | ```
10 |
--------------------------------------------------------------------------------
/ecommerce/shipping/lib/shipping/commands/add_item_to_shipment_picking_list.rb:
--------------------------------------------------------------------------------
1 | module Shipping
2 | class AddItemToShipmentPickingList < Infra::Command
3 | attribute :order_id, Infra::Types::UUID
4 | attribute :product_id, Infra::Types::UUID
5 |
6 | alias aggregate_id order_id
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/ecommerce/shipping/lib/shipping/commands/add_shipping_address_to_shipment.rb:
--------------------------------------------------------------------------------
1 | module Shipping
2 | class AddShippingAddressToShipment < Infra::Command
3 | attribute :order_id, Infra::Types::UUID
4 | attribute :postal_address, Infra::Types::PostalAddress
5 |
6 | alias aggregate_id order_id
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/ecommerce/shipping/lib/shipping/commands/authorize_shipment.rb:
--------------------------------------------------------------------------------
1 | module Shipping
2 | class AuthorizeShipment < Infra::Command
3 | attribute :order_id, Infra::Types::UUID
4 | end
5 | end
--------------------------------------------------------------------------------
/ecommerce/shipping/lib/shipping/commands/remove_item_from_shipment_picking_list.rb:
--------------------------------------------------------------------------------
1 | module Shipping
2 | class RemoveItemFromShipmentPickingList < Infra::Command
3 | attribute :order_id, Infra::Types::UUID
4 | attribute :product_id, Infra::Types::UUID
5 |
6 | alias aggregate_id order_id
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/ecommerce/shipping/lib/shipping/commands/submit_shipment.rb:
--------------------------------------------------------------------------------
1 | module Shipping
2 | class SubmitShipment < Infra::Command
3 | attribute :order_id, Infra::Types::UUID
4 | end
5 | end
--------------------------------------------------------------------------------
/ecommerce/shipping/lib/shipping/events/item_added_to_shipment_picking_list.rb:
--------------------------------------------------------------------------------
1 | module Shipping
2 | class ItemAddedToShipmentPickingList < Infra::Event
3 | attribute :order_id, Infra::Types::UUID
4 | attribute :product_id, Infra::Types::UUID
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/ecommerce/shipping/lib/shipping/events/item_removed_from_shipment_picking_list.rb:
--------------------------------------------------------------------------------
1 | module Shipping
2 | class ItemRemovedFromShipmentPickingList < Infra::Event
3 | attribute :order_id, Infra::Types::UUID
4 | attribute :product_id, Infra::Types::UUID
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/ecommerce/shipping/lib/shipping/events/shipment_authorized.rb:
--------------------------------------------------------------------------------
1 | module Shipping
2 | class ShipmentAuthorized < Infra::Event
3 | attribute :order_id, Infra::Types::UUID
4 | end
5 | end
--------------------------------------------------------------------------------
/ecommerce/shipping/lib/shipping/events/shipment_submitted.rb:
--------------------------------------------------------------------------------
1 | module Shipping
2 | class ShipmentSubmitted < Infra::Event
3 | attribute :order_id, Infra::Types::UUID
4 | end
5 | end
--------------------------------------------------------------------------------
/ecommerce/shipping/lib/shipping/events/shipping_address_added_to_shipment.rb:
--------------------------------------------------------------------------------
1 | module Shipping
2 | class ShippingAddressAddedToShipment < Infra::Event
3 | attribute :order_id, Infra::Types::UUID
4 | attribute :postal_address, Infra::Types::PostalAddress
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/ecommerce/shipping/lib/shipping/picking_list_item.rb:
--------------------------------------------------------------------------------
1 | module Shipping
2 | class PickingListItem
3 | attr_reader :product_id, :quantity
4 |
5 | def initialize(product_id)
6 | @product_id = product_id
7 | @quantity = 0
8 | end
9 |
10 | def increase
11 | @quantity += 1
12 | end
13 |
14 | def decrease
15 | @quantity -= 1
16 | end
17 | end
18 | end
--------------------------------------------------------------------------------
/ecommerce/shipping/lib/shipping/services/on_add_item_to_shipment_picking_list.rb:
--------------------------------------------------------------------------------
1 | module Shipping
2 | class OnAddItemToShipmentPickingList
3 | def initialize(event_store)
4 | @repository = AggregateRoot::Repository.new(event_store)
5 | end
6 |
7 | def call(command)
8 | @repository.with_aggregate(
9 | Shipment.new(command.order_id),
10 | "Shipping::Shipment$#{command.order_id}"
11 | ) do |shipment|
12 | shipment.add_item(command.product_id)
13 | end
14 | end
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/ecommerce/shipping/lib/shipping/services/on_add_shipping_address_to_shipment.rb:
--------------------------------------------------------------------------------
1 | module Shipping
2 | class OnAddShippingAddressToShipment
3 | def initialize(event_store)
4 | @repository = AggregateRoot::Repository.new(event_store)
5 | end
6 |
7 | def call(command)
8 | @repository.with_aggregate(
9 | Shipment.new(command.order_id),
10 | "Shipping::Shipment$#{command.order_id}"
11 | ) do |shipment|
12 | shipment.add_address(command.postal_address)
13 | end
14 | end
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/ecommerce/shipping/lib/shipping/services/on_authorize_shipment.rb:
--------------------------------------------------------------------------------
1 | module Shipping
2 | class OnAuthorizeShipment
3 | def initialize(event_store)
4 | @repository = AggregateRoot::Repository.new(event_store)
5 | end
6 |
7 | def call(command)
8 | @repository.with_aggregate(
9 | Shipment.new(command.order_id),
10 | "Shipping::Shipment$#{command.order_id}"
11 | ) do |shipment|
12 | shipment.authorize
13 | end
14 | end
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/ecommerce/shipping/lib/shipping/services/on_remove_item_from_shipment_picking_list.rb:
--------------------------------------------------------------------------------
1 | module Shipping
2 | class OnRemoveItemFromShipmentPickingList
3 | def initialize(event_store)
4 | @repository = AggregateRoot::Repository.new(event_store)
5 | end
6 |
7 | def call(command)
8 | @repository.with_aggregate(
9 | Shipment.new(command.order_id),
10 | "Shipping::Shipment$#{command.order_id}"
11 | ) do |shipment|
12 | shipment.remove_item(command.product_id)
13 | end
14 | end
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/ecommerce/shipping/lib/shipping/services/on_submit_shipment.rb:
--------------------------------------------------------------------------------
1 | module Shipping
2 | class OnSubmitShipment
3 | def initialize(event_store)
4 | @repository = AggregateRoot::Repository.new(event_store)
5 | end
6 |
7 | def call(command)
8 | @repository.with_aggregate(
9 | Shipment.new(command.order_id),
10 | "Shipping::Shipment$#{command.order_id}"
11 | ) do |shipment|
12 | shipment.submit
13 | end
14 | end
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/ecommerce/shipping/test/on_add_item_to_shipment_picking_list_test.rb:
--------------------------------------------------------------------------------
1 | require_relative "test_helper"
2 |
3 | module Shipping
4 | class OnAddItemToShipmentPickingListTest < Test
5 | cover "Shipping::OnAddItemToShipmentPickingList*"
6 |
7 | def test_add_item_to_shipment_picking_list
8 | order_id = SecureRandom.uuid
9 | product_id = SecureRandom.uuid
10 | stream = "Shipping::Shipment$#{order_id}"
11 |
12 | assert_events(
13 | stream,
14 | ItemAddedToShipmentPickingList.new(
15 | data: {
16 | order_id: order_id,
17 | product_id: product_id
18 | }
19 | )
20 | ) { act(AddItemToShipmentPickingList.new(order_id: order_id, product_id: product_id)) }
21 | end
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/ecommerce/shipping/test/on_add_shipping_address_to_shipment_test.rb:
--------------------------------------------------------------------------------
1 | require_relative "test_helper"
2 |
3 | module Shipping
4 | class OnAddShippingAddressToShipmentTest < Test
5 | cover "Shipping::OnAddShippingAddressToShipment*"
6 |
7 | def test_add_item_to_shipment_picking_list
8 | order_id = SecureRandom.uuid
9 | address = fake_address
10 | stream = "Shipping::Shipment$#{order_id}"
11 |
12 | assert_events(
13 | stream,
14 | ShippingAddressAddedToShipment.new(
15 | data: {
16 | order_id: order_id,
17 | postal_address: address
18 | }
19 | )
20 | ) do
21 | act(
22 | AddShippingAddressToShipment.new(
23 | order_id: order_id,
24 | postal_address: address
25 | )
26 | )
27 | end
28 | end
29 | end
30 | end
--------------------------------------------------------------------------------
/ecommerce/shipping/test/picking_list_item_test.rb:
--------------------------------------------------------------------------------
1 | require_relative "test_helper"
2 |
3 | module Shipping
4 | class PickingListItemTest < Test
5 |
6 | def test_initialize
7 | product_id = SecureRandom.uuid
8 | list_item = PickingListItem.new(product_id)
9 |
10 | assert_equal product_id, list_item.product_id
11 | assert_equal 0, list_item.quantity
12 | end
13 |
14 | def test_increase
15 | product_id = SecureRandom.uuid
16 | list_item = PickingListItem.new(product_id)
17 |
18 | list_item.increase
19 |
20 | assert_equal 1, list_item.quantity
21 | end
22 |
23 | def test_decrease
24 | product_id = SecureRandom.uuid
25 | list_item = PickingListItem.new(product_id)
26 |
27 | list_item.increase
28 | list_item.decrease
29 |
30 | assert_equal 0, list_item.quantity
31 | end
32 | end
33 | end
--------------------------------------------------------------------------------
/ecommerce/shipping/test/test_helper.rb:
--------------------------------------------------------------------------------
1 | require "minitest/autorun"
2 | require "mutant/minitest/coverage"
3 |
4 | require_relative "../lib/shipping"
5 |
6 | module Shipping
7 | class Test < Infra::InMemoryTest
8 |
9 | def before_setup
10 | super
11 | Shipping::Configuration.new.call(event_store, command_bus)
12 | end
13 |
14 | private
15 |
16 | def fake_address
17 | Infra::Types::PostalAddress.new(
18 | line_1: "Mme Anna Kowalska",
19 | line_2: "Ul. Bosmanska 1",
20 | line_3: "81-116 GDYNIA",
21 | line_4: "POLAND"
22 | )
23 | end
24 | end
25 | end
26 |
--------------------------------------------------------------------------------
/ecommerce/taxes/.mutant.yml:
--------------------------------------------------------------------------------
1 | requires:
2 | - ./test/test_helper
3 | integration: minitest
4 | usage: opensource
5 | coverage_criteria:
6 | process_abort: true
7 | matcher:
8 | subjects:
9 | - Taxes*
10 | ignore:
11 | - Taxes::Test*
12 | - Taxes::Configuration*
13 | - Taxes::VatRateCatalog#vat_rate_for
14 | - Taxes::AddAvailableVatRateHandler*
15 | - Taxes::RemoveAvailableVatRateHandler*
16 |
--------------------------------------------------------------------------------
/ecommerce/taxes/Gemfile:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 |
3 | eval_gemfile "../../infra/Gemfile.test"
4 | gem "infra", path: "../../infra"
--------------------------------------------------------------------------------
/ecommerce/taxes/Makefile:
--------------------------------------------------------------------------------
1 | install:
2 | @bundle install
3 |
4 | test:
5 | @bundle exec ruby -e "require \"rake/rake_test_loader\"" test/*_test.rb
6 |
7 | mutate:
8 | @RAILS_ENV=test bundle exec mutant run
9 |
10 | .PHONY: install test mutate
11 |
--------------------------------------------------------------------------------
/ecommerce/taxes/README.md:
--------------------------------------------------------------------------------
1 | # Taxes
2 |
3 | [](https://github.com/RailsEventStore/cqrs-es-sample-with-res/actions/workflows/taxes.yml)
4 |
5 |
6 | #### Up and running
7 |
8 | ```
9 | make install test mutate
10 | ```
11 |
--------------------------------------------------------------------------------
/ecommerce/taxes/lib/taxes.rb:
--------------------------------------------------------------------------------
1 | require 'infra'
2 | require_relative 'taxes/commands'
3 | require_relative 'taxes/events'
4 | require_relative 'taxes/services'
5 | require_relative 'taxes/product'
6 | require_relative 'taxes/vat_rate_catalog'
7 |
8 | module Taxes
9 | class Configuration
10 | def call(event_store, command_bus)
11 | command_bus.register(SetVatRate, SetVatRateHandler.new(event_store))
12 | command_bus.register(DetermineVatRate, DetermineVatRateHandler.new(event_store))
13 | command_bus.register(AddAvailableVatRate, AddAvailableVatRateHandler.new(event_store))
14 | command_bus.register(RemoveAvailableVatRate, RemoveAvailableVatRateHandler.new(event_store))
15 | end
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/ecommerce/taxes/lib/taxes/commands.rb:
--------------------------------------------------------------------------------
1 | module Taxes
2 | class SetVatRate < Infra::Command
3 | attribute :product_id, Infra::Types::UUID
4 | attribute :vat_rate_code, Infra::Types::String
5 | end
6 |
7 | class DetermineVatRate < Infra::Command
8 | attribute :product_id, Infra::Types::UUID
9 | attribute :order_id, Infra::Types::UUID
10 | end
11 |
12 | class AddAvailableVatRate < Infra::Command
13 | attribute :available_vat_rate_id, Infra::Types::UUID
14 | attribute :vat_rate, Infra::Types::VatRate
15 | end
16 |
17 | class RemoveAvailableVatRate < Infra::Command
18 | attribute :vat_rate_code, Infra::Types::String
19 | end
20 | end
21 |
--------------------------------------------------------------------------------
/ecommerce/taxes/lib/taxes/events.rb:
--------------------------------------------------------------------------------
1 | module Taxes
2 | class VatRateSet < Infra::Event
3 | attribute :product_id, Infra::Types::UUID
4 | attribute :vat_rate, Infra::Types::VatRate
5 | end
6 |
7 | class VatRateDetermined < Infra::Event
8 | attribute :order_id, Infra::Types::UUID
9 | attribute :product_id, Infra::Types::UUID
10 | attribute :vat_rate, Infra::Types::VatRate
11 | end
12 |
13 | class AvailableVatRateAdded < Infra::Event
14 | attribute :available_vat_rate_id, Infra::Types::UUID
15 | attribute :vat_rate, Infra::Types::VatRate
16 | end
17 |
18 | class AvailableVatRateRemoved < Infra::Event
19 | attribute :vat_rate_code, Infra::Types::String
20 | end
21 | end
22 |
--------------------------------------------------------------------------------
/ecommerce/taxes/lib/taxes/product.rb:
--------------------------------------------------------------------------------
1 | module Taxes
2 | class Product
3 | include AggregateRoot
4 |
5 | def initialize(id)
6 | @id = id
7 | end
8 |
9 | def set_vat_rate(vat_rate)
10 | apply(VatRateSet.new(data: { product_id: @id, vat_rate: vat_rate }))
11 | end
12 |
13 | private
14 |
15 | on(VatRateSet) { |_| }
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/ecommerce/taxes/test/test_helper.rb:
--------------------------------------------------------------------------------
1 | require "minitest/autorun"
2 | require "mutant/minitest/coverage"
3 |
4 | require_relative "../lib/taxes"
5 |
6 | module Taxes
7 | class Test < Infra::InMemoryTest
8 | cover "Taxes*"
9 |
10 | def before_setup
11 | super
12 | Configuration.new.call(event_store, command_bus)
13 | end
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/hanami_application/README.md:
--------------------------------------------------------------------------------
1 | Coming soon, there's no obstacle to use it with ~~Rails~~ **RubyEventStore**.
2 |
3 | It will reimplement most, if not all, of counterpart Rails application. It will also reuse contexts from [ecommerce/](../ecommerce/)
4 |
5 |
--------------------------------------------------------------------------------
/infra/.mutant.yml:
--------------------------------------------------------------------------------
1 | ---
2 | mutation:
3 | timeout: 10.0
4 | includes:
5 | - test
6 | integration:
7 | name: minitest
8 | usage: opensource
9 | coverage_criteria:
10 | timeout: true
11 | process_abort: true
12 | requires:
13 | - ./lib/infra.rb
14 | matcher:
15 | subjects:
16 | - Infra*
17 | ignore:
18 | - Infra::Event*
19 | - Infra::Command*
20 | - Infra::AggregateRootRepository*
21 | - Infra::TestPlumbing*
22 | - Infra::NoEvent*
23 | - Infra::EventStore*
24 | - Infra::Mapper*
25 | - Infra::Process*
26 | - Infra::EventHandler*
27 | - Infra::ProcessManager*
28 |
--------------------------------------------------------------------------------
/infra/Gemfile:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 |
3 | eval_gemfile "Gemfile.test"
4 |
5 | gemspec
6 |
--------------------------------------------------------------------------------
/infra/Gemfile.test:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org" do
2 | gem "minitest", "5.25.0"
3 | gem "mutant-minitest", "0.13.1"
4 | end
5 |
--------------------------------------------------------------------------------
/infra/Makefile:
--------------------------------------------------------------------------------
1 | install:
2 | @bundle install
3 |
4 | test:
5 | @bundle exec ruby -e "require \"rake/rake_test_loader\"" test/*_test.rb
6 |
7 | mutate:
8 | @RAILS_ENV=test bundle exec mutant run
9 |
10 | .PHONY: install test mutate
11 |
--------------------------------------------------------------------------------
/infra/infra.gemspec:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | Gem::Specification.new do |spec|
4 | spec.name = "infra"
5 | spec.version = "1.0.0"
6 | spec.authors = ["arkency"]
7 | spec.email = ["dev@arkency.com"]
8 | spec.require_paths = ["lib"]
9 | spec.files = Dir["lib/**/*"]
10 | spec.summary = "infrastructure for the application"
11 |
12 | spec.add_dependency "rake"
13 | spec.add_dependency "dry-struct"
14 | spec.add_dependency "dry-types"
15 | spec.add_dependency "aggregate_root", "~> 2.15"
16 | spec.add_dependency "arkency-command_bus"
17 | spec.add_dependency "ruby_event_store", "~> 2.15"
18 | spec.add_dependency "ruby_event_store-transformations"
19 | end
20 |
--------------------------------------------------------------------------------
/infra/lib/infra.rb:
--------------------------------------------------------------------------------
1 | require "ruby_event_store"
2 | require "aggregate_root"
3 | require "arkency/command_bus"
4 | require "dry-struct"
5 | require "dry-types"
6 | require "aggregate_root"
7 | require "active_support/notifications"
8 | require "minitest"
9 | require "ruby_event_store/transformations"
10 |
11 | require_relative "infra/command"
12 | require_relative "infra/command_bus"
13 | require_relative "infra/aggregate_root_repository"
14 | require_relative "infra/event"
15 | require_relative "infra/event_store"
16 | require_relative "infra/process"
17 | require_relative "infra/process_manager"
18 | require_relative "infra/retry"
19 | require_relative "infra/types"
20 | require_relative "infra/testing"
21 |
--------------------------------------------------------------------------------
/infra/lib/infra/aggregate_root_repository.rb:
--------------------------------------------------------------------------------
1 | require "aggregate_root"
2 | require "active_support/notifications"
3 |
4 | module Infra
5 | class AggregateRootRepository
6 | def initialize(event_store, notifications = ActiveSupport::Notifications)
7 | @repository =
8 | AggregateRoot::InstrumentedRepository.new(
9 | AggregateRoot::Repository.new(event_store),
10 | notifications
11 | )
12 | end
13 |
14 | def with_aggregate(aggregate_class, aggregate_id, &block)
15 | @repository.with_aggregate(
16 | aggregate_class.new(aggregate_id),
17 | stream_name(aggregate_class, aggregate_id),
18 | &block
19 | )
20 | end
21 |
22 | def stream_name(aggregate_class, aggregate_id)
23 | "#{aggregate_class.name}$#{aggregate_id}"
24 | end
25 | end
26 | end
27 |
--------------------------------------------------------------------------------
/infra/lib/infra/command.rb:
--------------------------------------------------------------------------------
1 | module Infra
2 | class Command < Dry::Struct
3 | Invalid = Class.new(StandardError)
4 |
5 | def self.new(*)
6 | super
7 | rescue Dry::Struct::Error => doh
8 | raise Invalid, doh
9 | end
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/infra/lib/infra/command_bus.rb:
--------------------------------------------------------------------------------
1 | module Infra
2 | CommandBus = Arkency::CommandBus
3 | end
4 |
--------------------------------------------------------------------------------
/infra/lib/infra/process.rb:
--------------------------------------------------------------------------------
1 | module Infra
2 | class Process
3 | def initialize(event_store, command_bus)
4 | @event_store = event_store
5 | @command_bus = command_bus
6 | end
7 |
8 | def call(event_name, event_data_keys, command, command_data_keys)
9 | @event_store.subscribe(
10 | ->(event) do
11 | @command_bus.call(
12 | command.new(
13 | Hash[
14 | command_data_keys.zip(
15 | event_data_keys.map { |key| event.data.fetch(key) }
16 | )
17 | ]
18 | )
19 | )
20 | end,
21 | to: [event_name]
22 | )
23 | end
24 | end
25 | end
--------------------------------------------------------------------------------
/infra/lib/infra/retry.rb:
--------------------------------------------------------------------------------
1 | module Infra
2 | module Retry
3 | def with_retry
4 | yield
5 | rescue RubyEventStore::WrongExpectedEventVersion
6 | yield
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/infra/test/test_helper.rb:
--------------------------------------------------------------------------------
1 | require "minitest/autorun"
2 | require "mutant/minitest/coverage"
3 |
4 | require_relative "../lib/infra"
5 |
6 |
--------------------------------------------------------------------------------
/infra/test/vat_rate_test.rb:
--------------------------------------------------------------------------------
1 | require_relative "test_helper"
2 |
3 | module Infra
4 | class VatRateTest < Minitest::Test
5 | cover "Infra::Types::VatRate"
6 |
7 | def test_comparable
8 | assert_equal(0, vat.new(code: '10', rate: 10.to_d) <=> vat.new(code: 'ten', rate: 10.to_d))
9 | assert_equal(1, vat.new(code: '11', rate: 11.to_d) <=> vat.new(code: 'ten', rate: 10.to_d))
10 | end
11 |
12 | private
13 |
14 | def vat
15 | Infra::Types::VatRate
16 | end
17 | end
18 | end
--------------------------------------------------------------------------------
/pricing_catalog_rails_app/.gitignore:
--------------------------------------------------------------------------------
1 | !/log/.keep
2 | !/storage/.keep
3 | !/tmp/.keep
4 | .byebug_history
5 | .yarn-integrity
6 | /.bundle
7 | /config/master.key
8 | /db/*.sqlite3*
9 | /db/*.sqlite3-journal
10 | /elm-stuff
11 | /log/*
12 | /node_modules
13 | /public/assets
14 | /public/packs
15 | /public/packs-test
16 | /storage/*
17 | /tmp/*
18 | /yarn-error.log
19 | coverage
20 | yarn-debug.log*
21 |
22 | /public/packs
23 | /public/packs-test
24 | /node_modules
25 | /yarn-error.log
26 | yarn-debug.log*
27 | .yarn-integrity
28 | .env*.local
29 |
30 | .idea
31 | .ruby-version
32 | /app/assets/builds/*
33 | !/app/assets/builds/.keep
34 |
35 | # Event to handlers and handler to events mappings generated by big_picture.rb script
36 | /lib/event_to_handlers.rb
37 | /lib/handler_to_events.rb
--------------------------------------------------------------------------------
/pricing_catalog_rails_app/README.md:
--------------------------------------------------------------------------------
1 | # README
2 |
3 | This README would normally document whatever steps are necessary to get the
4 | application up and running.
5 |
6 | Things you may want to cover:
7 |
8 | * Ruby version
9 |
10 | * System dependencies
11 |
12 | * Configuration
13 |
14 | * Database creation
15 |
16 | * Database initialization
17 |
18 | * How to run the test suite
19 |
20 | * Services (job queues, cache servers, search engines, etc.)
21 |
22 | * Deployment instructions
23 |
24 | * ...
25 |
--------------------------------------------------------------------------------
/pricing_catalog_rails_app/Rakefile:
--------------------------------------------------------------------------------
1 | # Add your own tasks in files placed in lib/tasks ending in .rake,
2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
3 |
4 | require_relative "config/application"
5 |
6 | Rails.application.load_tasks
7 |
--------------------------------------------------------------------------------
/pricing_catalog_rails_app/app/admin/read_models/admin_catalog/index.html.erb:
--------------------------------------------------------------------------------
1 | Admin Catalog
2 |
3 | <%= form_for new_product, url: {controller: "admin/catalog", action: "create"} do |f| %>
4 |
5 | <%= f.label :name %>
6 | <%= f.text_field :name %>
7 |
8 |
9 | <%= f.label :price %>
10 | <%= f.text_field :price %>
11 |
12 | <%= f.submit %>
13 | <% end %>
14 |
15 |
16 | <% products.each do |product| %>
17 | - <%= product.name %>, $<%= product.price %>
18 | <% end %>
19 |
--------------------------------------------------------------------------------
/pricing_catalog_rails_app/app/admin/register_product.rb:
--------------------------------------------------------------------------------
1 | class RegisterProduct
2 | def call(name, price)
3 | product_id = SecureRandom.uuid
4 | command_bus.(ProductCatalog::RegisterProduct.new(product_id: product_id))
5 | command_bus.(ProductCatalog::NameProduct.new(product_id: product_id, name: name))
6 | command_bus.(Pricing::SetPrice.new(product_id: product_id, price: price))
7 | end
8 |
9 | private
10 |
11 | def command_bus
12 | Rails.configuration.command_bus
13 | end
14 | end
--------------------------------------------------------------------------------
/pricing_catalog_rails_app/app/assets/config/manifest.js:
--------------------------------------------------------------------------------
1 | //= link_tree ../images
2 | //= link_directory ../stylesheets .css
3 | //= link_tree ../../javascript .js
4 | //= link_tree ../../../vendor/javascript .js
5 |
--------------------------------------------------------------------------------
/pricing_catalog_rails_app/app/assets/images/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RailsEventStore/ecommerce/54996c6159999e29a776b766afad20400ea22dcc/pricing_catalog_rails_app/app/assets/images/.keep
--------------------------------------------------------------------------------
/pricing_catalog_rails_app/app/assets/stylesheets/application.css:
--------------------------------------------------------------------------------
1 | /*
2 | * This is a manifest file that'll be compiled into application.css, which will include all the files
3 | * listed below.
4 | *
5 | * Any CSS (and SCSS, if configured) file within this directory, lib/assets/stylesheets, or any plugin's
6 | * vendor/assets/stylesheets directory can be referenced here using a relative path.
7 | *
8 | * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9 | * compiled file so the styles you add here take precedence over styles defined in any other CSS
10 | * files in this directory. Styles in this file should be added after the last require_* statement.
11 | * It is generally better to create a new file per style scope.
12 | *
13 | *= require_tree .
14 | *= require_self
15 | */
16 |
--------------------------------------------------------------------------------
/pricing_catalog_rails_app/app/channels/application_cable/channel.rb:
--------------------------------------------------------------------------------
1 | module ApplicationCable
2 | class Channel < ActionCable::Channel::Base
3 | end
4 | end
5 |
--------------------------------------------------------------------------------
/pricing_catalog_rails_app/app/channels/application_cable/connection.rb:
--------------------------------------------------------------------------------
1 | module ApplicationCable
2 | class Connection < ActionCable::Connection::Base
3 | end
4 | end
5 |
--------------------------------------------------------------------------------
/pricing_catalog_rails_app/app/controllers/admin/catalog_controller.rb:
--------------------------------------------------------------------------------
1 | class Admin::CatalogController < ApplicationController
2 | def index
3 | products = AdminCatalog::Product.all
4 | prepend_view_path Rails.root.join("app", "admin", "read_models")
5 | render template: "admin_catalog/index",
6 | locals: { products: products, new_product: AdminCatalog::Product.new }
7 | end
8 |
9 | def create
10 | RegisterProduct.new.call(
11 | params[:admin_catalog_product][:name],
12 | params[:admin_catalog_product][:price]
13 | )
14 | redirect_to admin_root_path
15 | end
16 | end
--------------------------------------------------------------------------------
/pricing_catalog_rails_app/app/controllers/application_controller.rb:
--------------------------------------------------------------------------------
1 | class ApplicationController < ActionController::Base
2 | end
3 |
--------------------------------------------------------------------------------
/pricing_catalog_rails_app/app/controllers/catalog_controller.rb:
--------------------------------------------------------------------------------
1 | class CatalogController < ApplicationController
2 | def index
3 | products = PublicCatalog::Product.all
4 | prepend_view_path Rails.root.join("app", "public", "read_models")
5 | render template: "public_catalog/index", locals: { products: products }
6 | end
7 | end
--------------------------------------------------------------------------------
/pricing_catalog_rails_app/app/controllers/concerns/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RailsEventStore/ecommerce/54996c6159999e29a776b766afad20400ea22dcc/pricing_catalog_rails_app/app/controllers/concerns/.keep
--------------------------------------------------------------------------------
/pricing_catalog_rails_app/app/helpers/application_helper.rb:
--------------------------------------------------------------------------------
1 | module ApplicationHelper
2 | end
3 |
--------------------------------------------------------------------------------
/pricing_catalog_rails_app/app/javascript/application.js:
--------------------------------------------------------------------------------
1 | // Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails
2 | import "@hotwired/turbo-rails"
3 | import "controllers"
4 |
--------------------------------------------------------------------------------
/pricing_catalog_rails_app/app/javascript/controllers/application.js:
--------------------------------------------------------------------------------
1 | import { Application } from "@hotwired/stimulus"
2 |
3 | const application = Application.start()
4 |
5 | // Configure Stimulus development experience
6 | application.debug = false
7 | window.Stimulus = application
8 |
9 | export { application }
10 |
--------------------------------------------------------------------------------
/pricing_catalog_rails_app/app/javascript/controllers/hello_controller.js:
--------------------------------------------------------------------------------
1 | import { Controller } from "@hotwired/stimulus"
2 |
3 | export default class extends Controller {
4 | connect() {
5 | this.element.textContent = "Hello World!"
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/pricing_catalog_rails_app/app/javascript/controllers/index.js:
--------------------------------------------------------------------------------
1 | // Import and register all your controllers from the importmap under controllers/*
2 |
3 | import { application } from "controllers/application"
4 |
5 | // Eager load all controllers defined in the import map under controllers/**/*_controller
6 | import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading"
7 | eagerLoadControllersFrom("controllers", application)
8 |
9 | // Lazy load controllers as they appear in the DOM (remember not to preload controllers in import map!)
10 | // import { lazyLoadControllersFrom } from "@hotwired/stimulus-loading"
11 | // lazyLoadControllersFrom("controllers", application)
12 |
--------------------------------------------------------------------------------
/pricing_catalog_rails_app/app/jobs/application_job.rb:
--------------------------------------------------------------------------------
1 | class ApplicationJob < ActiveJob::Base
2 | # Automatically retry jobs that encountered a deadlock
3 | # retry_on ActiveRecord::Deadlocked
4 |
5 | # Most jobs are safe to ignore if the underlying records are no longer available
6 | # discard_on ActiveJob::DeserializationError
7 | end
8 |
--------------------------------------------------------------------------------
/pricing_catalog_rails_app/app/models/application_record.rb:
--------------------------------------------------------------------------------
1 | class ApplicationRecord < ActiveRecord::Base
2 | primary_abstract_class
3 | end
4 |
--------------------------------------------------------------------------------
/pricing_catalog_rails_app/app/models/concerns/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RailsEventStore/ecommerce/54996c6159999e29a776b766afad20400ea22dcc/pricing_catalog_rails_app/app/models/concerns/.keep
--------------------------------------------------------------------------------
/pricing_catalog_rails_app/app/public/read_models/public_catalog/index.html.erb:
--------------------------------------------------------------------------------
1 | Catalog
2 |
3 |
4 | <% products.each do |product| %>
5 | - <%= product.name %>, $<%= product.price %>
6 | <% end %>
7 |
--------------------------------------------------------------------------------
/pricing_catalog_rails_app/app/views/layouts/application.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | PricingCatalogRailsApp
5 |
6 | <%= csrf_meta_tags %>
7 | <%= csp_meta_tag %>
8 |
9 | <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
10 | <%= javascript_importmap_tags %>
11 |
12 |
13 |
14 | <%= yield %>
15 |
16 |
17 |
--------------------------------------------------------------------------------
/pricing_catalog_rails_app/bin/docker-entrypoint:
--------------------------------------------------------------------------------
1 | #!/bin/bash -e
2 |
3 | # If running the rails server then create or migrate existing database
4 | if [ "${1}" == "./bin/rails" ] && [ "${2}" == "server" ]; then
5 | ./bin/rails db:prepare
6 | fi
7 |
8 | exec "${@}"
9 |
--------------------------------------------------------------------------------
/pricing_catalog_rails_app/bin/importmap:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | require_relative "../config/application"
4 | require "importmap/commands"
5 |
--------------------------------------------------------------------------------
/pricing_catalog_rails_app/bin/rails:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | APP_PATH = File.expand_path("../config/application", __dir__)
3 | require_relative "../config/boot"
4 | require "rails/commands"
5 |
--------------------------------------------------------------------------------
/pricing_catalog_rails_app/bin/rake:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require_relative "../config/boot"
3 | require "rake"
4 | Rake.application.run
5 |
--------------------------------------------------------------------------------
/pricing_catalog_rails_app/config.ru:
--------------------------------------------------------------------------------
1 | # This file is used by Rack-based servers to start the application.
2 |
3 | require_relative "config/environment"
4 |
5 | run Rails.application
6 | Rails.application.load_server
7 |
--------------------------------------------------------------------------------
/pricing_catalog_rails_app/config/boot.rb:
--------------------------------------------------------------------------------
1 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
2 |
3 | require "bundler/setup" # Set up gems listed in the Gemfile.
4 | require "bootsnap/setup" # Speed up boot time by caching expensive operations.
5 |
--------------------------------------------------------------------------------
/pricing_catalog_rails_app/config/cable.yml:
--------------------------------------------------------------------------------
1 | development:
2 | adapter: postgresql
3 |
4 | test:
5 | adapter: test
6 |
7 | production:
8 | adapter: postgresql
9 |
--------------------------------------------------------------------------------
/pricing_catalog_rails_app/config/credentials.yml.enc:
--------------------------------------------------------------------------------
1 | 1bVsLr2aTAhLscEGzm9z9M6tAEvNNqGbM+wJgoEaQFkaXELdbldo0WLR2WfGRmKICorkE4reE6N47g30nr0XIHRly9lfRb0/pSXxZY06X37RF+C/amRJJ4G0bqxJTwjcE1GBQ0x3Xv7WZnj0YvIVAfZX1pa3OS4piVDHCgvf/03a5PGcSJxcfF8xbYbHb5NLWIJtJ//mjPpOZiFnJh0W7PKsP0N2KeqjPIvyb97EcOw2l6UKf9pV1ZhkOWUf6RWc3MzfvPVQkvW7qPe6CUVNWp34w5/8eZCFAtNHw9v57A/GF148I3C8p6xuF3/B74xL3yuw/xE//RdwT2Tw0BwtOOFePK+iuxEBZDhZxwDCmB8VuWr0Fai0tI4jx7DUIPC1f2inoDLb+3zrd8Yjc2I8xC0UHDDB--mwsUJW1WffHciEwk--Ih++i+PuMS7pOk1yjdDf9Q==
--------------------------------------------------------------------------------
/pricing_catalog_rails_app/config/database.yml:
--------------------------------------------------------------------------------
1 | # SQLite. Versions 3.8.0 and up are supported.
2 | # gem install sqlite3
3 | #
4 | # Ensure the SQLite 3 gem is defined in your Gemfile
5 | # gem "sqlite3"
6 | #
7 | default: &default
8 | adapter: sqlite3
9 | pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
10 | timeout: 5000
11 |
12 | development:
13 | <<: *default
14 | database: storage/development.sqlite3
15 |
16 | # Warning: The database defined as "test" will be erased and
17 | # re-generated from your development database when you run "rake".
18 | # Do not set this db to the same as development or production.
19 | test:
20 | <<: *default
21 | database: storage/test.sqlite3
22 |
23 | production:
24 | <<: *default
25 | database: storage/production.sqlite3
26 |
--------------------------------------------------------------------------------
/pricing_catalog_rails_app/config/environment.rb:
--------------------------------------------------------------------------------
1 | # Load the Rails application.
2 | require_relative "application"
3 |
4 | # Initialize the Rails application.
5 | Rails.application.initialize!
6 |
--------------------------------------------------------------------------------
/pricing_catalog_rails_app/config/importmap.rb:
--------------------------------------------------------------------------------
1 | # Pin npm packages by running ./bin/importmap
2 |
3 | pin "application"
4 | pin "@hotwired/turbo-rails", to: "turbo.min.js", preload: true
5 | pin "@hotwired/stimulus", to: "stimulus.min.js"
6 | pin "@hotwired/stimulus-loading", to: "stimulus-loading.js"
7 | pin_all_from "app/javascript/controllers", under: "controllers"
8 |
--------------------------------------------------------------------------------
/pricing_catalog_rails_app/config/initializers/filter_parameter_logging.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Configure parameters to be partially matched (e.g. passw matches password) and filtered from the log file.
4 | # Use this to limit dissemination of sensitive information.
5 | # See the ActiveSupport::ParameterFilter documentation for supported notations and behaviors.
6 | Rails.application.config.filter_parameters += [
7 | :passw, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn
8 | ]
9 |
--------------------------------------------------------------------------------
/pricing_catalog_rails_app/config/initializers/inflections.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new inflection rules using the following format. Inflections
4 | # are locale specific, and you may define rules for as many different
5 | # locales as you wish. All of these examples are active by default:
6 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
7 | # inflect.plural /^(ox)$/i, "\\1en"
8 | # inflect.singular /^(ox)en/i, "\\1"
9 | # inflect.irregular "person", "people"
10 | # inflect.uncountable %w( fish sheep )
11 | # end
12 |
13 | # These inflection rules are supported but not enabled by default:
14 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
15 | # inflect.acronym "RESTful"
16 | # end
17 |
--------------------------------------------------------------------------------
/pricing_catalog_rails_app/config/initializers/permissions_policy.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Define an application-wide HTTP permissions policy. For further
4 | # information see: https://developers.google.com/web/updates/2018/06/feature-policy
5 |
6 | # Rails.application.config.permissions_policy do |policy|
7 | # policy.camera :none
8 | # policy.gyroscope :none
9 | # policy.microphone :none
10 | # policy.usb :none
11 | # policy.fullscreen :self
12 | # policy.payment :self, "https://secure.example.com"
13 | # end
14 |
--------------------------------------------------------------------------------
/pricing_catalog_rails_app/config/master.key:
--------------------------------------------------------------------------------
1 | c2400f761b119d3f092a64d00bad9fdc
--------------------------------------------------------------------------------
/pricing_catalog_rails_app/config/routes.rb:
--------------------------------------------------------------------------------
1 | Rails.application.routes.draw do
2 | mount RailsEventStore::Browser => '/res' if Rails.env.development?
3 | # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html
4 |
5 | # Reveal health status on /up that returns 200 if the app boots with no exceptions, otherwise 500.
6 | # Can be used by load balancers and uptime monitors to verify that the app is live.
7 | get "up" => "rails/health#show", as: :rails_health_check
8 |
9 | # Defines the root path route ("/")
10 | root "catalog#index"
11 |
12 | namespace :admin do
13 | root "catalog#index"
14 | post "catalog", to: "catalog#create"
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/pricing_catalog_rails_app/db/migrate/20240201132941_enable_pg_crypto.rb:
--------------------------------------------------------------------------------
1 | class EnablePgCrypto < ActiveRecord::Migration[7.1]
2 | def change
3 | enable_extension "pgcrypto"
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/pricing_catalog_rails_app/db/migrate/20240201133038_create_products_table.rb:
--------------------------------------------------------------------------------
1 | class CreateProductsTable < ActiveRecord::Migration[7.1]
2 | def change
3 | PublicCatalog::Migration.new.change
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/pricing_catalog_rails_app/db/migrate/20240201154705_create_admin_products.rb:
--------------------------------------------------------------------------------
1 | class CreateAdminProducts < ActiveRecord::Migration[7.1]
2 | def change
3 | AdminCatalog::Migration.new.change
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/pricing_catalog_rails_app/db/seeds.rb:
--------------------------------------------------------------------------------
1 | # This file should ensure the existence of records required to run the application in every environment (production,
2 | # development, test). The code here should be idempotent so that it can be executed at any point in every environment.
3 | # The data can then be loaded with the bin/rails db:seed command (or created alongside the database with db:setup).
4 | #
5 | # Example:
6 | #
7 | # ["Action", "Comedy", "Drama", "Horror"].each do |genre_name|
8 | # MovieGenre.find_or_create_by!(name: genre_name)
9 | # end
10 |
--------------------------------------------------------------------------------
/pricing_catalog_rails_app/lib/assets/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RailsEventStore/ecommerce/54996c6159999e29a776b766afad20400ea22dcc/pricing_catalog_rails_app/lib/assets/.keep
--------------------------------------------------------------------------------
/pricing_catalog_rails_app/lib/tasks/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RailsEventStore/ecommerce/54996c6159999e29a776b766afad20400ea22dcc/pricing_catalog_rails_app/lib/tasks/.keep
--------------------------------------------------------------------------------
/pricing_catalog_rails_app/log/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RailsEventStore/ecommerce/54996c6159999e29a776b766afad20400ea22dcc/pricing_catalog_rails_app/log/.keep
--------------------------------------------------------------------------------
/pricing_catalog_rails_app/public/apple-touch-icon-precomposed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RailsEventStore/ecommerce/54996c6159999e29a776b766afad20400ea22dcc/pricing_catalog_rails_app/public/apple-touch-icon-precomposed.png
--------------------------------------------------------------------------------
/pricing_catalog_rails_app/public/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RailsEventStore/ecommerce/54996c6159999e29a776b766afad20400ea22dcc/pricing_catalog_rails_app/public/apple-touch-icon.png
--------------------------------------------------------------------------------
/pricing_catalog_rails_app/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RailsEventStore/ecommerce/54996c6159999e29a776b766afad20400ea22dcc/pricing_catalog_rails_app/public/favicon.ico
--------------------------------------------------------------------------------
/pricing_catalog_rails_app/public/robots.txt:
--------------------------------------------------------------------------------
1 | # See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file
2 |
--------------------------------------------------------------------------------
/pricing_catalog_rails_app/storage/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RailsEventStore/ecommerce/54996c6159999e29a776b766afad20400ea22dcc/pricing_catalog_rails_app/storage/.keep
--------------------------------------------------------------------------------
/pricing_catalog_rails_app/test/application_system_test_case.rb:
--------------------------------------------------------------------------------
1 | require "test_helper"
2 |
3 | class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
4 | driven_by :selenium, using: :chrome, screen_size: [1400, 1400]
5 | end
6 |
--------------------------------------------------------------------------------
/pricing_catalog_rails_app/test/channels/application_cable/connection_test.rb:
--------------------------------------------------------------------------------
1 | require "test_helper"
2 |
3 | module ApplicationCable
4 | class ConnectionTest < ActionCable::Connection::TestCase
5 | # test "connects with cookies" do
6 | # cookies.signed[:user_id] = 42
7 | #
8 | # connect
9 | #
10 | # assert_equal connection.user_id, "42"
11 | # end
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/pricing_catalog_rails_app/test/controllers/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RailsEventStore/ecommerce/54996c6159999e29a776b766afad20400ea22dcc/pricing_catalog_rails_app/test/controllers/.keep
--------------------------------------------------------------------------------
/pricing_catalog_rails_app/test/fixtures/files/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RailsEventStore/ecommerce/54996c6159999e29a776b766afad20400ea22dcc/pricing_catalog_rails_app/test/fixtures/files/.keep
--------------------------------------------------------------------------------
/pricing_catalog_rails_app/test/helpers/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RailsEventStore/ecommerce/54996c6159999e29a776b766afad20400ea22dcc/pricing_catalog_rails_app/test/helpers/.keep
--------------------------------------------------------------------------------
/pricing_catalog_rails_app/test/integration/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RailsEventStore/ecommerce/54996c6159999e29a776b766afad20400ea22dcc/pricing_catalog_rails_app/test/integration/.keep
--------------------------------------------------------------------------------
/pricing_catalog_rails_app/test/models/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RailsEventStore/ecommerce/54996c6159999e29a776b766afad20400ea22dcc/pricing_catalog_rails_app/test/models/.keep
--------------------------------------------------------------------------------
/pricing_catalog_rails_app/test/system/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RailsEventStore/ecommerce/54996c6159999e29a776b766afad20400ea22dcc/pricing_catalog_rails_app/test/system/.keep
--------------------------------------------------------------------------------
/pricing_catalog_rails_app/test/test_helper.rb:
--------------------------------------------------------------------------------
1 | ENV["RAILS_ENV"] ||= "test"
2 | require_relative "../config/environment"
3 | require "rails/test_help"
4 |
5 | module ActiveSupport
6 | class TestCase
7 | # Run tests in parallel with specified workers
8 | parallelize(workers: :number_of_processors)
9 |
10 | # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.
11 | fixtures :all
12 |
13 | # Add more helper methods to be used by all tests here...
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/pricing_catalog_rails_app/tmp/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RailsEventStore/ecommerce/54996c6159999e29a776b766afad20400ea22dcc/pricing_catalog_rails_app/tmp/.keep
--------------------------------------------------------------------------------
/pricing_catalog_rails_app/tmp/local_secret.txt:
--------------------------------------------------------------------------------
1 | 4b650a883751aa8985f1c1e9bc291ec5dd22575339d1415cb6ca116e77fc00e6f30ae882aae0a55cc22a7611693563e3e6e3eb07b07726a4628a5665e47b54a3
--------------------------------------------------------------------------------
/pricing_catalog_rails_app/tmp/pids/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RailsEventStore/ecommerce/54996c6159999e29a776b766afad20400ea22dcc/pricing_catalog_rails_app/tmp/pids/.keep
--------------------------------------------------------------------------------
/pricing_catalog_rails_app/tmp/storage/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RailsEventStore/ecommerce/54996c6159999e29a776b766afad20400ea22dcc/pricing_catalog_rails_app/tmp/storage/.keep
--------------------------------------------------------------------------------
/pricing_catalog_rails_app/vendor/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RailsEventStore/ecommerce/54996c6159999e29a776b766afad20400ea22dcc/pricing_catalog_rails_app/vendor/.keep
--------------------------------------------------------------------------------
/pricing_catalog_rails_app/vendor/javascript/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RailsEventStore/ecommerce/54996c6159999e29a776b766afad20400ea22dcc/pricing_catalog_rails_app/vendor/javascript/.keep
--------------------------------------------------------------------------------
/productivity/todo/.mutant.yml:
--------------------------------------------------------------------------------
1 | requires:
2 | - ./test/test_helper
3 | integration: minitest
4 | usage: opensource
5 | coverage_criteria:
6 | process_abort: true
7 | matcher:
8 | subjects:
9 | - Todo*
10 | ignore:
11 | - Todo::Test*
12 | - Todo::AddTaskHandler#task_stream
--------------------------------------------------------------------------------
/productivity/todo/Gemfile:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 |
3 | eval_gemfile "../../infra/Gemfile.test"
4 | gem "infra", path: "../../infra"
--------------------------------------------------------------------------------
/productivity/todo/Makefile:
--------------------------------------------------------------------------------
1 | install:
2 | @bundle install
3 |
4 | test:
5 | @bundle exec ruby -e "require \"rake/rake_test_loader\"" test/*_test.rb
6 |
7 | mutate:
8 | @RAILS_ENV=test bundle exec mutant run
9 |
10 | .PHONY: install test mutate
11 |
--------------------------------------------------------------------------------
/productivity/todo/README.md:
--------------------------------------------------------------------------------
1 | # Todo
2 |
3 | A domain responsible for tasks, completion of them etc.
4 |
5 | #### Up and running
6 |
7 | ```
8 | make install test mutate
9 | ```
10 |
11 |
12 | ### Domain knowledge
13 |
14 | Tasks are meant here like in any todo app - nozbe, todoist etc
15 | However, the boundary here is clear - all time related things don't belong here.
16 | Similarly, it's not about assigning tasks to people nor to projects.
17 |
18 | It's mostly about a lifecycle of task.
19 |
--------------------------------------------------------------------------------
/productivity/todo/lib/todo.rb:
--------------------------------------------------------------------------------
1 | require 'infra'
2 | require_relative 'todo/todo'
3 |
4 | module Todo
5 | class Configuration
6 | def call(event_store, command_bus)
7 | command_bus.register(AddTask, AddTaskHandler.new(event_store))
8 | end
9 | end
10 | end
--------------------------------------------------------------------------------
/productivity/todo/test/test_helper.rb:
--------------------------------------------------------------------------------
1 | require "minitest/autorun"
2 | require "mutant/minitest/coverage"
3 |
4 | require_relative "../lib/todo"
5 |
6 | module Todo
7 | class Test < Infra::InMemoryTest
8 | def before_setup
9 | super
10 | Configuration.new.call(event_store, command_bus)
11 | end
12 |
13 | private
14 |
15 | end
16 | end
--------------------------------------------------------------------------------
/productivity/todo/test/todo_test.rb:
--------------------------------------------------------------------------------
1 | require_relative "test_helper"
2 |
3 | module Todo
4 | class TaskCreationTest < Test
5 | cover "Todo::Task"
6 |
7 | def test_add_task_twice_fails
8 | task_id = SecureRandom.uuid
9 | command_bus.call(AddTask.new(task_id: task_id))
10 | assert_raises TaskAlreadyExists do
11 | command_bus.call(AddTask.new(task_id: task_id))
12 | end
13 | end
14 | end
15 | end
--------------------------------------------------------------------------------
/rails_application/.env.development:
--------------------------------------------------------------------------------
1 | DATABASE_URL=postgres://postgres:secret@localhost/cqrs-es-sample-with-res_development
--------------------------------------------------------------------------------
/rails_application/.env.test:
--------------------------------------------------------------------------------
1 | DATABASE_URL=postgres://postgres:secret@localhost/cqrs-es-sample-with-res_test
2 |
--------------------------------------------------------------------------------
/rails_application/.gitignore:
--------------------------------------------------------------------------------
1 | !/log/.keep
2 | !/storage/.keep
3 | !/tmp/.keep
4 | .byebug_history
5 | .yarn-integrity
6 | /.bundle
7 | /config/master.key
8 | /db/*.sqlite3
9 | /db/*.sqlite3-journal
10 | /elm-stuff
11 | /log/*
12 | /node_modules
13 | /public/assets
14 | /public/packs
15 | /public/packs-test
16 | /storage/*
17 | /tmp/*
18 | /yarn-error.log
19 | coverage
20 | yarn-debug.log*
21 |
22 | /public/packs
23 | /public/packs-test
24 | /node_modules
25 | /yarn-error.log
26 | yarn-debug.log*
27 | .yarn-integrity
28 | .env*.local
29 |
30 | .idea
31 | .ruby-version
32 | /app/assets/builds/*
33 | !/app/assets/builds/.keep
34 |
35 | # Event to handlers and handler to events mappings generated by big_picture.rb script
36 | /lib/event_to_handlers.rb
37 | /lib/handler_to_events.rb
38 |
--------------------------------------------------------------------------------
/rails_application/Gemfile:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 |
3 | ruby '3.3.7'
4 |
5 | gem "rails", "~> 7.2.0"
6 | gem "pg", "~> 1.4"
7 | gem "puma", "~> 5.6"
8 | gem "bootsnap", ">= 1.9.2", require: false
9 | gem "honeybadger", "5.15.6"
10 | gem "skylight"
11 | gem "tailwindcss-rails", "~> 2.6.0"
12 | gem "kaminari"
13 | gem "sprockets-rails"
14 | gem "turbo-rails"
15 | gem "stimulus-rails"
16 | gem "importmap-rails", "~> 1.1"
17 | gem "rails_event_store", ">= 2.15.0", "< 3.0"
18 | gem 'arbre'
19 |
20 | group :development do
21 | gem "listen", "~> 3.3"
22 | end
23 |
24 | group :development, :test do
25 | gem "byebug", platforms: %i[mri mingw x64_mingw]
26 | gem "dotenv-rails"
27 | end
28 |
29 | group :test do
30 | eval_gemfile "../infra/Gemfile.test"
31 | end
32 |
33 | gem "infra", path: "../infra"
34 |
35 | gem "tzinfo-data", platforms: %i[mingw mswin x64_mingw jruby]
36 |
--------------------------------------------------------------------------------
/rails_application/Procfile:
--------------------------------------------------------------------------------
1 | release: bundle exec rake db:migrate
2 | web: bin/puma -C rails_application/config/puma.rb --dir rails_application
--------------------------------------------------------------------------------
/rails_application/Rakefile:
--------------------------------------------------------------------------------
1 | # Add your own tasks in files placed in lib/tasks ending in .rake,
2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
3 |
4 | require_relative "config/application"
5 |
6 | Rails.application.load_tasks
7 |
--------------------------------------------------------------------------------
/rails_application/app/assets/builds/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RailsEventStore/ecommerce/54996c6159999e29a776b766afad20400ea22dcc/rails_application/app/assets/builds/.keep
--------------------------------------------------------------------------------
/rails_application/app/assets/config/manifest.js:
--------------------------------------------------------------------------------
1 | //= link_tree ../images
2 | //= link_directory ../stylesheets .css
3 | //= link_tree ../builds
4 | //= link_tree ../../javascript .js
5 | //= link_tree ../../../vendor/javascript .js
6 |
--------------------------------------------------------------------------------
/rails_application/app/assets/images/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RailsEventStore/ecommerce/54996c6159999e29a776b766afad20400ea22dcc/rails_application/app/assets/images/.keep
--------------------------------------------------------------------------------
/rails_application/app/assets/stylesheets/application.tailwind.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | /*
6 |
7 | @layer components {
8 | .btn-primary {
9 | @apply py-2 px-4 bg-blue-200;
10 | }
11 | }
12 |
13 | */
14 |
--------------------------------------------------------------------------------
/rails_application/app/channels/application_cable/channel.rb:
--------------------------------------------------------------------------------
1 | module ApplicationCable
2 | class Channel < ActionCable::Channel::Base
3 | end
4 | end
5 |
--------------------------------------------------------------------------------
/rails_application/app/channels/application_cable/connection.rb:
--------------------------------------------------------------------------------
1 | module ApplicationCable
2 | class Connection < ActionCable::Connection::Base
3 | end
4 | end
5 |
--------------------------------------------------------------------------------
/rails_application/app/controllers/application_controller.rb:
--------------------------------------------------------------------------------
1 | class ApplicationController < ActionController::Base
2 | around_action :set_time_zone
3 |
4 | def event_store
5 | Rails.configuration.event_store
6 | end
7 |
8 | def command_bus
9 | Rails.configuration.command_bus
10 | end
11 |
12 | def not_found
13 | render file: "#{Rails.root}/public/404.html", layout: false, status: :not_found
14 | end
15 |
16 | private
17 |
18 | def set_time_zone(&block)
19 | Time.use_zone(cookies[:timezone], &block)
20 | end
21 | end
22 |
--------------------------------------------------------------------------------
/rails_application/app/controllers/client/base_controller.rb:
--------------------------------------------------------------------------------
1 | module Client
2 | class BaseController < ApplicationController
3 |
4 | layout "client_panel"
5 | before_action :ensure_logged_in
6 |
7 | private
8 |
9 | def ensure_logged_in
10 | if ClientOrders::Client.find_by(uid: cookies[:client_id]).nil?
11 | redirect_to logout_path
12 | return
13 | end
14 | end
15 | end
16 | end
--------------------------------------------------------------------------------
/rails_application/app/controllers/client/inbox_controller.rb:
--------------------------------------------------------------------------------
1 | module Client
2 | class InboxController < BaseController
3 |
4 | def index
5 | render html: ClientInbox::Rendering::InboxList.build(view_context, cookies[:client_id]), layout: true
6 | end
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/rails_application/app/controllers/client/products_controller.rb:
--------------------------------------------------------------------------------
1 | module Client
2 | class ProductsController < BaseController
3 |
4 | def index
5 | render html: PublicOffer::ProductsList.build(view_context), layout: true
6 | end
7 |
8 | end
9 | end
--------------------------------------------------------------------------------
/rails_application/app/controllers/events_catalog_controller.rb:
--------------------------------------------------------------------------------
1 | class EventsCatalogController < ApplicationController
2 | def index
3 | render file: ENV["EVENTS_CATALOG_PATH"] || "../events_catalog/out/index.html"
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/rails_application/app/controllers/invoices_controller.rb:
--------------------------------------------------------------------------------
1 | class InvoicesController < ApplicationController
2 | def show
3 | @invoice = Invoices::Invoice.find_by_order_uid(params[:id])
4 | not_found unless @invoice
5 | end
6 |
7 | def create
8 | begin
9 | ActiveRecord::Base.transaction do
10 | command_bus.(Invoicing::IssueInvoice.new(invoice_id: params[:order_id], issue_date: Time.zone.now.to_date))
11 | end
12 | rescue Invoicing::Invoice::BillingAddressNotSpecified
13 | flash[:alert] = "Billing address is missing"
14 | rescue Invoicing::Invoice::InvoiceAlreadyIssued
15 | flash[:alert] = "Invoice was already issued"
16 | rescue Invoicing::Invoice::InvoiceNumberInUse
17 | retry
18 | end
19 | redirect_to(invoice_path(params[:order_id]))
20 | end
21 | end
--------------------------------------------------------------------------------
/rails_application/app/controllers/product/future_price_controller.rb:
--------------------------------------------------------------------------------
1 | module Product
2 | class FuturePriceController < ApplicationController
3 | def add_future_price
4 | respond_to do |format|
5 | format.turbo_stream do
6 | render turbo_stream:
7 | turbo_stream.append(
8 | "future_prices",
9 | partial: "/products/future_price",
10 | locals: {
11 | disabled: false,
12 | valid_since: nil,
13 | price: nil
14 | }
15 | )
16 | end
17 | end
18 | end
19 | end
20 | end
21 |
--------------------------------------------------------------------------------
/rails_application/app/controllers/shipments_controller.rb:
--------------------------------------------------------------------------------
1 | class ShipmentsController < ApplicationController
2 | def index
3 | @shipments =
4 | Shipments::Shipment
5 | .joins(:order)
6 | .includes(:order)
7 | .with_full_address
8 | .order(id: :desc)
9 | .page(params[:page])
10 | .per(10)
11 | end
12 |
13 | def show
14 | @shipment = Shipments::Shipment.find(params[:id])
15 | @shipment_items = @shipment.shipment_items.page(params[:page]).per(25)
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/rails_application/app/javascript/application.js:
--------------------------------------------------------------------------------
1 | // Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails
2 | import "@hotwired/turbo-rails";
3 | import "channels";
4 | import "controllers";
5 |
--------------------------------------------------------------------------------
/rails_application/app/javascript/channels/consumer.js:
--------------------------------------------------------------------------------
1 | // Action Cable provides the framework to deal with WebSockets in Rails.
2 | // You can generate new channels where WebSocket features live using the `bin/rails generate channel` command.
3 |
4 | import { createConsumer } from "@rails/actioncable"
5 |
6 | export default createConsumer()
7 |
--------------------------------------------------------------------------------
/rails_application/app/javascript/channels/index.js:
--------------------------------------------------------------------------------
1 | // Import all the channels to be used by Action Cable
--------------------------------------------------------------------------------
/rails_application/app/javascript/controllers/application.js:
--------------------------------------------------------------------------------
1 | import { Application } from "@hotwired/stimulus"
2 |
3 | const application = Application.start()
4 |
5 | application.debug = false
6 | application.handleError = (error, message, detail) => {
7 | application.logger.error(`%s\n\n%o\n\n%o`, message, error, detail);
8 | };
9 | window.Stimulus = application
10 |
11 | export { application }
--------------------------------------------------------------------------------
/rails_application/app/javascript/controllers/index.js:
--------------------------------------------------------------------------------
1 | import { application } from "controllers/application"
2 | import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading"
3 |
4 | eagerLoadControllersFrom("controllers", application)
--------------------------------------------------------------------------------
/rails_application/app/javascript/controllers/timezone_controller.js:
--------------------------------------------------------------------------------
1 | // app/javascript/controllers/timezone_controller.js
2 | import { Controller } from "@hotwired/stimulus"
3 |
4 | export default class extends Controller {
5 |
6 | connect() {
7 | this.detectAndSetTimezone()
8 | }
9 |
10 | detectAndSetTimezone() {
11 | const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone
12 |
13 | document.cookie = `timezone=${timezone}`
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/rails_application/app/javascript/controllers/turbo_modal_controller.js:
--------------------------------------------------------------------------------
1 | import { Controller } from "@hotwired/stimulus"
2 |
3 | export default class extends Controller {
4 | hideModal() {
5 | this.element.parentElement.removeAttribute("src")
6 | this.element.remove()
7 | }
8 |
9 | submitEnd(e) {
10 | if (e.detail.success) {
11 | this.hideModal()
12 | }
13 | }
14 |
15 | closeWithKeyboard(e) {
16 | if (e.code == "Escape") {
17 | this.hideModal()
18 | }
19 | }
20 |
21 | closeBackground(e) {
22 | if (e && this.modalTarget.contains(e.target)) {
23 | return;
24 | }
25 | this.hideModal()
26 | }
27 | }
--------------------------------------------------------------------------------
/rails_application/app/models/application_record.rb:
--------------------------------------------------------------------------------
1 | class ApplicationRecord < ActiveRecord::Base
2 | self.abstract_class = true
3 |
4 | def self.with_advisory_lock(*args)
5 | transaction(requires_new: true) do
6 | ApplicationRecord.connection.execute("SELECT pg_advisory_xact_lock(#{Zlib.crc32(args.join)})")
7 | yield
8 | end
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/rails_application/app/processes/processes/confirm_order_on_payment_captured.rb:
--------------------------------------------------------------------------------
1 | module Processes
2 | class ConfirmOrderOnPaymentCaptured
3 |
4 | def initialize(command_bus)
5 | @command_bus = command_bus
6 | end
7 |
8 | def call(event)
9 | order_id = event.data.fetch(:order_id)
10 | command_bus.call(Fulfillment::ConfirmOrder.new(order_id: order_id))
11 | end
12 |
13 | private
14 | attr_reader :command_bus
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/rails_application/app/processes/processes/notify_payments_about_order_value.rb:
--------------------------------------------------------------------------------
1 | module Processes
2 | class NotifyPaymentsAboutOrderValue
3 | def initialize(event_store, command_bus)
4 | event_store.subscribe(
5 | ->(event) do
6 | command_bus.call(
7 | Payments::SetPaymentAmount.new(
8 | order_id: event.data.fetch(:order_id),
9 | amount: event.data.fetch(:discounted_amount).to_f
10 | )
11 | )
12 | end,
13 | to: [Pricing::OrderTotalValueCalculated]
14 | )
15 | end
16 | end
17 | end
--------------------------------------------------------------------------------
/rails_application/app/processes/processes/sync_shipment_from_pricing.rb:
--------------------------------------------------------------------------------
1 | module Processes
2 | class SyncShipmentFromPricing
3 | def initialize(event_store, command_bus)
4 | Infra::Process.new(event_store, command_bus)
5 | .call(Pricing::PriceItemAdded, [:order_id, :product_id],
6 | Shipping::AddItemToShipmentPickingList, [:order_id, :product_id])
7 | Infra::Process.new(event_store, command_bus)
8 | .call(Pricing::PriceItemRemoved, [:order_id, :product_id],
9 | Shipping::RemoveItemFromShipmentPickingList, [:order_id, :product_id])
10 | end
11 | end
12 | end
--------------------------------------------------------------------------------
/rails_application/app/processes/processes/welcome_message_process.rb:
--------------------------------------------------------------------------------
1 | module Processes
2 | class WelcomeMessageProcess
3 | def initialize(event_store, command_bus)
4 | @event_store = event_store
5 | @command_bus = command_bus
6 | end
7 |
8 | def call(event)
9 | case event
10 | when Crm::CustomerRegistered
11 | ClientInbox::Message.create(client_uid: event.data.fetch(:customer_id), title: "Welcome to our platform!")
12 | end
13 | end
14 | end
15 | end
--------------------------------------------------------------------------------
/rails_application/app/read_models/availability/configuration.rb:
--------------------------------------------------------------------------------
1 | module Availability
2 | class Product < ApplicationRecord
3 | self.table_name = "availability_products"
4 | end
5 |
6 | private_constant :Product
7 |
8 | def self.approximately_available?(product_id, desired_quantity)
9 | !Product.exists?(["uid = ? and available < ?", product_id, desired_quantity])
10 | end
11 |
12 | class UpdateAvailability
13 | def call(event)
14 | order = Product.find_or_create_by!(uid: event.data.fetch(:product_id))
15 | order.available = event.data.fetch(:available)
16 | order.save!
17 | end
18 | end
19 |
20 | class Configuration
21 | def call(event_store)
22 | event_store.subscribe(UpdateAvailability, to: [Inventory::AvailabilityChanged])
23 | end
24 | end
25 | end
--------------------------------------------------------------------------------
/rails_application/app/read_models/client_authentication/configuration.rb:
--------------------------------------------------------------------------------
1 | module ClientAuthentication
2 | class Account < ApplicationRecord
3 | self.table_name = "accounts"
4 | end
5 |
6 | class Configuration
7 | def call(event_store)
8 | event_store.subscribe(CreateAccount, to: [Authentication::AccountConnectedToClient])
9 | event_store.subscribe(SetPassword, to: [Authentication::PasswordHashSet])
10 | end
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/rails_application/app/read_models/client_authentication/create_account.rb:
--------------------------------------------------------------------------------
1 | module ClientAuthentication
2 | class CreateAccount
3 | def call(event)
4 | client_id = event.data.fetch(:client_id)
5 | account_id = event.data.fetch(:account_id)
6 | Account.find_or_create_by(account_id: account_id).update(client_id: client_id)
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/rails_application/app/read_models/client_authentication/set_password.rb:
--------------------------------------------------------------------------------
1 | module ClientAuthentication
2 | class SetPassword
3 | def call(event)
4 | find(event.data.fetch(:account_id)).update(password: event.data.fetch(:password_hash))
5 | end
6 |
7 | private
8 |
9 | def find(account_id)
10 | Account.find_or_create_by(account_id: account_id)
11 | end
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/rails_application/app/read_models/client_inbox/configuration.rb:
--------------------------------------------------------------------------------
1 | module ClientInbox
2 |
3 | class Message < ApplicationRecord
4 | self.table_name = 'client_inbox_messages'
5 | end
6 |
7 | class Configuration
8 | def call(event_store)
9 | end
10 | end
11 | end
--------------------------------------------------------------------------------
/rails_application/app/read_models/client_orders/create_customer.rb:
--------------------------------------------------------------------------------
1 | module ClientOrders
2 | class CreateCustomer
3 | def call(event)
4 | Client.create(
5 | uid: event.data.fetch(:customer_id),
6 | name: event.data.fetch(:name)
7 | )
8 | end
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/rails_application/app/read_models/coupons/configuration.rb:
--------------------------------------------------------------------------------
1 | module Coupons
2 | class Coupon < ApplicationRecord
3 | self.table_name = "coupons"
4 | end
5 |
6 | class Configuration
7 | def call(event_store)
8 | event_store.subscribe(RegisterCoupon.new, to: [Pricing::CouponRegistered])
9 | end
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/rails_application/app/read_models/coupons/register_coupon.rb:
--------------------------------------------------------------------------------
1 | module Coupons
2 | class RegisterCoupon
3 | def call(event)
4 | event_data = event.data
5 | Coupon.create(
6 | uid: event_data.fetch(:coupon_id),
7 | name: event_data.fetch(:name),
8 | code: event_data.fetch(:code),
9 | discount: event_data.fetch(:discount)
10 | )
11 | end
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/rails_application/app/read_models/customers/configuration.rb:
--------------------------------------------------------------------------------
1 | module Customers
2 | class Customer < ApplicationRecord
3 | self.table_name = "customers"
4 | end
5 |
6 | class Configuration
7 | def call(event_store)
8 | event_store.subscribe(RegisterCustomer.new, to: [Crm::CustomerRegistered])
9 | event_store.subscribe(PromoteToVip.new, to: [Crm::CustomerPromotedToVip])
10 | event_store.subscribe(UpdatePaidOrdersSummary.new, to: [Fulfillment::OrderConfirmed])
11 | event_store.subscribe(ConnectAccount.new, to: [Authentication::AccountConnectedToClient])
12 | end
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/rails_application/app/read_models/customers/connect_account.rb:
--------------------------------------------------------------------------------
1 | module Customers
2 | class ConnectAccount
3 | def call(event)
4 | Customer.find_or_create_by(id: event.data.fetch(:client_id)).update(account_id: event.data.fetch(:account_id))
5 | end
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/rails_application/app/read_models/customers/promote_to_vip.rb:
--------------------------------------------------------------------------------
1 | module Customers
2 | class PromoteToVip
3 | def call(event)
4 | promote_to_vip(event)
5 | end
6 |
7 | private
8 |
9 | def promote_to_vip(event)
10 | find(event.data.fetch(:customer_id)).update(vip: true)
11 | end
12 |
13 | def find(customer_id)
14 | Customer.where(id: customer_id).first
15 | end
16 |
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/rails_application/app/read_models/customers/register_customer.rb:
--------------------------------------------------------------------------------
1 | module Customers
2 | class RegisterCustomer
3 | def call(event)
4 | Customer.find_or_create_by(id: event.data.fetch(:customer_id)).update(name: event.data.fetch(:name))
5 | end
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/rails_application/app/read_models/customers/update_paid_orders_summary.rb:
--------------------------------------------------------------------------------
1 | module Customers
2 | class UpdatePaidOrdersSummary
3 | def call(event)
4 | order = ClientOrders::Order.find_by(order_uid: event.data.fetch(:order_id))
5 | customer = Customer.find(order.client_uid)
6 | customer.update(paid_orders_summary: customer.paid_orders_summary + order.discounted_value)
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/rails_application/app/read_models/invoices/create_invoice_item.rb:
--------------------------------------------------------------------------------
1 | module Invoices
2 | class CreateInvoiceItem
3 | def call(event)
4 | invoice = Invoice.find_or_initialize_by(order_uid: event.data.fetch(:invoice_id))
5 |
6 | item = InvoiceItem.create(
7 | invoice: invoice,
8 | name: event.data.fetch(:title),
9 | vat_rate: event.data.fetch(:vat_rate).fetch(:rate),
10 | unit_price: event.data.fetch(:unit_price),
11 | quantity: event.data.fetch(:quantity),
12 | value: event.data.fetch(:unit_price) * event.data.fetch(:quantity)
13 | )
14 | invoice.total_value = (invoice.total_value || 0) + item.value
15 | invoice.save!
16 | end
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/rails_application/app/read_models/invoices/mark_as_issued.rb:
--------------------------------------------------------------------------------
1 | module Invoices
2 | class MarkAsIssued
3 | def call(event)
4 | invoice = Invoice.find_or_initialize_by(order_uid: event.data.fetch(:invoice_id))
5 | invoice.issued = true
6 | invoice.issue_date = event.data.fetch(:issue_date)
7 | invoice.disposal_date = event.data.fetch(:disposal_date)
8 | invoice.number = event.data.fetch(:invoice_number)
9 | invoice.save!
10 | end
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/rails_application/app/read_models/invoices/mark_order_placed.rb:
--------------------------------------------------------------------------------
1 | module Invoices
2 | class MarkOrderPlaced
3 | def call(event)
4 | invoice = Invoice.find_or_initialize_by(order_uid: event.data.fetch(:order_id))
5 | Order.find_or_initialize_by(uid: event.data.fetch(:order_id)).update!(submitted: true)
6 | invoice.save!
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/rails_application/app/read_models/invoices/set_billing_address.rb:
--------------------------------------------------------------------------------
1 | module Invoices
2 | class SetBillingAddress
3 | def call(event)
4 | invoice = Invoice.find_or_initialize_by(order_uid: event.data.fetch(:invoice_id))
5 | invoice.address_present = true
6 | invoice.tax_id_number = event.data.fetch(:tax_id_number)
7 | postal_address = event.data.fetch(:postal_address)
8 | invoice.address_line_1 = postal_address.fetch(:line_1)
9 | invoice.address_line_2 = postal_address.fetch(:line_2)
10 | invoice.address_line_3 = postal_address.fetch(:line_3)
11 | invoice.address_line_4 = postal_address.fetch(:line_4)
12 | invoice.save!
13 | end
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/rails_application/app/read_models/invoices/set_payment_date.rb:
--------------------------------------------------------------------------------
1 | module Invoices
2 | class SetPaymentDate
3 | def call(event)
4 | invoice =
5 | Invoice.find_or_initialize_by(order_uid: event.data.fetch(:invoice_id))
6 | invoice.payment_date = event.data.fetch(:payment_date)
7 | invoice.save!
8 | end
9 | end
10 | end
11 |
12 |
--------------------------------------------------------------------------------
/rails_application/app/read_models/orders/assign_customer_to_order.rb:
--------------------------------------------------------------------------------
1 | module Orders
2 | class AssignCustomerToOrder
3 | def call(event)
4 | order_uid = event.data.fetch(:order_id)
5 | order = Order.find_or_create_by!(uid: order_uid) { |order| order.state = "Draft" }
6 | order.customer = Customer.find_by_uid(event.data.fetch(:customer_id)).name
7 | order.save!
8 |
9 | event_store.link_event_to_stream(event, "Orders$all")
10 | end
11 |
12 | private
13 |
14 | def event_store
15 | Rails.configuration.event_store
16 | end
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/rails_application/app/read_models/orders/broadcaster.rb:
--------------------------------------------------------------------------------
1 | module Orders
2 | class Broadcaster
3 | def call(stream_id, target_id, target_name, content)
4 | Turbo::StreamsChannel.broadcast_update_to(
5 | "orders_order_#{stream_id}",
6 | target: "orders_order_#{target_id}_#{target_name}",
7 | html: content)
8 | end
9 | end
10 | end
--------------------------------------------------------------------------------
/rails_application/app/read_models/orders/cancel_order.rb:
--------------------------------------------------------------------------------
1 | module Orders
2 | class CancelOrder
3 | def call(event)
4 | order = Order.find_by_uid(event.data.fetch(:order_id))
5 | order.state = "Cancelled"
6 | order.save!
7 |
8 | event_store.link_event_to_stream(event, "Orders$all")
9 | end
10 |
11 | private
12 |
13 | def event_store
14 | Rails.configuration.event_store
15 | end
16 | end
17 | end
18 |
19 |
--------------------------------------------------------------------------------
/rails_application/app/read_models/orders/change_product_name.rb:
--------------------------------------------------------------------------------
1 | module Orders
2 | class ChangeProductName
3 | def call(event)
4 | Product.find_or_create_by(uid: event.data.fetch(:product_id)).update(
5 | name: event.data.fetch(:name)
6 | )
7 |
8 | event_store.link_event_to_stream(event, "Orders$all")
9 | end
10 |
11 | private
12 |
13 | def event_store
14 | Rails.configuration.event_store
15 | end
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/rails_application/app/read_models/orders/change_product_price.rb:
--------------------------------------------------------------------------------
1 | module Orders
2 | class ChangeProductPrice
3 | def call(event)
4 | Product.find_or_create_by(uid: event.data.fetch(:product_id)).update(price: event.data.fetch(:price))
5 |
6 | event_store.link_event_to_stream(event, "Orders$all")
7 | end
8 |
9 | private
10 |
11 | def event_store
12 | Rails.configuration.event_store
13 | end
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/rails_application/app/read_models/orders/confirm_order.rb:
--------------------------------------------------------------------------------
1 | module Orders
2 | class ConfirmOrder
3 | def call(event)
4 | order = Order.find_by_uid(event.data.fetch(:order_id))
5 | order.state = "Paid"
6 | order.save!
7 |
8 | event_store.link_event_to_stream(event, "Orders$all")
9 | end
10 |
11 | private
12 |
13 | def event_store
14 | Rails.configuration.event_store
15 | end
16 | end
17 | end
18 |
19 |
--------------------------------------------------------------------------------
/rails_application/app/read_models/orders/create_customer.rb:
--------------------------------------------------------------------------------
1 | module Orders
2 | class CreateCustomer
3 | def call(event)
4 | Customer.create(
5 | uid: event.data.fetch(:customer_id),
6 | name: event.data.fetch(:name)
7 | )
8 |
9 | event_store.link_event_to_stream(event, "Orders$all")
10 | end
11 |
12 | private
13 |
14 | def event_store
15 | Rails.configuration.event_store
16 | end
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/rails_application/app/read_models/orders/expire_order.rb:
--------------------------------------------------------------------------------
1 | module Orders
2 | class ExpireOrder
3 | def call(event)
4 | order = Order.find_by_uid(event.data.fetch(:order_id))
5 | order.state = "Expired"
6 | order.save!
7 |
8 | event_store.link_event_to_stream(event, "Orders$all")
9 | end
10 |
11 | private
12 |
13 | def event_store
14 | Rails.configuration.event_store
15 | end
16 | end
17 | end
18 |
19 |
--------------------------------------------------------------------------------
/rails_application/app/read_models/orders/register_product.rb:
--------------------------------------------------------------------------------
1 | module Orders
2 | class RegisterProduct
3 | def call(event)
4 | Product.create(uid: event.data.fetch(:product_id))
5 |
6 | event_store.link_event_to_stream(event, "Orders$all")
7 | end
8 |
9 | private
10 |
11 | def event_store
12 | Rails.configuration.event_store
13 | end
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/rails_application/app/read_models/orders/remove_discount.rb:
--------------------------------------------------------------------------------
1 | module Orders
2 | class RemoveDiscount
3 | def call(event)
4 | return unless event.data.fetch(:type) == Pricing::Discounts::GENERAL_DISCOUNT
5 |
6 | order = Order.find_by_uid(event.data.fetch(:order_id))
7 | order.percentage_discount = nil
8 | order.save!
9 |
10 | event_store.link_event_to_stream(event, "Orders$all")
11 | end
12 |
13 | private
14 |
15 | def event_store
16 | Rails.configuration.event_store
17 | end
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/rails_application/app/read_models/orders/remove_time_promotion_discount.rb:
--------------------------------------------------------------------------------
1 | module Orders
2 | class RemoveTimePromotionDiscount
3 | def call(event)
4 | return unless event.data.fetch(:type) == Pricing::Discounts::TIME_PROMOTION_DISCOUNT
5 |
6 | order = Order.find_by(uid: event.data.fetch(:order_id))
7 |
8 | order.time_promotion_discount_value = nil
9 | order.save!
10 | end
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/rails_application/app/read_models/orders/submit_order.rb:
--------------------------------------------------------------------------------
1 | module Orders
2 | class SubmitOrder
3 | def call(event)
4 | order_id = event.data.fetch(:order_id)
5 | order = Order.find_or_create_by!(uid: order_id)
6 | order.number = event.data.fetch(:order_number)
7 | order.state = "Submitted"
8 | order.save!
9 | event_store.link_event_to_stream(event, "Orders$all")
10 | end
11 |
12 | private
13 |
14 | def event_store
15 | Rails.configuration.event_store
16 | end
17 | end
18 | end
19 |
20 |
--------------------------------------------------------------------------------
/rails_application/app/read_models/orders/update_time_promotion_discount_value.rb:
--------------------------------------------------------------------------------
1 | module Orders
2 | class UpdateTimePromotionDiscountValue
3 | def call(event)
4 | return unless event.data.fetch(:type) == Pricing::Discounts::TIME_PROMOTION_DISCOUNT
5 |
6 | order = Order.find_or_create_by(uid: event.data.fetch(:order_id))
7 |
8 | order.time_promotion_discount_value = event.data.fetch(:amount)
9 | order.save!
10 | end
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/rails_application/app/read_models/products/refresh_future_prices_calendar.rb:
--------------------------------------------------------------------------------
1 | module Products
2 | class RefreshFuturePricesCalendar
3 | def call(event)
4 | product_id = event.data.fetch(:product_id)
5 | product = Product.find_or_create_by(id: product_id)
6 | product.update!(current_prices_calendar: updated_prices_calendar(event, product))
7 | end
8 |
9 | private
10 |
11 | def updated_prices_calendar(event, product)
12 | (product.read_attribute(:current_prices_calendar) << new_entry_from_event(event))
13 | .sort_by { |entry| Time.parse(entry[:valid_since]) }
14 | end
15 |
16 | def new_entry_from_event(event)
17 | {
18 | price: event.data.fetch(:price).to_s,
19 | valid_since: event.metadata.fetch(:valid_at).to_s
20 | }
21 | end
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/rails_application/app/read_models/public_offer/product.rb:
--------------------------------------------------------------------------------
1 | module PublicOffer
2 | class Product < ApplicationRecord
3 | self.table_name = "public_offer_products"
4 |
5 | def lowest_recent_price_lower_from_current?
6 | lowest_recent_price && lowest_recent_price < price
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/rails_application/app/read_models/refunds/add_item_to_refund.rb:
--------------------------------------------------------------------------------
1 | module Refunds
2 | class AddItemToRefund
3 | def call(event)
4 | refund = Refund.find_by!(uid: event.data.fetch(:refund_id))
5 | product = Orders::Product.find_by!(uid: event.data.fetch(:product_id))
6 |
7 | item = refund.refund_items.find_or_create_by(product_uid: product.uid) do |item|
8 | item.price = product.price
9 | item.quantity = 0
10 | end
11 |
12 | refund.total_value += item.price
13 | item.quantity += 1
14 |
15 | ActiveRecord::Base.transaction do
16 | refund.save!
17 | item.save!
18 | end
19 | end
20 | end
21 | end
22 |
--------------------------------------------------------------------------------
/rails_application/app/read_models/refunds/create_draft_refund.rb:
--------------------------------------------------------------------------------
1 | module Refunds
2 | class CreateDraftRefund
3 | def call(event)
4 | Refund.create!(uid: event.data[:refund_id], order_uid: event.data[:order_id], status: "Draft", total_value: 0)
5 | end
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/rails_application/app/read_models/refunds/remove_item_from_refund.rb:
--------------------------------------------------------------------------------
1 | module Refunds
2 | class RemoveItemFromRefund
3 | def call(event)
4 | refund = Refund.find_by!(uid: event.data.fetch(:refund_id))
5 | item = refund.refund_items.find_by!(product_uid: event.data.fetch(:product_id))
6 |
7 | refund.total_value -= item.price
8 | item.quantity -= 1
9 |
10 | ActiveRecord::Base.transaction do
11 | refund.save!
12 | item.quantity > 0 ? item.save! : item.destroy!
13 | end
14 | end
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/rails_application/app/read_models/shipments/add_item_to_shipment.rb:
--------------------------------------------------------------------------------
1 | module Shipments
2 | class AddItemToShipment
3 | def call(event)
4 | product_id = event.data.fetch(:product_id)
5 | order_id = event.data.fetch(:order_id)
6 | product = Orders::Product.find_by_uid!(product_id)
7 |
8 | item = find_or_create_item(order_id, product)
9 | item.quantity += 1
10 | item.save!
11 | end
12 |
13 | private
14 |
15 | def find_or_create_item(order_id, product)
16 | Shipment
17 | .find_or_create_by!(order_uid: order_id)
18 | .shipment_items
19 | .create_with(product_name: product.name, quantity: 0)
20 | .find_or_create_by!(product_id: product.uid)
21 | end
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/rails_application/app/read_models/shipments/mark_order_placed.rb:
--------------------------------------------------------------------------------
1 | module Shipments
2 | class MarkOrderPlaced
3 | def call(event)
4 | Order.find_or_initialize_by(uid: event.data.fetch(:order_id)).update!(submitted: true)
5 | end
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/rails_application/app/read_models/shipments/remove_item_from_shipment.rb:
--------------------------------------------------------------------------------
1 | module Shipments
2 | class RemoveItemFromShipment
3 | def call(event)
4 | product_id = event.data.fetch(:product_id)
5 | order_id = event.data.fetch(:order_id)
6 |
7 | item = find(order_id, product_id)
8 | item.quantity -= 1
9 | item.quantity > 0 ? item.save! : item.destroy!
10 | end
11 |
12 | private
13 |
14 | def find(order_uid, product_id)
15 | Shipment
16 | .find_by!(order_uid: order_uid)
17 | .shipment_items
18 | .find_by!(product_id: product_id)
19 | end
20 | end
21 | end
22 |
--------------------------------------------------------------------------------
/rails_application/app/read_models/shipments/set_shipping_address.rb:
--------------------------------------------------------------------------------
1 | module Shipments
2 | class SetShippingAddress
3 | def call(event)
4 | shipment = Shipment.find_or_create_by(order_uid: event.data.fetch(:order_id))
5 | postal_address = event.data.fetch(:postal_address)
6 | shipment.address_line_1 = postal_address.fetch(:line_1)
7 | shipment.address_line_2 = postal_address.fetch(:line_2)
8 | shipment.address_line_3 = postal_address.fetch(:line_3)
9 | shipment.address_line_4 = postal_address.fetch(:line_4)
10 | shipment.save!
11 | end
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/rails_application/app/read_models/time_promotions/broadcaster.rb:
--------------------------------------------------------------------------------
1 | module TimePromotions
2 | class Broadcaster
3 | def call(content)
4 | Turbo::StreamsChannel.broadcast_append_to(
5 | "time_promotions",
6 | target: "time_promotions_table",
7 | html: content)
8 | end
9 | end
10 | end
--------------------------------------------------------------------------------
/rails_application/app/read_models/time_promotions/configuration.rb:
--------------------------------------------------------------------------------
1 | module TimePromotions
2 | class TimePromotion < ApplicationRecord
3 | self.table_name = "time_promotions"
4 |
5 | scope :current, -> { where("start_time < ? AND end_time > ?", Time.current, Time.current) }
6 | end
7 |
8 | class Configuration
9 | def call(event_store)
10 | event_store.subscribe(CreateTimePromotion, to: [Pricing::TimePromotionCreated])
11 | end
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/rails_application/app/read_models/time_promotions/create_time_promotion.rb:
--------------------------------------------------------------------------------
1 | module TimePromotions
2 | class CreateTimePromotion
3 | def call(event)
4 | time_promotion = TimePromotion.create!(event.data.slice(:code, :discount, :start_time, :end_time, :label).merge(id: event.data[:time_promotion_id]))
5 | Broadcaster.new.call(<<~HTML
6 |
7 | #{time_promotion.label} |
8 | #{time_promotion.discount} |
9 | #{time_promotion.start_time.strftime("%Y-%m-%d %H:%M")} |
10 | #{time_promotion.end_time.strftime(("%Y-%m-%d %H:%M"))} |
11 |
12 | HTML
13 | )
14 | end
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/rails_application/app/read_models/vat_rates/add_available_vat_rate.rb:
--------------------------------------------------------------------------------
1 | module VatRates
2 | class AddAvailableVatRate
3 | def call(event)
4 | AvailableVatRate.create!(
5 | uid: event.data.fetch(:available_vat_rate_id),
6 | code: event.data.fetch(:vat_rate).fetch(:code),
7 | rate: event.data.fetch(:vat_rate).fetch(:rate)
8 | )
9 | end
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/rails_application/app/read_models/vat_rates/configuration.rb:
--------------------------------------------------------------------------------
1 | module VatRates
2 | class AvailableVatRate < ApplicationRecord
3 | self.table_name = "available_vat_rates"
4 | end
5 |
6 | class Configuration
7 | def call(event_store)
8 | event_store.subscribe(AddAvailableVatRate, to: [Taxes::AvailableVatRateAdded])
9 | event_store.subscribe(RemoveAvailableVatRate, to: [Taxes::AvailableVatRateRemoved])
10 | end
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/rails_application/app/read_models/vat_rates/remove_available_vat_rate.rb:
--------------------------------------------------------------------------------
1 | module VatRates
2 | class RemoveAvailableVatRate
3 | def call(event)
4 | AvailableVatRate.destroy_by(code: event.data.fetch(:vat_rate_code))
5 | end
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/rails_application/app/views/application/_client_top_navigation.html.erb:
--------------------------------------------------------------------------------
1 |
21 |
--------------------------------------------------------------------------------
/rails_application/app/views/application/_tracking.html.erb:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/rails_application/app/views/coupons/index.html.erb:
--------------------------------------------------------------------------------
1 | <% content_for(:header) do %>
2 | Coupons
3 | <% end %>
4 |
5 | <% content_for(:actions) do %>
6 | <%= primary_action_button do %>
7 | <%= link_to 'New Coupon', new_coupon_path %>
8 | <% end %>
9 | <% end %>
10 |
11 |
12 |
13 |
14 | Name |
15 | Code |
16 | Discount % |
17 |
18 |
19 |
20 |
21 | <% @coupons.each do |coupon| %>
22 |
23 | <%= coupon.name %> |
24 | <%= coupon.code %> |
25 | <%= coupon.discount %> |
26 |
27 | <% end %>
28 |
29 |
30 |
--------------------------------------------------------------------------------
/rails_application/app/views/customers/new.html.erb:
--------------------------------------------------------------------------------
1 | <% content_for(:header) do %>
2 | New Customer
3 | <% end %>
4 |
5 | <% content_for(:actions) do %>
6 | <%= secondary_action_button do %>
7 | <%= link_to 'Back', customers_path %>
8 | <% end %>
9 |
10 | <%= primary_form_action_button do %>
11 | Create Customer
12 | <% end %>
13 | <% end %>
14 |
15 | <%= form_tag({controller: "customers", action: "create"}, method: "post", id: "form") do %>
16 | <%= hidden_field_tag(:customer_id, @customer_id) %>
17 |
18 |
21 | <%= text_field_tag :name, "", required: true, class: "mt-1 focus:ring-blue-500 focus:border-blue-500 block shadow-sm sm:text-sm border-gray-300 rounded-md" %>
22 | <% end %>
23 |
--------------------------------------------------------------------------------
/rails_application/app/views/kaminari/_gap.html.erb:
--------------------------------------------------------------------------------
1 | <%# Non-link tag that stands for skipped pages...
2 | - available local variables
3 | current_page: a page object for the currently displayed page
4 | total_pages: total number of pages
5 | per_page: number of items to fetch per page
6 | remote: data-remote
7 | -%>
8 | <%= t('views.pagination.truncate').html_safe %>
9 |
--------------------------------------------------------------------------------
/rails_application/app/views/kaminari/_page.html.erb:
--------------------------------------------------------------------------------
1 | <%# Link showing page number
2 | - available local variables
3 | page: a page object for "this" page
4 | url: url to this page
5 | current_page: a page object for the currently displayed page
6 | total_pages: total number of pages
7 | per_page: number of items to fetch per page
8 | remote: data-remote
9 | -%>
10 |
11 | <%= link_to page, url, {
12 | remote: remote,
13 | rel: page.rel,
14 | class: class_names(
15 | "relative inline-flex items-center px-4 py-2 border text-sm font-medium",
16 | "z-10 bg-blue-50 border-blue-500 text-blue-600 " => page.current?,
17 | "bg-white border-gray-300 text-gray-500 hover:bg-gray-50" => !page.current?
18 | )
19 | }.merge(Hash(({"aria-current": "page"} if page.current?))) %>
--------------------------------------------------------------------------------
/rails_application/app/views/orders/edit_discount.html.erb:
--------------------------------------------------------------------------------
1 | <% content_for(:header) do %>
2 | Discount
3 | <% end %>
4 |
5 | <% content_for(:actions) do %>
6 | <%= secondary_action_button do %>
7 | <%= link_to 'Back', edit_order_path(@order_id) %>
8 | <% end %>
9 |
10 | <%= primary_form_action_button do %>
11 | Set Discount
12 | <% end %>
13 | <% end %>
14 |
15 | <%= form_tag({controller: "orders", action: "update_discount"}, method: "post", id: "form") do %>
16 | <%= hidden_field_tag(:order_id, @order_id) %>
17 |
20 | <%= number_field_tag :amount, 20, min: 10, max: 100, step: 10, id: "discount", class: "mt-1 focus:ring-blue-500 focus:border-blue-500 block shadow-sm sm:text-sm border-gray-300 rounded-md" %>
21 | <% end %>
--------------------------------------------------------------------------------
/rails_application/bin/dev:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | if ! command -v foreman &> /dev/null
4 | then
5 | echo "Installing foreman..."
6 | gem install foreman
7 | fi
8 |
9 | foreman start -f Procfile.dev
10 |
--------------------------------------------------------------------------------
/rails_application/bin/importmap:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | require_relative "../config/application"
4 | require "importmap/commands"
5 |
--------------------------------------------------------------------------------
/rails_application/bin/puma:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | #
5 | # This file was generated by Bundler.
6 | #
7 | # The application 'puma' is installed as part of a gem, and
8 | # this file is here to facilitate running it.
9 | #
10 |
11 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
12 |
13 | bundle_binstub = File.expand_path("bundle", __dir__)
14 |
15 | if File.file?(bundle_binstub)
16 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
17 | load(bundle_binstub)
18 | else
19 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
20 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
21 | end
22 | end
23 |
24 | require "rubygems"
25 | require "bundler/setup"
26 |
27 | load Gem.bin_path("puma", "puma")
28 |
--------------------------------------------------------------------------------
/rails_application/bin/pumactl:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | #
5 | # This file was generated by Bundler.
6 | #
7 | # The application 'pumactl' is installed as part of a gem, and
8 | # this file is here to facilitate running it.
9 | #
10 |
11 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
12 |
13 | bundle_binstub = File.expand_path("bundle", __dir__)
14 |
15 | if File.file?(bundle_binstub)
16 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
17 | load(bundle_binstub)
18 | else
19 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
20 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
21 | end
22 | end
23 |
24 | require "rubygems"
25 | require "bundler/setup"
26 |
27 | load Gem.bin_path("puma", "pumactl")
28 |
--------------------------------------------------------------------------------
/rails_application/bin/rails:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | APP_PATH = File.expand_path("../config/application", __dir__)
3 | require_relative "../config/boot"
4 | require "rails/commands"
5 |
--------------------------------------------------------------------------------
/rails_application/bin/rake:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require_relative "../config/boot"
3 | require "rake"
4 | Rake.application.run
5 |
--------------------------------------------------------------------------------
/rails_application/bin/reset_heroku_db.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | heroku restart --app=res-ecommerce-rails
4 | heroku pg:reset DATABASE --app=res-ecommerce-rails --confirm res-ecommerce-rails
5 | heroku run "cd rails_application; rake db:schema:load" --app=res-ecommerce-rails
6 | heroku run "cd rails_application; rake db:seed" --app=res-ecommerce-rails
7 |
--------------------------------------------------------------------------------
/rails_application/config.ru:
--------------------------------------------------------------------------------
1 | # This file is used by Rack-based servers to start the application.
2 |
3 | require_relative "config/environment"
4 |
5 | run Rails.application
6 | Rails.application.load_server
7 |
--------------------------------------------------------------------------------
/rails_application/config/application.rb:
--------------------------------------------------------------------------------
1 | require_relative "boot"
2 |
3 | require "rails"
4 | require "active_model/railtie"
5 | require "active_job/railtie"
6 | require "active_record/railtie"
7 | require "action_controller/railtie"
8 | require "action_view/railtie"
9 | require "action_cable/engine"
10 | require "rails/test_unit/railtie"
11 |
12 | Bundler.require(*Rails.groups)
13 |
14 | class RailsApplication
15 | class Application < Rails::Application
16 |
17 | config.load_defaults 7.2
18 | config.generators.system_tests = nil
19 | end
20 | end
21 |
--------------------------------------------------------------------------------
/rails_application/config/boot.rb:
--------------------------------------------------------------------------------
1 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
2 |
3 | require "bundler/setup" # Set up gems listed in the Gemfile.
4 | require "bootsnap/setup" # Speed up boot time by caching expensive operations.
5 |
--------------------------------------------------------------------------------
/rails_application/config/cable.yml:
--------------------------------------------------------------------------------
1 | development:
2 | adapter: postgresql
3 |
4 | test:
5 | adapter: async
6 |
7 | production:
8 | adapter: postgresql
--------------------------------------------------------------------------------
/rails_application/config/credentials.yml.enc:
--------------------------------------------------------------------------------
1 | 2qDA1olCP8FK/VU1SR/iMB//MXCr+5rNGfdZ+D+4xNCASu7YWL8pEPzNeIKdmLwcJE9pXQc53wOlIoeqFDf1Z8OXlogkMFJQmx1de+2P/RGY5tqkfXCRrWWGPu10rINb8tWN3JIs/HkXpRSTHBxGjpeKTDzBL9C1zI5m27d0WjtKTs1hDwMnepc7/Fish6QCc9av+31wR2Q70+lgpKEyG3Gt3Aqsc0/X9JP5jHI/X3rJ2UYGjRNHxdfkNelERtZONz0wVou2MlFzgYrfxkMQSbllA0Z0x4CZLqxdwUByZoF2gZUJdfB51RjWPgLVj5vxyxSxXdAjKOx0tLj7g3l/4+1rPZN4A7dI4Uvlq7AApT9dUCk5/XEBCm217LIor55D0roJl9wohevwU0mQWOSO/piQ2GJPVeRUaho2--jtmUvUDxD9a1kPRN--KI0w8zoY8tfhkBBw1U+79A==
--------------------------------------------------------------------------------
/rails_application/config/database.yml:
--------------------------------------------------------------------------------
1 | default: &default
2 | url: <%= ENV['DATABASE_URL'] %>
3 | pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
4 | timeout: 5000
5 |
6 | development:
7 | <<: *default
8 |
9 | test:
10 | <<: *default
11 |
12 | production:
13 | <<: *default
14 |
--------------------------------------------------------------------------------
/rails_application/config/environment.rb:
--------------------------------------------------------------------------------
1 | # Load the Rails application.
2 | require_relative "application"
3 |
4 | # Initialize the Rails application.
5 | Rails.application.initialize!
6 |
--------------------------------------------------------------------------------
/rails_application/config/importmap.rb:
--------------------------------------------------------------------------------
1 | # Pin npm packages by running ./bin/importmap
2 |
3 | pin "application", preload: true
4 | pin "@hotwired/turbo-rails", to: "turbo.min.js", preload: true
5 | pin "@hotwired/stimulus", to: "stimulus.min.js", preload: true
6 | pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true
7 | pin "@rails/actioncable", to: "actioncable.esm.js"
8 | pin_all_from "app/javascript/channels", under: "channels"
9 | pin_all_from "app/javascript/controllers", under: "controllers"
10 |
--------------------------------------------------------------------------------
/rails_application/config/initializers/application_controller_renderer.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # ActiveSupport::Reloader.to_prepare do
4 | # ApplicationController.renderer.defaults.merge!(
5 | # http_host: 'example.org',
6 | # https: false
7 | # )
8 | # end
9 |
--------------------------------------------------------------------------------
/rails_application/config/initializers/assets.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Version of your assets, change this if you want to expire all your assets.
4 | Rails.application.config.assets.version = "1.0"
5 |
6 | # Add additional assets to the asset load path.
7 | # Rails.application.config.assets.paths << Emoji.images_path
8 |
9 | # Precompile additional assets.
10 | # application.js, application.css, and all non-JS/CSS in the app/assets
11 | # folder are already added.
12 | # Rails.application.config.assets.precompile += %w( admin.js admin.css )
13 |
--------------------------------------------------------------------------------
/rails_application/config/initializers/backtrace_silencers.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
4 | # Rails.backtrace_cleaner.add_silencer { |line| /my_noisy_library/.match?(line) }
5 |
6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code
7 | # by setting BACKTRACE=1 before calling your invocation, like "BACKTRACE=1 ./bin/rails runner 'MyClass.perform'".
8 | Rails.backtrace_cleaner.remove_silencers! if ENV["BACKTRACE"]
9 |
--------------------------------------------------------------------------------
/rails_application/config/initializers/cookies_serializer.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Specify a serializer for the signed and encrypted cookie jars.
4 | # Valid options are :json, :marshal, and :hybrid.
5 | Rails.application.config.action_dispatch.cookies_serializer = :json
6 |
--------------------------------------------------------------------------------
/rails_application/config/initializers/filter_parameter_logging.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Configure sensitive parameters which will be filtered from the log file.
4 | Rails.application.config.filter_parameters += [
5 | :passw, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn
6 | ]
7 |
--------------------------------------------------------------------------------
/rails_application/config/initializers/inflections.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new inflection rules using the following format. Inflections
4 | # are locale specific, and you may define rules for as many different
5 | # locales as you wish. All of these examples are active by default:
6 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
7 | # inflect.plural /^(ox)$/i, '\1en'
8 | # inflect.singular /^(ox)en/i, '\1'
9 | # inflect.irregular 'person', 'people'
10 | # inflect.uncountable %w( fish sheep )
11 | # end
12 |
13 | # These inflection rules are supported but not enabled by default:
14 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
15 | # inflect.acronym 'RESTful'
16 | # end
17 |
--------------------------------------------------------------------------------
/rails_application/config/initializers/mime_types.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new mime types for use in respond_to blocks:
4 | # Mime::Type.register "text/richtext", :rtf
5 |
--------------------------------------------------------------------------------
/rails_application/config/initializers/permissions_policy.rb:
--------------------------------------------------------------------------------
1 | # Define an application-wide HTTP permissions policy. For further
2 | # information see https://developers.google.com/web/updates/2018/06/feature-policy
3 | #
4 | # Rails.application.config.permissions_policy do |f|
5 | # f.camera :none
6 | # f.gyroscope :none
7 | # f.microphone :none
8 | # f.usb :none
9 | # f.fullscreen :self
10 | # f.payment :self, "https://secure.example.com"
11 | # end
12 |
--------------------------------------------------------------------------------
/rails_application/config/initializers/rails_event_store.rb:
--------------------------------------------------------------------------------
1 | require "rails_event_store"
2 | require "arkency/command_bus"
3 |
4 | require_relative "../../lib/configuration"
5 |
6 | Rails.configuration.to_prepare do
7 | Rails.configuration.event_store = Infra::EventStore.main
8 | Rails.configuration.command_bus = Arkency::CommandBus.new
9 |
10 | Configuration.new.call(Rails.configuration.event_store, Rails.configuration.command_bus)
11 | end
--------------------------------------------------------------------------------
/rails_application/config/initializers/session_store.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | Rails.application.config.session_store :cookie_store, key: "_cqrs_es_sample_with_res_session"
4 |
--------------------------------------------------------------------------------
/rails_application/config/initializers/wrap_parameters.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # This file contains settings for ActionController::ParamsWrapper which
4 | # is enabled by default.
5 |
6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
7 | ActiveSupport.on_load(:action_controller) do
8 | wrap_parameters format: [:json]
9 | end
10 |
11 | # To enable root element in JSON for ActiveRecord objects.
12 | # ActiveSupport.on_load(:active_record) do
13 | # self.include_root_in_json = true
14 | # end
15 |
--------------------------------------------------------------------------------
/rails_application/config/sidekiq.yml:
--------------------------------------------------------------------------------
1 | ---
2 | :verbose: false
3 | :concurrency: 5
4 | :timeout: 30
5 | :queues:
6 | - default
7 |
8 |
--------------------------------------------------------------------------------
/rails_application/config/tailwind.config.js:
--------------------------------------------------------------------------------
1 | const defaultTheme = require('tailwindcss/defaultTheme')
2 |
3 | module.exports = {
4 | content: [
5 | './public/*.html',
6 | './app/helpers/**/*.rb',
7 | './app/views/**/*.{erb,haml,html,slim}',
8 | './app/read_models/**/*.rb'
9 | ],
10 | theme: {
11 | extend: {
12 | fontFamily: {
13 | sans: ['Inter var', ...defaultTheme.fontFamily.sans],
14 | },
15 | },
16 | },
17 | plugins: [
18 | require('@tailwindcss/forms'),
19 | require('@tailwindcss/aspect-ratio'),
20 | require('@tailwindcss/typography'),
21 | ]
22 | }
23 |
--------------------------------------------------------------------------------
/rails_application/db/migrate/20150429224522_create_orders.rb:
--------------------------------------------------------------------------------
1 | class CreateOrders < ActiveRecord::Migration[5.1]
2 | def change
3 | create_table :orders do |t|
4 | t.string :uid
5 | t.string :number
6 | t.string :customer
7 | t.string :state
8 | end
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/rails_application/db/migrate/20150429224621_create_order_lines.rb:
--------------------------------------------------------------------------------
1 | class CreateOrderLines < ActiveRecord::Migration[5.1]
2 | def change
3 | create_table :order_lines do |t|
4 | t.string :order_uid
5 | t.integer :product_id
6 | t.string :product_name
7 | t.integer :quantity
8 | end
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/rails_application/db/migrate/20150429224628_create_customers.rb:
--------------------------------------------------------------------------------
1 | class CreateCustomers < ActiveRecord::Migration[5.1]
2 | def change
3 | create_table :customers do |t|
4 | t.string :name
5 |
6 | t.timestamps null: false
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/rails_application/db/migrate/20150429224746_create_products.rb:
--------------------------------------------------------------------------------
1 | class CreateProducts < ActiveRecord::Migration[5.1]
2 | def change
3 | create_table :products do |t|
4 | t.string :name
5 |
6 | t.timestamps null: false
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/rails_application/db/migrate/20181123154324_index_by_event_type.rb:
--------------------------------------------------------------------------------
1 | class IndexByEventType < ActiveRecord::Migration[4.2]
2 | def change
3 | add_index :event_store_events, :event_type unless index_exists? :event_store_events, :event_type
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/rails_application/db/migrate/20181123155503_limit_for_event_id.rb:
--------------------------------------------------------------------------------
1 | class LimitForEventId < ActiveRecord::Migration[4.2]
2 | def change
3 | postgres = ActiveRecord::Base.connection.adapter_name == "PostgreSQL"
4 | change_column(:event_store_events_in_streams, :event_id, :string, limit: 36) unless postgres
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/rails_application/db/migrate/20210102110315_harmonize_schema.rb:
--------------------------------------------------------------------------------
1 | class HarmonizeSchema < ActiveRecord::Migration[5.2]
2 | def change
3 | execute "ALTER TABLE event_store_events ALTER COLUMN id TYPE uuid USING id::uuid;"
4 | execute "ALTER TABLE event_store_events_in_streams ALTER COLUMN event_id TYPE uuid USING event_id::uuid;"
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/rails_application/db/migrate/20210102182818_binary_to_jsonb.rb:
--------------------------------------------------------------------------------
1 | class BinaryToJsonb < ActiveRecord::Migration[5.2]
2 | def change
3 | execute "ALTER TABLE event_store_events ALTER COLUMN data TYPE jsonb USING convert_from(data, 'UTF-8')::jsonb"
4 | execute "ALTER TABLE event_store_events ALTER COLUMN metadata TYPE jsonb USING convert_from(metadata, 'UTF-8')::jsonb"
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/rails_application/db/migrate/20210103114309_add_valid_at.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class AddValidAt < ActiveRecord::Migration[4.2]
4 | def change
5 | case ActiveRecord::Base.connection.adapter_name
6 | when "PostgreSQL"
7 | add_column :event_store_events, :valid_at, :datetime, null: true
8 | else
9 | add_column :event_store_events, :valid_at, :datetime, precision: 6, null: true
10 | end
11 |
12 | add_index :event_store_events, :valid_at
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/rails_application/db/migrate/20210103114315_add_foreign_key_constraint_to_active_storage_attachments_for_blob_id.active_storage.rb:
--------------------------------------------------------------------------------
1 | # This migration comes from active_storage (originally 20180723000244)
2 | class AddForeignKeyConstraintToActiveStorageAttachmentsForBlobId < ActiveRecord::Migration[6.0]
3 | def up
4 | return if foreign_key_exists?(:active_storage_attachments, column: :blob_id)
5 |
6 | if table_exists?(:active_storage_blobs)
7 | add_foreign_key :active_storage_attachments, :active_storage_blobs, column: :blob_id
8 | end
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/rails_application/db/migrate/20210306222803_create_active_admin_comments.rb:
--------------------------------------------------------------------------------
1 | class CreateActiveAdminComments < ActiveRecord::Migration[6.1]
2 | def self.up
3 | create_table :active_admin_comments do |t|
4 | t.string :namespace
5 | t.text :body
6 | t.references :resource, polymorphic: true
7 | t.references :author, polymorphic: true
8 | t.timestamps
9 | end
10 | add_index :active_admin_comments, [:namespace]
11 | end
12 |
13 | def self.down
14 | drop_table :active_admin_comments
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/rails_application/db/migrate/20210318093812_disable_pg_crypto.rb:
--------------------------------------------------------------------------------
1 | class DisablePgCrypto < ActiveRecord::Migration[6.1]
2 | def up
3 | disable_extension "pg_crypto"
4 | end
5 |
6 | def down
7 | enable_extension "pg_crypto"
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/rails_application/db/migrate/20210505162028_add_price_to_products.rb:
--------------------------------------------------------------------------------
1 | class AddPriceToProducts < ActiveRecord::Migration[6.1]
2 | def change
3 | add_column :products, :price, :decimal, precision: 8, scale: 2
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/rails_application/db/migrate/20210505163254_add_price_to_order_lines.rb:
--------------------------------------------------------------------------------
1 | class AddPriceToOrderLines < ActiveRecord::Migration[6.1]
2 | def change
3 | add_column :order_lines, :price, :decimal, precision: 8, scale: 2
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/rails_application/db/migrate/20210617230722_add_uid_to_product.rb:
--------------------------------------------------------------------------------
1 | class AddUidToProduct < ActiveRecord::Migration[6.1]
2 | def change
3 | add_column :products, :uid, :uuid
4 | end
5 | end
--------------------------------------------------------------------------------
/rails_application/db/migrate/20210626171021_add_uid_to_customer.rb:
--------------------------------------------------------------------------------
1 | class AddUidToCustomer < ActiveRecord::Migration[6.1]
2 | def change
3 | add_column :customers, :uid, :uuid
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/rails_application/db/migrate/20210626210851_migrate_from_id_to_uuid.rb:
--------------------------------------------------------------------------------
1 | class MigrateFromIdToUuid < ActiveRecord::Migration[6.1]
2 | def change
3 | change_table :products do |t|
4 | t.remove :id
5 | t.change_default :uid, "gen_random_uuid()"
6 | t.change_null :uid, false
7 | t.rename :uid, :id
8 | end
9 | execute "ALTER TABLE products ADD PRIMARY KEY (id);"
10 |
11 | change_table :customers do |t|
12 | t.remove :id
13 | t.change_default :uid, "gen_random_uuid()"
14 | t.change_null :uid, false
15 | t.rename :uid, :id
16 | end
17 | execute "ALTER TABLE customers ADD PRIMARY KEY (id);"
18 |
19 | remove_column :order_lines, :product_id
20 | add_column :order_lines, :product_id, :uuid
21 | end
22 | end
23 |
--------------------------------------------------------------------------------
/rails_application/db/migrate/20210719172643_add_discount_and_total_value_and_discount_value_to_orders.rb:
--------------------------------------------------------------------------------
1 | class AddDiscountAndTotalValueAndDiscountValueToOrders < ActiveRecord::Migration[6.1]
2 | def change
3 | add_column "orders", "percentage_discount", :decimal, precision: 8, scale: 2
4 | add_column "orders", "total_value", :decimal, precision: 8, scale: 2
5 | add_column "orders", "discounted_value", :decimal, precision: 8, scale: 2
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/rails_application/db/migrate/20210824130811_add_stock_level_to_product.rb:
--------------------------------------------------------------------------------
1 | class AddStockLevelToProduct < ActiveRecord::Migration[6.1]
2 | def change
3 | add_column :products, :stock_level, :integer
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/rails_application/db/migrate/20210830164940_drop_active_admin_comments.rb:
--------------------------------------------------------------------------------
1 | class DropActiveAdminComments < ActiveRecord::Migration[6.1]
2 | def change
3 | drop_table "active_admin_comments"
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/rails_application/db/migrate/20210907115224_string_to_native_uuids.rb:
--------------------------------------------------------------------------------
1 | class StringToNativeUuids < ActiveRecord::Migration[6.1]
2 | def change
3 | change_column :orders, :uid, :uuid, null: false, using: "uid::uuid"
4 | change_column :order_lines, :order_uid, :uuid, null: false, using: "order_uid::uuid"
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/rails_application/db/migrate/20210916191138_customers_add_registered_at.rb:
--------------------------------------------------------------------------------
1 | class CustomersAddRegisteredAt < ActiveRecord::Migration[6.1]
2 | def change
3 | add_column :customers, :registered_at, :datetime
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/rails_application/db/migrate/20210918000940_products_add_registered_at.rb:
--------------------------------------------------------------------------------
1 | class ProductsAddRegisteredAt < ActiveRecord::Migration[6.1]
2 | def change
3 | add_column :products, :registered_at, :datetime
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/rails_application/db/migrate/20210918003625_products_drop_timestamps.rb:
--------------------------------------------------------------------------------
1 | class ProductsDropTimestamps < ActiveRecord::Migration[6.1]
2 | def change
3 | remove_column :products, :created_at
4 | remove_column :products, :updated_at
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/rails_application/db/migrate/20211003170051_create_shipments.rb:
--------------------------------------------------------------------------------
1 | class CreateShipments < ActiveRecord::Migration[6.1]
2 | def change
3 | create_table :shipments do |t|
4 | t.string :order_uid, null: false
5 | t.string :address_line_1
6 | t.string :address_line_2
7 | t.string :address_line_3
8 | t.string :address_line_4
9 | end
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/rails_application/db/migrate/20211116135113_create_products_in_orders_read_model.rb:
--------------------------------------------------------------------------------
1 | class CreateProductsInOrdersReadModel < ActiveRecord::Migration[6.1]
2 | def change
3 | create_table :orders_products do |t|
4 | t.uuid :uid, null: false
5 | t.string :name
6 | t.decimal :price, precision: 8, scale: 2
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/rails_application/db/migrate/20211116154857_create_customers_in_orders_read_model.rb:
--------------------------------------------------------------------------------
1 | class CreateCustomersInOrdersReadModel < ActiveRecord::Migration[6.1]
2 | def change
3 | create_table :orders_customers do |t|
4 | t.uuid "uid", null: false
5 | t.string "name"
6 | end
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/rails_application/db/migrate/20211124220955_add_vat_rate_code_to_product.rb:
--------------------------------------------------------------------------------
1 | class AddVatRateCodeToProduct < ActiveRecord::Migration[6.1]
2 | def change
3 | add_column :products, :vat_rate_code, :string
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/rails_application/db/migrate/20220109175702_create_orders_in_invoices_read_model.rb:
--------------------------------------------------------------------------------
1 | class CreateOrdersInInvoicesReadModel < ActiveRecord::Migration[6.1]
2 | def change
3 | create_table :invoices_orders do |t|
4 | t.uuid "uid", null: false
5 | t.boolean "submitted", default: false
6 | end
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/rails_application/db/migrate/20220109175753_create_orders_in_shipments_read_model.rb:
--------------------------------------------------------------------------------
1 | class CreateOrdersInShipmentsReadModel < ActiveRecord::Migration[6.1]
2 | def change
3 | create_table :shipments_orders do |t|
4 | t.uuid "uid", null: false
5 | t.boolean "submitted", default: false
6 | end
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/rails_application/db/migrate/20220121131334_add_vip_to_customer.rb:
--------------------------------------------------------------------------------
1 | class AddVipToCustomer < ActiveRecord::Migration[6.1]
2 | def change
3 | add_column :customers, :vip, :boolean, default: false, null: false
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/rails_application/db/migrate/20220219130650_create_clients.rb:
--------------------------------------------------------------------------------
1 | class CreateClients < ActiveRecord::Migration[6.1]
2 | def change
3 | create_table :clients do |t|
4 | t.uuid :uid
5 | t.string :name
6 |
7 | t.timestamps
8 | end
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/rails_application/db/migrate/20220219162101_create_client_orders.rb:
--------------------------------------------------------------------------------
1 | class CreateClientOrders < ActiveRecord::Migration[6.1]
2 | def change
3 | create_table :client_orders do |t|
4 | t.uuid :client_uid
5 | t.string :number
6 | t.uuid :order_uid
7 | t.string :state
8 |
9 | t.timestamps
10 | end
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/rails_application/db/migrate/20220519094635_create_coupons.rb:
--------------------------------------------------------------------------------
1 | class CreateCoupons < ActiveRecord::Migration[7.0]
2 | def change
3 | create_table :coupons do |t|
4 | t.uuid "uid", null: false
5 | t.string :name
6 | t.string :code
7 | t.decimal :discount
8 |
9 | t.timestamps
10 | end
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/rails_application/db/migrate/20220528213429_create_happy_hours.rb:
--------------------------------------------------------------------------------
1 | class CreateHappyHours < ActiveRecord::Migration[7.0]
2 | def change
3 | create_table :happy_hours, id: :uuid, default: -> { "gen_random_uuid()" } do |t|
4 | t.string :name
5 | t.string :code
6 | t.integer :discount
7 | t.integer :start_hour
8 | t.integer :end_hour
9 | t.string :product_ids, array: true
10 |
11 | t.timestamps
12 | end
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/rails_application/db/migrate/20220607201447_add_happy_hour_value_to_orders.rb:
--------------------------------------------------------------------------------
1 | class AddHappyHourValueToOrders < ActiveRecord::Migration[7.0]
2 | def change
3 | add_column :orders, :happy_hour_value, :decimal, precision: 8, scale: 2
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/rails_application/db/migrate/20220703122711_drop_happy_hours.rb:
--------------------------------------------------------------------------------
1 | class DropHappyHours < ActiveRecord::Migration[7.0]
2 | def change
3 | drop_table :happy_hours, id: :uuid, default: -> { "gen_random_uuid()" } do |t|
4 | t.string :name
5 | t.string :code
6 | t.integer :discount
7 | t.integer :start_hour
8 | t.integer :end_hour
9 | t.string :product_ids, array: true
10 |
11 | t.timestamps
12 | end
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/rails_application/db/migrate/20220703123059_create_time_promotions.rb:
--------------------------------------------------------------------------------
1 | class CreateTimePromotions < ActiveRecord::Migration[7.0]
2 | def change
3 | create_table :time_promotions, id: :uuid, default: -> { "gen_random_uuid()" } do |t|
4 | t.string :label
5 | t.string :code
6 | t.integer :discount
7 | t.datetime :start_time
8 | t.datetime :end_time
9 |
10 | t.timestamps
11 | end
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/rails_application/db/migrate/20220703133325_add_active_to_time_promotions.rb:
--------------------------------------------------------------------------------
1 | class AddActiveToTimePromotions < ActiveRecord::Migration[7.0]
2 | def change
3 | add_column :time_promotions, :active, :boolean, default: false
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/rails_application/db/migrate/20220703133431_add_unique_constraint_on_code_on_time_promotions.rb:
--------------------------------------------------------------------------------
1 | class AddUniqueConstraintOnCodeOnTimePromotions < ActiveRecord::Migration[7.0]
2 | def change
3 | add_index :time_promotions, :code, unique: true
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/rails_application/db/migrate/20220723133454_remove_code_column_from_time_promotions.rb:
--------------------------------------------------------------------------------
1 | class RemoveCodeColumnFromTimePromotions < ActiveRecord::Migration[7.0]
2 | def change
3 | remove_column :time_promotions, :code, :string
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/rails_application/db/migrate/20220829155333_create_public_offer_products.rb:
--------------------------------------------------------------------------------
1 | class CreatePublicOfferProducts < ActiveRecord::Migration[7.0]
2 | def change
3 | create_table :public_offer_products, id: :uuid, default: -> { "gen_random_uuid()" } do |t|
4 | t.string :name
5 | t.decimal :price
6 | t.timestamps
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/rails_application/db/migrate/20221104111403_create_client_order_lines_read_model.rb:
--------------------------------------------------------------------------------
1 | class CreateClientOrderLinesReadModel < ActiveRecord::Migration[7.0]
2 | def up
3 | create_table :client_order_lines do |t|
4 | t.string :order_uid
5 | t.integer :product_id
6 | t.string :product_name
7 | t.integer :product_quantity
8 | t.decimal :product_price, precision: 8, scale: 2
9 | end
10 | end
11 |
12 | def down
13 | drop_table :client_order_lines
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/rails_application/db/migrate/20221104152434_create_client_order_products_read_model.rb:
--------------------------------------------------------------------------------
1 | class CreateClientOrderProductsReadModel < ActiveRecord::Migration[7.0]
2 | def change
3 | create_table :client_order_products do |t|
4 | t.uuid "uid", null: false
5 | t.string "name"
6 | t.decimal "price", precision: 8, scale: 2
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/rails_application/db/migrate/20221107085656_add_client_column_to_order.rb:
--------------------------------------------------------------------------------
1 | class AddClientColumnToOrder < ActiveRecord::Migration[7.0]
2 | def change
3 | add_column :client_orders, :client_name, :string
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/rails_application/db/migrate/20221109141019_fix_client_order_lines_product_id_column_type.rb:
--------------------------------------------------------------------------------
1 | class FixClientOrderLinesProductIdColumnType < ActiveRecord::Migration[7.0]
2 | def change
3 | remove_column :client_order_lines, :product_id, :integer
4 | add_column :client_order_lines, :product_id, :uuid
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/rails_application/db/migrate/20221109150819_add_values_to_client_order.rb:
--------------------------------------------------------------------------------
1 | class AddValuesToClientOrder < ActiveRecord::Migration[7.0]
2 | def change
3 | add_column "client_orders", "percentage_discount", :decimal, precision: 8, scale: 2
4 | add_column "client_orders", "total_value", :decimal, precision: 8, scale: 2
5 | add_column "client_orders", "discounted_value", :decimal, precision: 8, scale: 2
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/rails_application/db/migrate/20221124200914_add_prices_chart_to_product.rb:
--------------------------------------------------------------------------------
1 | class AddPricesChartToProduct < ActiveRecord::Migration[7.0]
2 | def change
3 | add_column :products, :prices_chart, :text
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/rails_application/db/migrate/20221208185429_rename_future_prices_column.rb:
--------------------------------------------------------------------------------
1 | class RenameFuturePricesColumn < ActiveRecord::Migration[7.0]
2 | def change
3 | rename_column :products, :prices_chart, :future_prices_calendar
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/rails_application/db/migrate/20221214202855_add_uniq_uid_index_to_orders_order.rb:
--------------------------------------------------------------------------------
1 | class AddUniqUidIndexToOrdersOrder < ActiveRecord::Migration[7.0]
2 | def change
3 | add_index "orders", :uid, unique: true
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/rails_application/db/migrate/20221215214459_rename_future_prices_calendar_future_prices_calendar.rb:
--------------------------------------------------------------------------------
1 | class RenameFuturePricesCalendarFuturePricesCalendar < ActiveRecord::Migration[7.0]
2 | def change
3 | rename_column :products, :future_prices_calendar, :current_prices_calendar
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/rails_application/db/migrate/20221216113438_add_order_total_value_updated_at_to_orders_table.rb:
--------------------------------------------------------------------------------
1 | class AddOrderTotalValueUpdatedAtToOrdersTable < ActiveRecord::Migration[7.0]
2 | def change
3 | add_column :orders, :total_value_updated_at, :datetime
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/rails_application/db/migrate/20221216115301_add_discount_updated_at_to_orders_table.rb:
--------------------------------------------------------------------------------
1 | class AddDiscountUpdatedAtToOrdersTable < ActiveRecord::Migration[7.0]
2 | def change
3 | add_column :orders, :discount_updated_at, :datetime
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/rails_application/db/migrate/20230110235838_add_lowest_recent_price_to_client_order_products.rb:
--------------------------------------------------------------------------------
1 | class AddLowestRecentPriceToClientOrderProducts < ActiveRecord::Migration[7.0]
2 | def up
3 | add_column :client_order_products, :lowest_recent_price, :decimal, precision: 8, scale: 2
4 |
5 | execute <<~SQL
6 | UPDATE client_order_products
7 | SET lowest_recent_price = price;
8 | SQL
9 | end
10 |
11 | def down
12 | remove_column :client_order_products, :lowest_recent_price
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/rails_application/db/migrate/20230118170447_create_availability_products.rb:
--------------------------------------------------------------------------------
1 | class CreateAvailabilityProducts < ActiveRecord::Migration[7.0]
2 | def change
3 | create_table :availability_products do |t|
4 | t.uuid :uid
5 | t.integer :available
6 | end
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/rails_application/db/migrate/20230123202350_add_paid_products_summary_to_clients.rb:
--------------------------------------------------------------------------------
1 | class AddPaidProductsSummaryToClients < ActiveRecord::Migration[7.0]
2 | def change
3 | add_column :clients, :paid_orders_summary, :decimal, precision: 8, scale: 2, default: 0
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/rails_application/db/migrate/20230124134558_add_paid_products_summary_to_customers.rb:
--------------------------------------------------------------------------------
1 | class AddPaidProductsSummaryToCustomers < ActiveRecord::Migration[7.0]
2 | def change
3 | add_column :customers, :paid_orders_summary, :decimal, precision: 8, scale: 2, default: 0
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/rails_application/db/migrate/20230124195547_remove_price_column_from_products.rb:
--------------------------------------------------------------------------------
1 | class RemovePriceColumnFromProducts < ActiveRecord::Migration[7.0]
2 | def change
3 | remove_column :products, :price
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/rails_application/db/migrate/20230126174907_add_accounting_column_to_customer.rb:
--------------------------------------------------------------------------------
1 | class AddAccountingColumnToCustomer < ActiveRecord::Migration[7.0]
2 | def change
3 | add_column :customers, :account_id, :uuid
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/rails_application/db/migrate/20230127125135_create_table_accounts.rb:
--------------------------------------------------------------------------------
1 | class CreateTableAccounts < ActiveRecord::Migration[7.0]
2 | def change
3 | create_table :table_accounts do |t|
4 | t.uuid :client_id
5 | t.text :password
6 | end
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/rails_application/db/migrate/20230128110104_move_lowest_recent_price_to_public_offer.rb:
--------------------------------------------------------------------------------
1 | class MoveLowestRecentPriceToPublicOffer < ActiveRecord::Migration[7.0]
2 | def up
3 | add_column :public_offer_products, :lowest_recent_price, :decimal, precision: 8, scale: 2
4 |
5 | execute <<~SQL
6 | UPDATE public_offer_products
7 | SET lowest_recent_price = client_order_products.lowest_recent_price
8 | FROM client_order_products
9 | WHERE client_order_products.uid = public_offer_products.id
10 | SQL
11 | end
12 |
13 | def down
14 | remove_column :public_offer_products, :lowest_recent_price
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/rails_application/db/migrate/20230130172454_rename_accounts_table.rb:
--------------------------------------------------------------------------------
1 | class RenameAccountsTable < ActiveRecord::Migration[7.0]
2 | def change
3 | rename_table :table_accounts, :accounts
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/rails_application/db/migrate/20230130172623_add_account_id_to_accounts.rb:
--------------------------------------------------------------------------------
1 | class AddAccountIdToAccounts < ActiveRecord::Migration[7.0]
2 | def change
3 | add_column :accounts, :account_id, :uuid
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/rails_application/db/migrate/20230202175228_remove_lowest_recent_price_from_client_order_product.rb:
--------------------------------------------------------------------------------
1 | class RemoveLowestRecentPriceFromClientOrderProduct < ActiveRecord::Migration[7.0]
2 | def change
3 | remove_column :client_order_products, :lowest_recent_price, :decimal, precision: 8, scale: 2
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/rails_application/db/migrate/20230906105023_add_event_id_index_to_event_store_events_in_streams.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class AddEventIdIndexToEventStoreEventsInStreams < ActiveRecord::Migration[7.0]
4 | def change
5 | return if index_exists?(:event_store_events_in_streams, :event_id)
6 |
7 | add_index :event_store_events_in_streams, [:event_id]
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/rails_application/db/migrate/20230906105317_add_foreign_key_on_event_id_to_event_store_events_in_streams.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class AddForeignKeyOnEventIdToEventStoreEventsInStreams < ActiveRecord::Migration[7.0]
4 | def change
5 | add_foreign_key :event_store_events_in_streams, :event_store_events, column: :event_id, primary_key: :event_id, if_not_exists: true, validate: false
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/rails_application/db/migrate/20230906105318_validate_add_foreign_key_on_event_id_to_event_store_events_in_streams.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class ValidateAddForeignKeyOnEventIdToEventStoreEventsInStreams < ActiveRecord::Migration[7.0]
4 | def change
5 | validate_foreign_key :event_store_events_in_streams, :event_store_events, column: :event_id, primary_key: :event_id
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/rails_application/db/migrate/20240812080357_create_available_vat_rates.rb:
--------------------------------------------------------------------------------
1 | class CreateAvailableVatRates < ActiveRecord::Migration[7.0]
2 | def change
3 | create_table :available_vat_rates do |t|
4 | t.uuid :uid, null: false
5 | t.string :code, null: false
6 | t.decimal :rate, null: false
7 |
8 | t.timestamps
9 | end
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/rails_application/db/migrate/20240826132237_add_available_to_products.rb:
--------------------------------------------------------------------------------
1 | class AddAvailableToProducts < ActiveRecord::Migration[7.2]
2 | def change
3 | add_column :products, :available, :integer
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/rails_application/db/migrate/20240827090619_add_available_to_client_order_products.rb:
--------------------------------------------------------------------------------
1 | class AddAvailableToClientOrderProducts < ActiveRecord::Migration[7.2]
2 | def change
3 | add_column :client_order_products, :available, :boolean, default: true
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/rails_application/db/migrate/20241002130622_rename_happy_hour_value_to_time_promotion_discount_value.rb:
--------------------------------------------------------------------------------
1 | class RenameHappyHourValueToTimePromotionDiscountValue < ActiveRecord::Migration[7.2]
2 | def change
3 | rename_column :orders, :happy_hour_value, :time_promotion_discount_value
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/rails_application/db/migrate/20241017115056_create_shipment_items.rb:
--------------------------------------------------------------------------------
1 | class CreateShipmentItems < ActiveRecord::Migration[7.2]
2 | def change
3 | create_table :shipment_items do |t|
4 | t.references :shipment, null: false
5 | t.string :product_name, null: false
6 | t.integer :quantity, null: false
7 | t.uuid :product_id, null: false
8 |
9 | t.timestamps
10 | end
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/rails_application/db/migrate/20241018113912_change_order_uid_to_uuid_in_shipments.rb:
--------------------------------------------------------------------------------
1 | class ChangeOrderUidToUuidInShipments < ActiveRecord::Migration[7.2]
2 | def up
3 | change_column :shipments, :order_uid, 'uuid USING order_uid::uuid', null: false
4 | end
5 |
6 | def down
7 | change_column :shipments, :order_uid, 'varchar USING order_uid::varchar', null: false
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/rails_application/db/migrate/20241129122521_add_time_promotion_discount_to_client_orders.rb:
--------------------------------------------------------------------------------
1 | class AddTimePromotionDiscountToClientOrders < ActiveRecord::Migration[7.2]
2 | def change
3 | add_column :client_orders, :time_promotion_discount, :jsonb
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/rails_application/db/migrate/20241209100544_create_refunds.rb:
--------------------------------------------------------------------------------
1 | class CreateRefunds < ActiveRecord::Migration[7.2]
2 | def change
3 | create_table :refunds do |t|
4 | t.uuid :uid, null: false
5 | t.uuid :order_uid, null: false
6 | t.string :status, null: false
7 | t.decimal :total_value, precision: 8, scale: 2, null: false
8 |
9 | t.timestamps
10 | end
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/rails_application/db/migrate/20241209102208_create_refund_items.rb:
--------------------------------------------------------------------------------
1 | class CreateRefundItems < ActiveRecord::Migration[7.2]
2 | def change
3 | create_table :refund_items do |t|
4 | t.uuid :refund_uid, null: false
5 | t.uuid :product_uid, null: false
6 | t.integer :quantity, null: false
7 | t.decimal :price, precision: 8, scale: 2, null: false
8 |
9 | t.timestamps
10 | end
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/rails_application/db/migrate/20250507182202_create_client_inbox_messages.rb:
--------------------------------------------------------------------------------
1 | class CreateClientInboxMessages < ActiveRecord::Migration[7.2]
2 | def change
3 | create_table :client_inbox_messages, id: :uuid do |t|
4 | t.uuid :client_uid, null: false, index: true
5 | t.string :title, null: false
6 | t.boolean :read, default: false, null: false
7 | t.timestamps
8 | end
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/rails_application/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: "3.2"
2 | volumes:
3 | gems:
4 | services:
5 | postgres:
6 | image: postgres:17-alpine
7 | restart: always
8 | environment:
9 | - POSTGRES_DB=cqrs-es-sample-with-res
10 | - POSTGRES_PASSWORD=secret
11 | ports:
12 | - "5432:5432"
13 |
--------------------------------------------------------------------------------
/rails_application/public/apple-touch-icon-precomposed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RailsEventStore/ecommerce/54996c6159999e29a776b766afad20400ea22dcc/rails_application/public/apple-touch-icon-precomposed.png
--------------------------------------------------------------------------------
/rails_application/public/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RailsEventStore/ecommerce/54996c6159999e29a776b766afad20400ea22dcc/rails_application/public/apple-touch-icon.png
--------------------------------------------------------------------------------
/rails_application/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RailsEventStore/ecommerce/54996c6159999e29a776b766afad20400ea22dcc/rails_application/public/favicon.ico
--------------------------------------------------------------------------------
/rails_application/public/robots.txt:
--------------------------------------------------------------------------------
1 | # See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file
2 |
--------------------------------------------------------------------------------
/rails_application/test/client_orders/order_line_test.rb:
--------------------------------------------------------------------------------
1 | require "test_helper"
2 |
3 | module ClientOrders
4 | class OrderLineTest < InMemoryTestCase
5 | cover "ClientOrders*"
6 |
7 | def test_calculates_value_correctly
8 | order_line = OrderLine.new(product_price: 10, product_quantity: 1)
9 | assert_equal 10, order_line.value
10 |
11 | order_line = OrderLine.new(product_price: 10, product_quantity: 0)
12 | assert_equal 0, order_line.value
13 |
14 | order_line = OrderLine.new(product_price: 10, product_quantity: 10)
15 | assert_equal 100, order_line.value
16 | end
17 | end
18 | end
--------------------------------------------------------------------------------
/rails_application/test/client_orders/product_registered_test.rb:
--------------------------------------------------------------------------------
1 | require "test_helper"
2 |
3 | module ClientOrders
4 | class ProductRegisteredTest < InMemoryTestCase
5 | cover "ClientOrders*"
6 |
7 | def test_reflects_change
8 | product_id = SecureRandom.uuid
9 |
10 | run_command(ProductCatalog::RegisterProduct.new(product_id: product_id))
11 |
12 | assert_equal(product_id, Product.find_by_uid(product_id).uid)
13 | end
14 |
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/rails_application/test/customers/customer_promoted_to_vip_test.rb:
--------------------------------------------------------------------------------
1 | require "test_helper"
2 |
3 | module Customers
4 | class CustomerPromotedToVipTest < InMemoryTestCase
5 | cover "Customers"
6 |
7 | def test_promote_customer_to_vip
8 | event_store = Rails.configuration.event_store
9 |
10 | customer_id = SecureRandom.uuid
11 | run_command(
12 | Crm::RegisterCustomer.new(
13 | customer_id: customer_id,
14 | name: "Joe Fake"
15 | )
16 | )
17 |
18 | event_store.publish(
19 | Crm::CustomerPromotedToVip.new(
20 | data: {
21 | customer_id: customer_id
22 | }
23 | )
24 | )
25 |
26 | customer = Customer.find_by(id: customer_id)
27 | assert_equal(customer.vip, true)
28 | end
29 | end
30 | end
31 |
--------------------------------------------------------------------------------
/rails_application/test/integration/client_inbox_test.rb:
--------------------------------------------------------------------------------
1 | require_relative "../test_helper"
2 |
3 | class ClientInboxTest < InMemoryRESIntegrationTestCase
4 | def test_customer_sees_inbox_messages
5 | customer_id = register_customer("Test Customer")
6 | login(customer_id)
7 | get "/client/inbox"
8 |
9 | assert_response :success
10 | assert_select "h1", "Your Inbox"
11 |
12 | assert_message("Welcome to our platform!", /minute ago/)
13 | end
14 |
15 | private
16 |
17 | def assert_message(title, timestamp)
18 | assert_select "h3.font-bold.text-gray-900.text-lg", title
19 | assert_select "span.text-sm.text-gray-500", timestamp
20 | assert_select "span.inline-block.h-2.w-2.rounded-full.bg-blue-600"
21 | end
22 |
23 | end
24 |
25 |
--------------------------------------------------------------------------------
/rails_application/test/integration/routing_404_test.rb:
--------------------------------------------------------------------------------
1 | require "test_helper"
2 |
3 | class MissingResourcesTest < InMemoryRESIntegrationTestCase
4 |
5 |
6 | def test_order_not_found
7 | get "/orders/123"
8 | assert_response :not_found
9 | end
10 |
11 | def test_invoice_not_found
12 | get "/invoices/123"
13 | assert_response :not_found
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/rails_application/test/orders/order_line_test.rb:
--------------------------------------------------------------------------------
1 | require "test_helper"
2 |
3 | module Orders
4 | class OrderLineTest < InMemoryTestCase
5 | cover "Orders*"
6 |
7 | def test_calculates_value_correctly
8 | order_line = OrderLine.new(price: 10, quantity: 1)
9 | assert_equal 10, order_line.value
10 |
11 | order_line = OrderLine.new(price: 10, quantity: 0)
12 | assert_equal 0, order_line.value
13 |
14 | order_line = OrderLine.new(price: 10, quantity: 10)
15 | assert_equal 100, order_line.value
16 | end
17 | end
18 | end
--------------------------------------------------------------------------------
/rails_application/test/processes/money_splitter_test.rb:
--------------------------------------------------------------------------------
1 | require "test_helper"
2 |
3 | module Processes
4 | class MoneySplitterTest < Minitest::Test
5 | cover "Processes::MoneySplitter"
6 |
7 | def test_splitting_money_without_losing_cents
8 | assert_equal([0.01, 0.01, 0.01], MoneySplitter.new(0.03, 3).call)
9 | assert_equal([0.01, 0.02], MoneySplitter.new(0.03, 2).call.sort)
10 | assert_equal([0, 0, 0.01, 0.01, 0.01], MoneySplitter.new(0.03, 5).call.sort)
11 | end
12 | end
13 | end
--------------------------------------------------------------------------------
/rails_application/test/processes/order_confirmation_test.rb:
--------------------------------------------------------------------------------
1 | require "test_helper"
2 |
3 | module Processes
4 | class OrderConfirmationTest < ProcessTest
5 | cover "Processes::OrderConfirmation"
6 |
7 | def test_payment_confirms_order
8 | process = ConfirmOrderOnPaymentCaptured.new(command_bus)
9 | given([payment_authorized]).each do |event|
10 | process.call(event)
11 | end
12 | assert_command(Fulfillment::ConfirmOrder.new(order_id: order_id))
13 | end
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/rails_application/test/products/product_test.rb:
--------------------------------------------------------------------------------
1 | require "test_helper"
2 |
3 | module Products
4 | class ProductTest < InMemoryTestCase
5 | cover "Products*"
6 |
7 | def test_unavailable
8 | product = Product.new(available: nil)
9 | refute product.unavailable?
10 |
11 | product = Product.new(available: 0)
12 | assert product.unavailable?
13 |
14 | product = Product.new(available: 1)
15 | refute product.unavailable?
16 |
17 | product = Product.new(available: -1)
18 | assert product.unavailable?
19 | end
20 | end
21 | end
22 |
--------------------------------------------------------------------------------
/rails_application/vendor/javascript/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RailsEventStore/ecommerce/54996c6159999e29a776b766afad20400ea22dcc/rails_application/vendor/javascript/.keep
--------------------------------------------------------------------------------