From c684b892a7eee0f8f496e784c42c0cac018e9bed Mon Sep 17 00:00:00 2001 From: Sylvain Date: Thu, 17 Nov 2022 16:59:33 +0100 Subject: [PATCH 01/11] (bug) ES not synced during tests --- CHANGELOG.md | 2 ++ test/integration/exports/statistics_export_test.rb | 3 +++ .../reservation_subscription_statistic_service_test.rb | 6 ++++++ 3 files changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0fa0bafde..639e8ac58 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Changelog Fab-manager +- Fix a bug: some automated tests were randomly failing because ElasticSearch was not synced + ## v5.5.4 2022 November 17 - Fix a bug: unable to download an existing export of the statistics diff --git a/test/integration/exports/statistics_export_test.rb b/test/integration/exports/statistics_export_test.rb index 0b7d4da8f..222fee336 100644 --- a/test/integration/exports/statistics_export_test.rb +++ b/test/integration/exports/statistics_export_test.rb @@ -12,6 +12,7 @@ class Exports::StatisticsExportTest < ActionDispatch::IntegrationTest end test 'export machine reservations statistics to Excel' do + Stats::Machine.refresh_index! # Build the stats for the June 2015, a machine reservation should have happened at the time ::Statistics::BuilderService.generate_statistic({ start_date: '2015-06-01'.to_date.beginning_of_day, end_date: '2015-06-30'.to_date.end_of_day }) @@ -35,6 +36,7 @@ class Exports::StatisticsExportTest < ActionDispatch::IntegrationTest assert_not_nil e, 'Export was not created in database' # Run the worker + Stats::Machine.refresh_index! worker = StatisticsExportWorker.new worker.perform(e.id) @@ -69,6 +71,7 @@ class Exports::StatisticsExportTest < ActionDispatch::IntegrationTest end test 'export global statistics to Excel' do + Stats::Machine.refresh_index! # Build the stats for the June 2015 ::Statistics::BuilderService.generate_statistic({ start_date: '2015-06-01'.to_date.beginning_of_day, end_date: '2015-06-30'.to_date.end_of_day }) diff --git a/test/services/statistics/reservation_subscription_statistic_service_test.rb b/test/services/statistics/reservation_subscription_statistic_service_test.rb index 7a0e7bcac..e998f3a19 100644 --- a/test/services/statistics/reservation_subscription_statistic_service_test.rb +++ b/test/services/statistics/reservation_subscription_statistic_service_test.rb @@ -91,6 +91,10 @@ class ReservationSubscriptionStatisticServiceTest < ActionDispatch::IntegrationT ] }.to_json, headers: default_headers + Stats::Machine.refresh_index! + Stats::Training.refresh_index! + Stats::Subscription.refresh_index! + # Build the stats for the last 3 days, we expect the above invoices (reservations+subscription) to appear in the resulting stats ::Statistics::BuilderService.generate_statistic({ start_date: 2.days.ago.beginning_of_day, end_date: DateTime.current.end_of_day }) @@ -126,6 +130,8 @@ class ReservationSubscriptionStatisticServiceTest < ActionDispatch::IntegrationT check_statistics_on_user(stat_hour) # training + Stats::Training.refresh_index! + stat_training = Stats::Training.search(query: { bool: { must: [{ term: { date: 1.day.ago.to_date.iso8601 } }, { term: { type: 'booking' } }] } }).first assert_not_nil stat_training From 451cd0e915ab173547bdc6fb5bc53f5d140de184 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Thu, 17 Nov 2022 17:14:31 +0100 Subject: [PATCH 02/11] (test) add admin offer product to himself Also: split AdminPayOrderTest in two files --- app/services/cart/set_offer_service.rb | 3 + .../store/admin_order_for_himself_test.rb | 209 ++++++++++++++++++ .../integration/store/admin_pay_order_test.rb | 180 --------------- ..._admin_pay_by_card_and_wallet_success.yml} | 0 ...store_order_admin_pay_by_card_success.yml} | 0 5 files changed, 212 insertions(+), 180 deletions(-) create mode 100644 test/integration/store/admin_order_for_himself_test.rb rename test/vcr_cassettes/{store_order_admin_pay_by_cart_and_wallet_success.yml => store_order_admin_pay_by_card_and_wallet_success.yml} (100%) rename test/vcr_cassettes/{store_order_admin_pay_by_cart_success.yml => store_order_admin_pay_by_card_success.yml} (100%) diff --git a/app/services/cart/set_offer_service.rb b/app/services/cart/set_offer_service.rb index d9bd11b1a..18a18c706 100644 --- a/app/services/cart/set_offer_service.rb +++ b/app/services/cart/set_offer_service.rb @@ -1,5 +1,8 @@ # frozen_string_literal: true +# module definition +module Cart; end + # Provides methods for set offer to item in cart class Cart::SetOfferService def call(order, orderable, is_offered) diff --git a/test/integration/store/admin_order_for_himself_test.rb b/test/integration/store/admin_order_for_himself_test.rb new file mode 100644 index 000000000..fdb9223de --- /dev/null +++ b/test/integration/store/admin_order_for_himself_test.rb @@ -0,0 +1,209 @@ +# frozen_string_literal: true + +require 'test_helper' + +module Store; end + +class Store::AdminOrderForHimselfTest < ActionDispatch::IntegrationTest + setup do + @admin = User.find_by(username: 'admin') + @pjproudhon = User.find_by(username: 'pjproudhon') + @caisse_en_bois = Product.find_by(slug: 'caisse-en-bois') + @panneaux = Product.find_by(slug: 'panneaux-de-mdf') + @cart1 = Order.find_by(token: '0DKxbAOzSXRx-amXyhmDdg1666691976019') + end + + test 'admin pay himself order by card with success' do + login_as(@admin, scope: :user) + + invoice_count = Invoice.count + invoice_items_count = InvoiceItem.count + + VCR.use_cassette('store_order_admin_pay_by_card_success') do + post '/api/checkout/payment', + params: { + payment_id: stripe_payment_method, + order_token: @cart1.token, + customer_id: @admin.id + }.to_json, headers: default_headers + end + + @cart1.reload + + # general assertions + assert_equal 200, response.status + assert_equal invoice_count + 1, Invoice.count + assert_equal invoice_items_count + 2, InvoiceItem.count + + # invoice_items assertions + invoice_item = InvoiceItem.last + + assert invoice_item.check_footprint + + # invoice assertions + invoice = Invoice.last + assert_invoice_pdf invoice + assert_not_nil invoice.debug_footprint + + assert_not @cart1.payment_gateway_object.blank? + assert_not invoice.payment_gateway_object.blank? + assert_not invoice.total.blank? + assert invoice.check_footprint + + # notification + assert_not_empty Notification.where(attached_object: invoice) + + assert_equal @cart1.state, 'paid' + assert_equal @cart1.payment_method, 'card' + assert_equal @cart1.paid_total, 262_500 + + stock_movement = @caisse_en_bois.product_stock_movements.last + assert_equal stock_movement.stock_type, 'external' + assert_equal stock_movement.reason, 'sold' + assert_equal stock_movement.quantity, -5 + assert_equal stock_movement.order_item_id, @cart1.order_items.first.id + + stock_movement = @panneaux.product_stock_movements.last + assert_equal stock_movement.stock_type, 'external' + assert_equal stock_movement.reason, 'sold' + assert_equal stock_movement.quantity, -2 + assert_equal stock_movement.order_item_id, @cart1.order_items.last.id + + activity = @cart1.order_activities.last + assert_equal activity.activity_type, 'paid' + assert_equal activity.operator_profile_id, @admin.invoicing_profile.id + end + + test 'admin pay himself order by card and wallet with success' do + login_as(@admin, scope: :user) + + service = WalletService.new(user: @admin, wallet: @admin.wallet) + service.credit(1000) + + invoice_count = Invoice.count + invoice_items_count = InvoiceItem.count + users_credit_count = UsersCredit.count + wallet_transactions_count = WalletTransaction.count + + VCR.use_cassette('store_order_admin_pay_by_cart_and_wallet_success') do + post '/api/checkout/payment', + params: { + payment_id: stripe_payment_method, + order_token: @cart1.token, + customer_id: @admin.id + }.to_json, headers: default_headers + end + + @admin.wallet.reload + @cart1.reload + + # general assertions + assert_equal 200, response.status + assert_equal invoice_count + 1, Invoice.count + assert_equal invoice_items_count + 2, InvoiceItem.count + + # invoice_items assertions + invoice_item = InvoiceItem.last + + assert invoice_item.check_footprint + + # invoice assertions + invoice = Invoice.last + assert_invoice_pdf invoice + assert_not_nil invoice.debug_footprint + + assert_not @cart1.payment_gateway_object.blank? + assert_not invoice.payment_gateway_object.blank? + assert_not invoice.total.blank? + assert invoice.check_footprint + + # notification + assert_not_empty Notification.where(attached_object: invoice) + + assert_equal @cart1.state, 'paid' + assert_equal @cart1.payment_method, 'card' + assert_equal @cart1.paid_total, 162_500 + assert_equal users_credit_count, UsersCredit.count + assert_equal wallet_transactions_count + 1, WalletTransaction.count + + # wallet + assert_equal 0, @admin.wallet.amount + assert_equal 2, @admin.wallet.wallet_transactions.count + transaction = @admin.wallet.wallet_transactions.last + assert_equal 'debit', transaction.transaction_type + assert_equal @cart1.wallet_amount / 100.0, transaction.amount + assert_equal @cart1.wallet_transaction_id, transaction.id + assert_equal invoice.wallet_amount / 100.0, transaction.amount + end + + test 'admin pay himself order by wallet with success' do + login_as(@admin, scope: :user) + + service = WalletService.new(user: @admin, wallet: @admin.wallet) + service.credit(@cart1.total / 100) + + invoice_count = Invoice.count + invoice_items_count = InvoiceItem.count + users_credit_count = UsersCredit.count + wallet_transactions_count = WalletTransaction.count + + post '/api/checkout/payment', + params: { + order_token: @cart1.token, + customer_id: @admin.id + }.to_json, headers: default_headers + + @admin.wallet.reload + @cart1.reload + + # general assertions + assert_equal 200, response.status + assert_equal @cart1.state, 'paid' + assert_equal invoice_count + 1, Invoice.count + assert_equal invoice_items_count + 2, InvoiceItem.count + assert_equal users_credit_count, UsersCredit.count + assert_equal wallet_transactions_count + 1, WalletTransaction.count + + # invoice_items assertions + invoice_item = InvoiceItem.last + + assert invoice_item.check_footprint + + # invoice assertions + invoice = Invoice.last + assert_invoice_pdf invoice + assert_not_nil invoice.debug_footprint + + assert invoice.payment_gateway_object.blank? + assert_not invoice.total.blank? + assert invoice.check_footprint + + # notification + assert_not_empty Notification.where(attached_object: invoice) + + # wallet + assert_equal 0, @admin.wallet.amount + assert_equal 2, @admin.wallet.wallet_transactions.count + transaction = @admin.wallet.wallet_transactions.last + assert_equal 'debit', transaction.transaction_type + assert_equal @cart1.paid_total, 0 + assert_equal @cart1.wallet_amount / 100.0, transaction.amount + assert_equal @cart1.payment_method, 'wallet' + assert_equal @cart1.wallet_transaction_id, transaction.id + assert_equal invoice.wallet_amount / 100.0, transaction.amount + end + + test 'admin cannot offer products to himself' do + login_as(@admin, scope: :user) + + put '/api/cart/set_offer', + params: { + order_token: @cart1.token, + customer_id: @admin.id, + is_offered: true, + orderable_id: @caisse_en_bois.id + }.to_json, headers: default_headers + + assert_equal 403, response.status + end +end diff --git a/test/integration/store/admin_pay_order_test.rb b/test/integration/store/admin_pay_order_test.rb index 3b303f112..ec24538a0 100644 --- a/test/integration/store/admin_pay_order_test.rb +++ b/test/integration/store/admin_pay_order_test.rb @@ -13,129 +13,6 @@ class Store::AdminPayOrderTest < ActionDispatch::IntegrationTest @cart1 = Order.find_by(token: '0DKxbAOzSXRx-amXyhmDdg1666691976019') end - test 'admin pay himself order by cart with success' do - login_as(@admin, scope: :user) - - invoice_count = Invoice.count - invoice_items_count = InvoiceItem.count - - VCR.use_cassette('store_order_admin_pay_by_cart_success') do - post '/api/checkout/payment', - params: { - payment_id: stripe_payment_method, - order_token: @cart1.token, - customer_id: @admin.id - }.to_json, headers: default_headers - end - - @cart1.reload - - # general assertions - assert_equal 200, response.status - assert_equal invoice_count + 1, Invoice.count - assert_equal invoice_items_count + 2, InvoiceItem.count - - # invoice_items assertions - invoice_item = InvoiceItem.last - - assert invoice_item.check_footprint - - # invoice assertions - invoice = Invoice.last - assert_invoice_pdf invoice - assert_not_nil invoice.debug_footprint - - assert_not @cart1.payment_gateway_object.blank? - assert_not invoice.payment_gateway_object.blank? - assert_not invoice.total.blank? - assert invoice.check_footprint - - # notification - assert_not_empty Notification.where(attached_object: invoice) - - assert_equal @cart1.state, 'paid' - assert_equal @cart1.payment_method, 'card' - assert_equal @cart1.paid_total, 262_500 - - stock_movement = @caisse_en_bois.product_stock_movements.last - assert_equal stock_movement.stock_type, 'external' - assert_equal stock_movement.reason, 'sold' - assert_equal stock_movement.quantity, -5 - assert_equal stock_movement.order_item_id, @cart1.order_items.first.id - - stock_movement = @panneaux.product_stock_movements.last - assert_equal stock_movement.stock_type, 'external' - assert_equal stock_movement.reason, 'sold' - assert_equal stock_movement.quantity, -2 - assert_equal stock_movement.order_item_id, @cart1.order_items.last.id - - activity = @cart1.order_activities.last - assert_equal activity.activity_type, 'paid' - assert_equal activity.operator_profile_id, @admin.invoicing_profile.id - end - - test 'admin pay himself order by cart and wallet with success' do - login_as(@admin, scope: :user) - - service = WalletService.new(user: @admin, wallet: @admin.wallet) - service.credit(1000) - - invoice_count = Invoice.count - invoice_items_count = InvoiceItem.count - users_credit_count = UsersCredit.count - wallet_transactions_count = WalletTransaction.count - - VCR.use_cassette('store_order_admin_pay_by_cart_and_wallet_success') do - post '/api/checkout/payment', - params: { - payment_id: stripe_payment_method, - order_token: @cart1.token, - customer_id: @admin.id - }.to_json, headers: default_headers - end - - @admin.wallet.reload - @cart1.reload - - # general assertions - assert_equal 200, response.status - assert_equal invoice_count + 1, Invoice.count - assert_equal invoice_items_count + 2, InvoiceItem.count - - # invoice_items assertions - invoice_item = InvoiceItem.last - - assert invoice_item.check_footprint - - # invoice assertions - invoice = Invoice.last - assert_invoice_pdf invoice - assert_not_nil invoice.debug_footprint - - assert_not @cart1.payment_gateway_object.blank? - assert_not invoice.payment_gateway_object.blank? - assert_not invoice.total.blank? - assert invoice.check_footprint - - # notification - assert_not_empty Notification.where(attached_object: invoice) - - assert_equal @cart1.state, 'paid' - assert_equal @cart1.payment_method, 'card' - assert_equal @cart1.paid_total, 162_500 - assert_equal users_credit_count, UsersCredit.count - assert_equal wallet_transactions_count + 1, WalletTransaction.count - - # wallet - assert_equal 0, @admin.wallet.amount - assert_equal 2, @admin.wallet.wallet_transactions.count - transaction = @admin.wallet.wallet_transactions.last - assert_equal 'debit', transaction.transaction_type - assert_equal @cart1.wallet_amount / 100.0, transaction.amount - assert_equal @cart1.wallet_transaction_id, transaction.id - assert_equal invoice.wallet_amount / 100.0, transaction.amount - end - test 'admin pay user order by local with success' do login_as(@admin, scope: :user) @@ -254,63 +131,6 @@ class Store::AdminPayOrderTest < ActionDispatch::IntegrationTest assert_equal activity.operator_profile_id, @admin.invoicing_profile.id end - test 'admin pay himself order by wallet with success' do - login_as(@admin, scope: :user) - - service = WalletService.new(user: @admin, wallet: @admin.wallet) - service.credit(@cart1.total / 100) - - invoice_count = Invoice.count - invoice_items_count = InvoiceItem.count - users_credit_count = UsersCredit.count - wallet_transactions_count = WalletTransaction.count - - post '/api/checkout/payment', - params: { - order_token: @cart1.token, - customer_id: @admin.id - }.to_json, headers: default_headers - - @admin.wallet.reload - @cart1.reload - - # general assertions - assert_equal 200, response.status - assert_equal @cart1.state, 'paid' - assert_equal invoice_count + 1, Invoice.count - assert_equal invoice_items_count + 2, InvoiceItem.count - assert_equal users_credit_count, UsersCredit.count - assert_equal wallet_transactions_count + 1, WalletTransaction.count - - # invoice_items assertions - invoice_item = InvoiceItem.last - - assert invoice_item.check_footprint - - # invoice assertions - invoice = Invoice.last - assert_invoice_pdf invoice - assert_not_nil invoice.debug_footprint - - assert invoice.payment_gateway_object.blank? - assert_not invoice.total.blank? - assert invoice.check_footprint - - # notification - assert_not_empty Notification.where(attached_object: invoice) - - # wallet - assert_equal 0, @admin.wallet.amount - assert_equal 2, @admin.wallet.wallet_transactions.count - transaction = @admin.wallet.wallet_transactions.last - assert_equal 'debit', transaction.transaction_type - assert_equal @cart1.paid_total, 0 - assert_equal @cart1.wallet_amount / 100.0, transaction.amount - assert_equal @cart1.payment_method, 'wallet' - assert_equal @cart1.wallet_transaction_id, transaction.id - assert_equal invoice.wallet_amount / 100.0, transaction.amount - end - test 'admin pay user order by wallet with success' do login_as(@admin, scope: :user) diff --git a/test/vcr_cassettes/store_order_admin_pay_by_cart_and_wallet_success.yml b/test/vcr_cassettes/store_order_admin_pay_by_card_and_wallet_success.yml similarity index 100% rename from test/vcr_cassettes/store_order_admin_pay_by_cart_and_wallet_success.yml rename to test/vcr_cassettes/store_order_admin_pay_by_card_and_wallet_success.yml diff --git a/test/vcr_cassettes/store_order_admin_pay_by_cart_success.yml b/test/vcr_cassettes/store_order_admin_pay_by_card_success.yml similarity index 100% rename from test/vcr_cassettes/store_order_admin_pay_by_cart_success.yml rename to test/vcr_cassettes/store_order_admin_pay_by_card_success.yml From b2540a1cd4be5340ea83374d6efcfbe378a87580 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Mon, 21 Nov 2022 15:22:23 +0100 Subject: [PATCH 03/11] (bug) broken display of machines pages --- CHANGELOG.md | 1 + .../src/javascript/components/machines/machine-card.tsx | 4 ++-- app/frontend/src/javascript/models/machine.ts | 9 ++++++--- app/views/api/machines/index.json.jbuilder | 4 +++- app/views/api/machines/show.json.jbuilder | 6 +++--- 5 files changed, 15 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 639e8ac58..0deb6f449 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Changelog Fab-manager +- Fix a bug: broken display of machines pages - Fix a bug: some automated tests were randomly failing because ElasticSearch was not synced ## v5.5.4 2022 November 17 diff --git a/app/frontend/src/javascript/components/machines/machine-card.tsx b/app/frontend/src/javascript/components/machines/machine-card.tsx index 5e6135aa7..37b2b9160 100644 --- a/app/frontend/src/javascript/components/machines/machine-card.tsx +++ b/app/frontend/src/javascript/components/machines/machine-card.tsx @@ -44,12 +44,12 @@ const MachineCard: React.FC = ({ user, machine, onShowMachine, * Return the machine's picture or a placeholder */ const machinePicture = (): ReactNode => { - if (!machine.machine_image_attributes) { + if (!machine.machine_image) { return
; } return ( -
+
); }; diff --git a/app/frontend/src/javascript/models/machine.ts b/app/frontend/src/javascript/models/machine.ts index 43b1a143a..da506c0d7 100644 --- a/app/frontend/src/javascript/models/machine.ts +++ b/app/frontend/src/javascript/models/machine.ts @@ -1,6 +1,5 @@ import { Reservation } from './reservation'; import { ApiFilter } from './api'; -import { FileType } from './file'; export interface MachineIndexFilter extends ApiFilter { disabled: boolean, @@ -13,8 +12,12 @@ export interface Machine { spec?: string, disabled: boolean, slug: string, - machine_image_attributes: FileType, - machine_files_attributes?: Array, + machine_image: string, + machine_files_attributes?: Array<{ + id: number, + attachment: string, + attachment_url: string + }>, trainings?: Array<{ id: number, name: string, diff --git a/app/views/api/machines/index.json.jbuilder b/app/views/api/machines/index.json.jbuilder index e86436e0b..b9e8f5b76 100644 --- a/app/views/api/machines/index.json.jbuilder +++ b/app/views/api/machines/index.json.jbuilder @@ -1,5 +1,7 @@ # frozen_string_literal: true json.array!(@machines) do |machine| - json.partial! 'api/machines/machine', machine: machine + json.extract! machine, :id, :name, :slug, :disabled + + json.machine_image machine.machine_image.attachment.medium.url if machine.machine_image end diff --git a/app/views/api/machines/show.json.jbuilder b/app/views/api/machines/show.json.jbuilder index 099a136f3..2b271f395 100644 --- a/app/views/api/machines/show.json.jbuilder +++ b/app/views/api/machines/show.json.jbuilder @@ -1,11 +1,11 @@ # frozen_string_literal: true -json.partial! 'api/machines/machine', machine: @machine -json.extract! @machine, :description, :spec +json.extract! @machine, :id, :name, :description, :spec, :disabled, :slug +json.machine_image @machine.machine_image.attachment.large.url if @machine.machine_image json.machine_files_attributes @machine.machine_files do |f| json.id f.id - json.attachment_name f.attachment_identifier + json.attachment f.attachment_identifier json.attachment_url f.attachment_url end json.trainings @machine.trainings.each, :id, :name, :disabled From 16087d3aa3fdff0fe66c869316eccc3bbf00a671 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Mon, 21 Nov 2022 16:56:28 +0100 Subject: [PATCH 04/11] (bug) objects not sync on Stripe --- CHANGELOG.md | 1 + app/services/setting_service.rb | 2 +- app/workers/sync_objects_on_stripe_worker.rb | 40 ++++++++++++++------ 3 files changed, 30 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0deb6f449..320ead133 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ - Fix a bug: broken display of machines pages - Fix a bug: some automated tests were randomly failing because ElasticSearch was not synced +- Fix a bug: payment related objects are not synced on Stripe when enabling the online payment module ## v5.5.4 2022 November 17 diff --git a/app/services/setting_service.rb b/app/services/setting_service.rb index e8b1ba092..c90048ac6 100644 --- a/app/services/setting_service.rb +++ b/app/services/setting_service.rb @@ -46,7 +46,7 @@ class SettingService # sync all objects on stripe def sync_stripe_objects(setting) - return unless setting.name == 'stripe_secret_key' + return unless %w[stripe_secret_key online_payment_module].include?(setting.name) SyncObjectsOnStripeWorker.perform_async(setting.history_values.last&.invoicing_profile&.user&.id) end diff --git a/app/workers/sync_objects_on_stripe_worker.rb b/app/workers/sync_objects_on_stripe_worker.rb index 62cf8826b..a1950a8b4 100644 --- a/app/workers/sync_objects_on_stripe_worker.rb +++ b/app/workers/sync_objects_on_stripe_worker.rb @@ -7,22 +7,45 @@ class SyncObjectsOnStripeWorker sidekiq_options lock: :until_executed, on_conflict: :reject, queue: :stripe def perform(notify_user_id = nil) + unless Stripe::Helper.enabled? + Rails.logger.warn('A request to sync payment related objects on Stripe will not run because Stripe is not enabled') + return false + end + + w = StripeWorker.new + sync_customers(w) + sync_coupons + sync_objects(w) + + Rails.logger.info 'Sync is done' + return unless notify_user_id + + Rails.logger.info "Notify user #{notify_user_id}" + user = User.find(notify_user_id) + NotificationCenter.call type: :notify_admin_objects_stripe_sync, + receiver: user, + attached_object: user + end + + def sync_customers(worker) Rails.logger.info 'We create all non-existing customers on stripe. This may take a while...' total = User.online_payers.count User.online_payers.each_with_index do |member, index| Rails.logger.info "#{index} / #{total}" begin stp_customer = member.payment_gateway_object&.gateway_object&.retrieve - StripeWorker.new.create_stripe_customer(member.id) if stp_customer.nil? || stp_customer[:deleted] + worker.perform(:create_stripe_customer, member.id) if stp_customer.nil? || stp_customer[:deleted] rescue Stripe::InvalidRequestError begin - StripeWorker.new.create_stripe_customer(member.id) + worker.perform(:create_stripe_customer, member.id) rescue Stripe::InvalidRequestError => e Rails.logger.error "Unable to create the customer #{member.id} do to a Stripe error: #{e}" end end end + end + def sync_coupons Rails.logger.info 'We create all non-existing coupons on stripe. This may take a while...' total = Coupon.all.count Coupon.all.each_with_index do |coupon, index| @@ -35,23 +58,16 @@ class SyncObjectsOnStripeWorker Rails.logger.error "Unable to create coupon #{coupon.code} on stripe: #{e}" end end + end - w = StripeWorker.new + def sync_objects(worker) [Machine, Training, Space, Plan].each do |klass| Rails.logger.info "We create all non-existing #{klass} on stripe. This may take a while..." total = klass.all.count klass.all.each_with_index do |item, index| Rails.logger.info "#{index} / #{total}" - w.perform(:create_or_update_stp_product, klass.name, item.id) + worker.perform(:create_or_update_stp_product, klass.name, item.id) end end - Rails.logger.info 'Sync is done' - return unless notify_user_id - - Rails.logger.info "Notify user #{notify_user_id}" - user = User.find(notify_user_id) - NotificationCenter.call type: :notify_admin_objects_stripe_sync, - receiver: user, - attached_object: user end end From 73b8f95031565d499f7e9af9e5e7b441693efa58 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Tue, 22 Nov 2022 09:50:04 +0100 Subject: [PATCH 05/11] (doc) known OIDC issues --- doc/sso_open_id_connect.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/doc/sso_open_id_connect.md b/doc/sso_open_id_connect.md index e44c3893e..a0443ebc5 100644 --- a/doc/sso_open_id_connect.md +++ b/doc/sso_open_id_connect.md @@ -21,3 +21,12 @@ to the corresponding OpenID Connect claims: - profile.address To use the automatic mapping, add one of the fields above and click on the magic wand button near to the "Userinfo claim" input. + +## Known issues + +``` +Not found. Authentication passthru. +``` +This issue may occur if you have misconfigured the environment variable `DEFAULT_HOST` and/or `DEFAULT_PROTOCOL`. +Especially, if you have an automatic redirection (e.g. from example.org to example.com), `DEFAULT_HOST` *MUST* be configured with the redirection target (here example.com). +Once you have reconfigured these variables, please switch back the active authentication provider to FabManager, restart the application, then delete the OIDC provider you configured and re-create a new one for the new settings to be used. From 03f3819255e584fed867856229e2e38ad85eedd0 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Tue, 22 Nov 2022 09:52:22 +0100 Subject: [PATCH 06/11] (bug) upgrade: the input device is not a TTY --- CHANGELOG.md | 1 + setup/upgrade.sh | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 320ead133..cd1f52d77 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Changelog Fab-manager +- Fix a bug: in upgrade script, the error "the input device is not a TTY" is thrown when migrating the database - Fix a bug: broken display of machines pages - Fix a bug: some automated tests were randomly failing because ElasticSearch was not synced - Fix a bug: payment related objects are not synced on Stripe when enabling the online payment module diff --git a/setup/upgrade.sh b/setup/upgrade.sh index c8c62c3dd..143b8ab46 100644 --- a/setup/upgrade.sh +++ b/setup/upgrade.sh @@ -296,7 +296,7 @@ upgrade() fi done compile_assets - if ! docker-compose run --rm "$SERVICE" bundle exec rake db:migrate; then + if ! docker-compose run --rm "$SERVICE" bundle exec rake db:migrate Date: Tue, 22 Nov 2022 14:17:25 +0100 Subject: [PATCH 07/11] (feat) soft destroy machines/spaces --- CHANGELOG.md | 1 + app/controllers/api/machines_controller.rb | 8 +++++++- app/controllers/api/spaces_controller.rb | 10 ++++++++-- app/models/machine.rb | 4 ++++ app/models/space.rb | 4 ++++ app/policies/machine_policy.rb | 2 +- app/policies/space_policy.rb | 5 ++++- app/services/machine_service.rb | 3 +++ .../20221122123557_add_deleted_at_to_machine.rb | 12 ++++++++++++ db/migrate/20221122123605_add_deleted_at_to_space.rb | 12 ++++++++++++ db/schema.rb | 6 +++++- 11 files changed, 61 insertions(+), 6 deletions(-) create mode 100644 db/migrate/20221122123557_add_deleted_at_to_machine.rb create mode 100644 db/migrate/20221122123605_add_deleted_at_to_space.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index cd1f52d77..f06621ca1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Changelog Fab-manager +- Soft destroy of spaces and machines - Fix a bug: in upgrade script, the error "the input device is not a TTY" is thrown when migrating the database - Fix a bug: broken display of machines pages - Fix a bug: some automated tests were randomly failing because ElasticSearch was not synced diff --git a/app/controllers/api/machines_controller.rb b/app/controllers/api/machines_controller.rb index a6baebc10..1879bd183 100644 --- a/app/controllers/api/machines_controller.rb +++ b/app/controllers/api/machines_controller.rb @@ -12,6 +12,8 @@ class API::MachinesController < API::ApiController def show @machine = Machine.includes(:machine_files, :projects).friendly.find(params[:id]) + + head :not_found if @machine.deleted_at end def create @@ -35,7 +37,11 @@ class API::MachinesController < API::ApiController def destroy authorize @machine - @machine.destroy + if @machine.destroyable? + @machine.destroy + else + @machine.soft_destroy! + end head :no_content end diff --git a/app/controllers/api/spaces_controller.rb b/app/controllers/api/spaces_controller.rb index 18a02cac5..75390c85d 100644 --- a/app/controllers/api/spaces_controller.rb +++ b/app/controllers/api/spaces_controller.rb @@ -6,11 +6,13 @@ class API::SpacesController < API::ApiController respond_to :json def index - @spaces = Space.includes(:space_image) + @spaces = Space.includes(:space_image).where(deleted_at: nil) end def show @space = Space.includes(:space_files, :projects).friendly.find(params[:id]) + + head :not_found if @space.deleted_at end def create @@ -36,7 +38,11 @@ class API::SpacesController < API::ApiController def destroy @space = get_space authorize @space - @space.destroy + if @space.destroyable? + @space.destroy + else + @space.soft_destroy! + end head :no_content end diff --git a/app/models/machine.rb b/app/models/machine.rb index 661f37256..0abd5e1c3 100644 --- a/app/models/machine.rb +++ b/app/models/machine.rb @@ -82,6 +82,10 @@ class Machine < ApplicationRecord reservations.empty? end + def soft_destroy! + update(deleted_at: DateTime.current) + end + def packs?(user) prepaid_packs.where(group_id: user.group_id) .where(disabled: [false, nil]) diff --git a/app/models/space.rb b/app/models/space.rb index 228c5fd5f..934b51754 100644 --- a/app/models/space.rb +++ b/app/models/space.rb @@ -66,6 +66,10 @@ class Space < ApplicationRecord reservations.empty? end + def soft_destroy! + update(deleted_at: DateTime.current) + end + private def update_gateway_product diff --git a/app/policies/machine_policy.rb b/app/policies/machine_policy.rb index 069da4f45..5a1ac8481 100644 --- a/app/policies/machine_policy.rb +++ b/app/policies/machine_policy.rb @@ -11,6 +11,6 @@ class MachinePolicy < ApplicationPolicy end def destroy? - user.admin? and record.destroyable? + user.admin? end end diff --git a/app/policies/space_policy.rb b/app/policies/space_policy.rb index d4174200d..6c69c0faa 100644 --- a/app/policies/space_policy.rb +++ b/app/policies/space_policy.rb @@ -1,3 +1,6 @@ +# frozen_string_literal: true + +# Check the access policies for API::SpacesController class SpacePolicy < ApplicationPolicy def create? user.admin? @@ -8,6 +11,6 @@ class SpacePolicy < ApplicationPolicy end def destroy? - user.admin? and record.destroyable? + user.admin? end end diff --git a/app/services/machine_service.rb b/app/services/machine_service.rb index 02f18c4f1..bc5720c8d 100644 --- a/app/services/machine_service.rb +++ b/app/services/machine_service.rb @@ -9,6 +9,9 @@ class MachineService else Machine.includes(:machine_image, :plans).order(sort_by) end + # do not include soft destroyed + machines = machines.where(deleted_at: nil) + if filters[:disabled].present? state = filters[:disabled] == 'false' ? [nil, false] : true machines = machines.where(disabled: state) diff --git a/db/migrate/20221122123557_add_deleted_at_to_machine.rb b/db/migrate/20221122123557_add_deleted_at_to_machine.rb new file mode 100644 index 000000000..23107b653 --- /dev/null +++ b/db/migrate/20221122123557_add_deleted_at_to_machine.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +# Allow soft destroy of machines. +# Machines with existing reservation cannot be destroyed because we need them for rebuilding invoices, statistics, etc. +# This attribute allows to make a "soft destroy" of a Machine, marking it as destroyed so it doesn't appear anymore in +# the interface (as if it was destroyed) but still lives in the database so we can use it to build data. +class AddDeletedAtToMachine < ActiveRecord::Migration[5.2] + def change + add_column :machines, :deleted_at, :datetime + add_index :machines, :deleted_at + end +end diff --git a/db/migrate/20221122123605_add_deleted_at_to_space.rb b/db/migrate/20221122123605_add_deleted_at_to_space.rb new file mode 100644 index 000000000..f5f13b0aa --- /dev/null +++ b/db/migrate/20221122123605_add_deleted_at_to_space.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +# Allow soft destroy of spaces. +# Spaces with existing reservation cannot be destroyed because we need them for rebuilding invoices, statistics, etc. +# This attribute allows to make a "soft destroy" of a Space, marking it as destroyed so it doesn't appear anymore in +# the interface (as if it was destroyed) but still lives in the database so we can use it to build data. +class AddDeletedAtToSpace < ActiveRecord::Migration[5.2] + def change + add_column :spaces, :deleted_at, :datetime + add_index :spaces, :deleted_at + end +end diff --git a/db/schema.rb b/db/schema.rb index 9e6a688de..effaba6fc 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2022_10_03_133019) do +ActiveRecord::Schema.define(version: 2022_11_22_123605) do # These are extensions that must be enabled in order to support this database enable_extension "fuzzystrmatch" @@ -358,6 +358,8 @@ ActiveRecord::Schema.define(version: 2022_10_03_133019) do t.datetime "updated_at" t.string "slug" t.boolean "disabled" + t.datetime "deleted_at" + t.index ["deleted_at"], name: "index_machines_on_deleted_at" t.index ["slug"], name: "index_machines_on_slug", unique: true end @@ -878,6 +880,8 @@ ActiveRecord::Schema.define(version: 2022_10_03_133019) do t.datetime "updated_at", null: false t.text "characteristics" t.boolean "disabled" + t.datetime "deleted_at" + t.index ["deleted_at"], name: "index_spaces_on_deleted_at" end create_table "spaces_availabilities", id: :serial, force: :cascade do |t| From fe0af02ba8dbee27df4dd483fa30fd3c841cf161 Mon Sep 17 00:00:00 2001 From: Du Peng Date: Tue, 22 Nov 2022 16:31:19 +0100 Subject: [PATCH 08/11] (bug) unable set a main image of product and remove an image of product --- CHANGELOG.md | 1 + .../components/form/form-image-upload.tsx | 11 +++++---- .../form/form-multi-file-upload.tsx | 24 +++++++++++++++---- .../form/form-multi-image-upload.tsx | 18 ++++++-------- app/models/product.rb | 2 +- app/views/api/products/_product.json.jbuilder | 4 ++-- 6 files changed, 36 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f06621ca1..77abd4585 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - Fix a bug: broken display of machines pages - Fix a bug: some automated tests were randomly failing because ElasticSearch was not synced - Fix a bug: payment related objects are not synced on Stripe when enabling the online payment module +- Fix a bug: unable set a main image of product and remove an image of product ## v5.5.4 2022 November 17 diff --git a/app/frontend/src/javascript/components/form/form-image-upload.tsx b/app/frontend/src/javascript/components/form/form-image-upload.tsx index 10c0db07b..fd90e1043 100644 --- a/app/frontend/src/javascript/components/form/form-image-upload.tsx +++ b/app/frontend/src/javascript/components/form/form-image-upload.tsx @@ -60,11 +60,12 @@ export const FormImageUpload = , - { - attachment_name: f.name, - _destroy: false - } as UnpackNestedValue>> + `${id}[attachment_name]` as Path, + f.name as UnpackNestedValue>> + ); + setValue( + `${id}[_destroy]` as Path, + false as UnpackNestedValue>> ); if (typeof onFileChange === 'function') { onFileChange({ attachment_name: f.name }); diff --git a/app/frontend/src/javascript/components/form/form-multi-file-upload.tsx b/app/frontend/src/javascript/components/form/form-multi-file-upload.tsx index 3746a9097..08a3fde47 100644 --- a/app/frontend/src/javascript/components/form/form-multi-file-upload.tsx +++ b/app/frontend/src/javascript/components/form/form-multi-file-upload.tsx @@ -6,9 +6,10 @@ import { FieldValues } from 'react-hook-form/dist/types/fields'; import { FormComponent, FormControlledComponent } from '../../models/form-component'; import { AbstractFormItemProps } from './abstract-form-item'; import { UseFormSetValue } from 'react-hook-form/dist/types/form'; -import { ArrayPath, FieldArray, useFieldArray } from 'react-hook-form'; +import { ArrayPath, FieldArray, Path, useFieldArray, useWatch } from 'react-hook-form'; import { FileType } from '../../models/file'; import { UnpackNestedValue } from 'react-hook-form/dist/types'; +import { FieldPathValue } from 'react-hook-form/dist/types/path'; interface FormMultiFileUploadProps extends FormComponent, FormControlledComponent, AbstractFormItemProps { setValue: UseFormSetValue, @@ -20,13 +21,26 @@ interface FormMultiFileUploadProps extend * This component allows to upload multiple files, in forms managed by react-hook-form. */ export const FormMultiFileUpload = ({ id, className, register, control, setValue, formState, addButtonLabel, accept }: FormMultiFileUploadProps) => { - const { fields, append, remove } = useFieldArray({ control, name: id as ArrayPath }); + const { append } = useFieldArray({ control, name: id as ArrayPath }); + const output = useWatch({ control, name: id as Path }); + + /** + * Remove an file + */ + const handleRemoveFile = (file: FileType, index: number) => { + return () => { + setValue( + `${id}.${index}._destroy` as Path, + true as UnpackNestedValue>> + ); + }; + }; return (
- {fields.map((field: FileType, index) => ( - ( + remove(index)}/> + onFileRemove={() => handleRemoveFile(field, index)}/> ))}
exten * This component allows to upload multiple images, in forms managed by react-hook-form. */ export const FormMultiImageUpload = ({ id, className, register, control, setValue, formState, addButtonLabel }: FormMultiImageUploadProps) => { - const { fields, append, remove } = useFieldArray({ control, name: id as ArrayPath }); + const { append } = useFieldArray({ control, name: id as ArrayPath }); const output = useWatch({ control, name: id as Path }); /** @@ -44,14 +44,10 @@ export const FormMultiImageUpload = >> ); } - if (typeof image.id === 'string') { - remove(index); - } else { - setValue( - `${id}.${index}._destroy` as Path, - true as UnpackNestedValue>> - ); - } + setValue( + `${id}.${index}._destroy` as Path, + true as UnpackNestedValue>> + ); }; }; @@ -74,8 +70,8 @@ export const FormMultiImageUpload =
- {fields.map((field: ImageType, index) => ( - ( + (i) { i[:attachment].blank? } + accepts_nested_attributes_for :product_images, allow_destroy: true, reject_if: ->(i) { i[:attachment].blank? && i[:id].blank? } has_many :product_stock_movements, dependent: :destroy accepts_nested_attributes_for :product_stock_movements, allow_destroy: true, reject_if: :all_blank diff --git a/app/views/api/products/_product.json.jbuilder b/app/views/api/products/_product.json.jbuilder index f5f143349..961baf234 100644 --- a/app/views/api/products/_product.json.jbuilder +++ b/app/views/api/products/_product.json.jbuilder @@ -4,12 +4,12 @@ json.extract! product, :id, :name, :slug, :sku, :is_active, :product_category_id :low_stock_threshold, :machine_ids, :created_at json.description sanitize(product.description) json.amount product.amount / 100.0 if product.amount.present? -json.product_files_attributes product.product_files do |f| +json.product_files_attributes product.product_files.order(created_at: :asc) do |f| json.id f.id json.attachment_name f.attachment_identifier json.attachment_url f.attachment_url end -json.product_images_attributes product.product_images do |f| +json.product_images_attributes product.product_images.order(created_at: :asc) do |f| json.id f.id json.attachment_name f.attachment_identifier json.attachment_url f.attachment_url From 37b17595a11ac7fa0e4f359f18794016e07eac92 Mon Sep 17 00:00:00 2001 From: Du Peng Date: Tue, 22 Nov 2022 16:36:21 +0100 Subject: [PATCH 09/11] (test) add missing vcr cassettes test file --- ...r_admin_pay_by_cart_and_wallet_success.yml | 340 ++++++++++++++++++ 1 file changed, 340 insertions(+) create mode 100644 test/vcr_cassettes/store_order_admin_pay_by_cart_and_wallet_success.yml diff --git a/test/vcr_cassettes/store_order_admin_pay_by_cart_and_wallet_success.yml b/test/vcr_cassettes/store_order_admin_pay_by_cart_and_wallet_success.yml new file mode 100644 index 000000000..5542abba4 --- /dev/null +++ b/test/vcr_cassettes/store_order_admin_pay_by_cart_and_wallet_success.yml @@ -0,0 +1,340 @@ +--- +http_interactions: +- request: + method: post + uri: https://api.stripe.com/v1/payment_methods + body: + encoding: UTF-8 + string: type=card&card[number]=4242424242424242&card[exp_month]=4&card[exp_year]=2023&card[cvc]=314 + headers: + User-Agent: + - Stripe/v1 RubyBindings/5.29.0 + Authorization: + - Bearer sk_test_testfaketestfaketestfake + Content-Type: + - application/x-www-form-urlencoded + X-Stripe-Client-Telemetry: + - '{"last_request_metrics":{"request_id":"req_Xmih0ndHQjzde4","request_duration_ms":2}}' + Stripe-Version: + - '2019-08-14' + X-Stripe-Client-User-Agent: + - '{"bindings_version":"5.29.0","lang":"ruby","lang_version":"2.6.3 p62 (2019-04-16)","platform":"x86_64-darwin18","engine":"ruby","publisher":"stripe","uname":"Darwin + MacBook-Pro-Sleede-Peng 20.6.0 Darwin Kernel Version 20.6.0: Thu Sep 29 20:15:11 + PDT 2022; root:xnu-7195.141.42~1/RELEASE_X86_64 x86_64","hostname":"MacBook-Pro-Sleede-Peng"}' + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Server: + - nginx + Date: + - Tue, 22 Nov 2022 10:23:29 GMT + Content-Type: + - application/json + Content-Length: + - '930' + Connection: + - keep-alive + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Allow-Methods: + - GET, POST, HEAD, OPTIONS, DELETE + Access-Control-Allow-Origin: + - "*" + Access-Control-Expose-Headers: + - Request-Id, Stripe-Manage-Version, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required + Access-Control-Max-Age: + - '300' + Cache-Control: + - no-cache, no-store + Idempotency-Key: + - 524422f2-06be-4b13-a164-1d78f9221db3 + Original-Request: + - req_LJ3F130BMDTCDc + Request-Id: + - req_LJ3F130BMDTCDc + Stripe-Should-Retry: + - 'false' + Stripe-Version: + - '2019-08-14' + Strict-Transport-Security: + - max-age=63072000; includeSubDomains; preload + body: + encoding: UTF-8 + string: |- + { + "id": "pm_1M6tdV2sOmf47Nz9ZhwXBRTL", + "object": "payment_method", + "billing_details": { + "address": { + "city": null, + "country": null, + "line1": null, + "line2": null, + "postal_code": null, + "state": null + }, + "email": null, + "name": null, + "phone": null + }, + "card": { + "brand": "visa", + "checks": { + "address_line1_check": null, + "address_postal_code_check": null, + "cvc_check": "unchecked" + }, + "country": "US", + "exp_month": 4, + "exp_year": 2023, + "fingerprint": "o52jybR7bnmNn6AT", + "funding": "credit", + "generated_from": null, + "last4": "4242", + "networks": { + "available": [ + "visa" + ], + "preferred": null + }, + "three_d_secure_usage": { + "supported": true + }, + "wallet": null + }, + "created": 1669112609, + "customer": null, + "livemode": false, + "metadata": {}, + "type": "card" + } + recorded_at: Tue, 22 Nov 2022 10:23:29 GMT +- request: + method: post + uri: https://api.stripe.com/v1/payment_intents + body: + encoding: UTF-8 + string: payment_method=pm_1M6tdV2sOmf47Nz9ZhwXBRTL&amount=162500¤cy=usd&confirmation_method=manual&confirm=true&customer=cus_8CyNk3UTi8lvCc + headers: + User-Agent: + - Stripe/v1 RubyBindings/5.29.0 + Authorization: + - Bearer sk_test_testfaketestfaketestfake + Content-Type: + - application/x-www-form-urlencoded + X-Stripe-Client-Telemetry: + - '{"last_request_metrics":{"request_id":"req_LJ3F130BMDTCDc","request_duration_ms":737}}' + Stripe-Version: + - '2019-08-14' + X-Stripe-Client-User-Agent: + - '{"bindings_version":"5.29.0","lang":"ruby","lang_version":"2.6.3 p62 (2019-04-16)","platform":"x86_64-darwin18","engine":"ruby","publisher":"stripe","uname":"Darwin + MacBook-Pro-Sleede-Peng 20.6.0 Darwin Kernel Version 20.6.0: Thu Sep 29 20:15:11 + PDT 2022; root:xnu-7195.141.42~1/RELEASE_X86_64 x86_64","hostname":"MacBook-Pro-Sleede-Peng"}' + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Server: + - nginx + Date: + - Tue, 22 Nov 2022 10:23:31 GMT + Content-Type: + - application/json + Content-Length: + - '4476' + Connection: + - keep-alive + Access-Control-Allow-Credentials: + - 'true' + Access-Control-Allow-Methods: + - GET, POST, HEAD, OPTIONS, DELETE + Access-Control-Allow-Origin: + - "*" + Access-Control-Expose-Headers: + - Request-Id, Stripe-Manage-Version, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required + Access-Control-Max-Age: + - '300' + Cache-Control: + - no-cache, no-store + Idempotency-Key: + - 3c835ee8-86ea-4896-811f-0d3e0785cd09 + Original-Request: + - req_ybBnHONjaSyx1X + Request-Id: + - req_ybBnHONjaSyx1X + Stripe-Should-Retry: + - 'false' + Stripe-Version: + - '2019-08-14' + Strict-Transport-Security: + - max-age=63072000; includeSubDomains; preload + body: + encoding: UTF-8 + string: |- + { + "id": "pi_3M6tdW2sOmf47Nz90KZ43vXZ", + "object": "payment_intent", + "amount": 162500, + "amount_capturable": 0, + "amount_details": { + "tip": {} + }, + "amount_received": 162500, + "application": null, + "application_fee_amount": null, + "automatic_payment_methods": null, + "canceled_at": null, + "cancellation_reason": null, + "capture_method": "automatic", + "charges": { + "object": "list", + "data": [ + { + "id": "ch_3M6tdW2sOmf47Nz90CWztIp1", + "object": "charge", + "amount": 162500, + "amount_captured": 162500, + "amount_refunded": 0, + "application": null, + "application_fee": null, + "application_fee_amount": null, + "balance_transaction": "txn_3M6tdW2sOmf47Nz90xHPB7ge", + "billing_details": { + "address": { + "city": null, + "country": null, + "line1": null, + "line2": null, + "postal_code": null, + "state": null + }, + "email": null, + "name": null, + "phone": null + }, + "calculated_statement_descriptor": "Stripe", + "captured": true, + "created": 1669112610, + "currency": "usd", + "customer": "cus_8CyNk3UTi8lvCc", + "description": null, + "destination": null, + "dispute": null, + "disputed": false, + "failure_balance_transaction": null, + "failure_code": null, + "failure_message": null, + "fraud_details": {}, + "invoice": null, + "livemode": false, + "metadata": {}, + "on_behalf_of": null, + "order": null, + "outcome": { + "network_status": "approved_by_network", + "reason": null, + "risk_level": "normal", + "risk_score": 19, + "seller_message": "Payment complete.", + "type": "authorized" + }, + "paid": true, + "payment_intent": "pi_3M6tdW2sOmf47Nz90KZ43vXZ", + "payment_method": "pm_1M6tdV2sOmf47Nz9ZhwXBRTL", + "payment_method_details": { + "card": { + "brand": "visa", + "checks": { + "address_line1_check": null, + "address_postal_code_check": null, + "cvc_check": "pass" + }, + "country": "US", + "exp_month": 4, + "exp_year": 2023, + "fingerprint": "o52jybR7bnmNn6AT", + "funding": "credit", + "installments": null, + "last4": "4242", + "mandate": null, + "network": "visa", + "three_d_secure": null, + "wallet": null + }, + "type": "card" + }, + "receipt_email": null, + "receipt_number": null, + "receipt_url": "https://pay.stripe.com/receipts/payment/CAcaFwoVYWNjdF8xMDNyRTYyc09tZjQ3Tno5KKPG8psGMgbPi1sBzwo6LBYq6qekkFXHe51z6Ei_GniUXrfPnNwAP9pxTHhMG3SSyEbSD-_jgxqNwc0o", + "refunded": false, + "refunds": { + "object": "list", + "data": [], + "has_more": false, + "total_count": 0, + "url": "/v1/charges/ch_3M6tdW2sOmf47Nz90CWztIp1/refunds" + }, + "review": null, + "shipping": null, + "source": null, + "source_transfer": null, + "statement_descriptor": null, + "statement_descriptor_suffix": null, + "status": "succeeded", + "transfer_data": null, + "transfer_group": null + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/charges?payment_intent=pi_3M6tdW2sOmf47Nz90KZ43vXZ" + }, + "client_secret": "pi_3M6tdW2sOmf47Nz90KZ43vXZ_secret_EZPT1VWG8jNZgqaW0iqXN0LjY", + "confirmation_method": "manual", + "created": 1669112610, + "currency": "usd", + "customer": "cus_8CyNk3UTi8lvCc", + "description": null, + "invoice": null, + "last_payment_error": null, + "livemode": false, + "metadata": {}, + "next_action": null, + "on_behalf_of": null, + "payment_method": "pm_1M6tdV2sOmf47Nz9ZhwXBRTL", + "payment_method_options": { + "card": { + "installments": null, + "mandate_options": null, + "network": null, + "request_three_d_secure": "automatic" + } + }, + "payment_method_types": [ + "card" + ], + "processing": null, + "receipt_email": null, + "review": null, + "setup_future_usage": null, + "shipping": null, + "source": null, + "statement_descriptor": null, + "statement_descriptor_suffix": null, + "status": "succeeded", + "transfer_data": null, + "transfer_group": null + } + recorded_at: Tue, 22 Nov 2022 10:23:31 GMT +recorded_with: VCR 6.0.0 From 1f5808da4abdfb8c495254dcf8be3af3d28ccc5a Mon Sep 17 00:00:00 2001 From: Du Peng Date: Tue, 22 Nov 2022 17:12:39 +0100 Subject: [PATCH 10/11] (quality) use point instant of square brackets in react-form-hook field path --- .../src/javascript/components/form/form-image-upload.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/frontend/src/javascript/components/form/form-image-upload.tsx b/app/frontend/src/javascript/components/form/form-image-upload.tsx index fd90e1043..2428992d7 100644 --- a/app/frontend/src/javascript/components/form/form-image-upload.tsx +++ b/app/frontend/src/javascript/components/form/form-image-upload.tsx @@ -60,11 +60,11 @@ export const FormImageUpload = , + `${id}.attachment_name` as Path, f.name as UnpackNestedValue>> ); setValue( - `${id}[_destroy]` as Path, + `${id}._destroy` as Path, false as UnpackNestedValue>> ); if (typeof onFileChange === 'function') { From 2a4d50e7d936827a28ee6ad2a40d43682755e166 Mon Sep 17 00:00:00 2001 From: Du Peng Date: Tue, 22 Nov 2022 17:19:58 +0100 Subject: [PATCH 11/11] Version 5.5.5 --- CHANGELOG.md | 2 ++ package.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 77abd4585..2bba52116 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Changelog Fab-manager +## v5.5.5 2022 November 22 + - Soft destroy of spaces and machines - Fix a bug: in upgrade script, the error "the input device is not a TTY" is thrown when migrating the database - Fix a bug: broken display of machines pages diff --git a/package.json b/package.json index 6ff9014a2..264204554 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fab-manager", - "version": "5.5.4", + "version": "5.5.5", "description": "Fab-manager is the FabLab management solution. It provides a comprehensive, web-based, open-source tool to simplify your administrative tasks and your marker's projects.", "keywords": [ "fablab",