├── .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 | [![Build Status](https://github.com/RailsEventStore/cqrs-es-sample-with-res/workflows/crm/badge.svg)](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 | [![Build Status](https://github.com/RailsEventStore/cqrs-es-sample-with-res/workflows/inventory/badge.svg)](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 | [![Build Status](https://github.com/RailsEventStore/cqrs-es-sample-with-res/workflows/invoicing/badge.svg)](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 | [![Build Status](https://github.com/RailsEventStore/cqrs-es-sample-with-res/workflows/payments/badge.svg)](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 | [![Build Status](https://github.com/RailsEventStore/cqrs-es-sample-with-res/workflows/pricing/badge.svg)](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 | [![Build Status](https://github.com/RailsEventStore/cqrs-es-sample-with-res/workflows/product_catalog/badge.svg)](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 | [![Build Status](https://github.com/RailsEventStore/cqrs-es-sample-with-res/workflows/inventory/badge.svg)](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 | [![Build Status](https://github.com/RailsEventStore/cqrs-es-sample-with-res/workflows/taxes/badge.svg)](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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | <% @coupons.each do |coupon| %> 22 | 23 | 24 | 25 | 26 | 27 | <% end %> 28 | 29 |
NameCodeDiscount %
<%= coupon.name %><%= coupon.code %><%= coupon.discount %>
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 --------------------------------------------------------------------------------