diff --git a/.codeclimate.yml b/.codeclimate.yml new file mode 100644 index 000000000..797220f60 --- /dev/null +++ b/.codeclimate.yml @@ -0,0 +1,5 @@ +plugins: + rubocop: + enabled: true + config: + file: .rubocop.yml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 000000000..eb9d8ee34 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,9 @@ +stages: + - test + +include: + - template: Code-Quality.gitlab-ci.yml + +code_quality: + artifacts: + paths: [ gl-code-quality-report.json ] diff --git a/.rubocop.yml b/.rubocop.yml index 2eca3bfc2..b037a56b2 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,4 +1,8 @@ -Metrics/LineLength: +require: rubocop-rails + +AllCops: + NewCops: enable +Layout/LineLength: Max: 140 Metrics/MethodLength: Max: 35 @@ -19,8 +23,6 @@ Metrics/BlockLength: - 'test/**/*.rb' Metrics/ParameterLists: CountKeywordArgs: false -Style/BracesAroundHashParameters: - EnforcedStyle: context_dependent Style/RegexpLiteral: EnforcedStyle: slashes Style/EmptyElse: diff --git a/CHANGELOG.md b/CHANGELOG.md index d21d8c8ff..bcd029667 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,32 @@ # Changelog Fab-manager -## next deploy +## v5.4.13 2022 July 27 + +- Improved calendars loading time +- Refactored and documented the availability-slot-reservation data model +- Display bookers names to connected users now apply to all resources +- Updated rails locales files +- Usage of the rails logger instead of printing to standard output +- Optionnaly add a username column to the member list (#375) +- Improved attached ICS file texts, in reservations emails +- Fix a bug: user validation required alert is displayed and disappears instantly +- Fix a bug: canceled trainings are still shown on the public profile page +- Fix a bug: prevent same slot booking feature ignores canceled reservations +- Fix a bug: wrong currency on invoices files +- Fix a bug: unable to reserve if user's subscription plan is disabled +- Fix a bug: for admins and managers, the current password is not requested before changing their own password +- Fix a bug: missing translations +- Fix a bug: unable to book a space's slot with an existing reservation +- Fix a bug: Unable to import accounts from SSO when the transformation modal was opened but leaved empty +- Fix a bug: Unable to change the group of a user +- Fix a bug: As admin, unable to create a new member (#374) +- Fix a bug: profile completion form is not shown is T&C were not filled +- Fix a bug: Erroneous "cancelation failed" message if elasticsearch was disabled +- Fix a bug: admin group being replaced in SSO authentication (#376) +- Fix a bug: SSO data being overridden when it is empty and the user can change it (#377) +- Fix a security issue: updated terser to 4.8.1 to fix [CVE-2022-25858](https://cve.mitre.org/cgi-bin/cvename.cgi?CVE-2022-25858) +- Fix a security issue: updated tzinfo to 1.2.10 to fix [CVE-2022-31163](https://cve.mitre.org/cgi-bin/cvename.cgi?CVE-2022-31163) +- Fix a security issue: updated rails to 5.2.8.1 to fix [CVE-2022-32224](https://cve.mitre.org/cgi-bin/cvename.cgi?CVE-2022-32224) ## v5.4.12 2022 July 06 diff --git a/Gemfile b/Gemfile index 487c6ae3e..272c0fd78 100644 --- a/Gemfile +++ b/Gemfile @@ -3,7 +3,7 @@ source 'https://rubygems.org' # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' -gem 'rails', '~> 5.2.7' +gem 'rails', '~> 5.2.8' # Used by rails 5.2 to reduce the app boot time by over 50% gem 'bootsnap' # Use Puma as web server @@ -39,7 +39,8 @@ group :development do gem 'rb-readline' # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring gem 'railroady' - gem 'rubocop', '~> 0.61.1', require: false + gem 'rubocop', '~> 1.31', require: false + gem 'rubocop-rails', require: false gem 'spring' gem 'spring-watcher-listen', '~> 2.0.0' end diff --git a/Gemfile.lock b/Gemfile.lock index 35bb51c9b..d3b3a069e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -4,46 +4,46 @@ GEM Ascii85 (1.0.3) aasm (5.0.8) concurrent-ruby (~> 1.0) - actioncable (5.2.7.1) - actionpack (= 5.2.7.1) + actioncable (5.2.8.1) + actionpack (= 5.2.8.1) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailer (5.2.7.1) - actionpack (= 5.2.7.1) - actionview (= 5.2.7.1) - activejob (= 5.2.7.1) + actionmailer (5.2.8.1) + actionpack (= 5.2.8.1) + actionview (= 5.2.8.1) + activejob (= 5.2.8.1) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 2.0) - actionpack (5.2.7.1) - actionview (= 5.2.7.1) - activesupport (= 5.2.7.1) + actionpack (5.2.8.1) + actionview (= 5.2.8.1) + activesupport (= 5.2.8.1) rack (~> 2.0, >= 2.0.8) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.0.2) actionpack-page_caching (1.2.2) actionpack (>= 5.0.0) - actionview (5.2.7.1) - activesupport (= 5.2.7.1) + actionview (5.2.8.1) + activesupport (= 5.2.8.1) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.0.3) active_record_query_trace (1.7) - activejob (5.2.7.1) - activesupport (= 5.2.7.1) + activejob (5.2.8.1) + activesupport (= 5.2.8.1) globalid (>= 0.3.6) - activemodel (5.2.7.1) - activesupport (= 5.2.7.1) - activerecord (5.2.7.1) - activemodel (= 5.2.7.1) - activesupport (= 5.2.7.1) + activemodel (5.2.8.1) + activesupport (= 5.2.8.1) + activerecord (5.2.8.1) + activemodel (= 5.2.8.1) + activesupport (= 5.2.8.1) arel (>= 9.0) - activestorage (5.2.7.1) - actionpack (= 5.2.7.1) - activerecord (= 5.2.7.1) + activestorage (5.2.8.1) + actionpack (= 5.2.8.1) + activerecord (= 5.2.8.1) marcel (~> 1.0.0) - activesupport (5.2.7.1) + activesupport (5.2.8.1) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 0.7, < 2) minitest (~> 5.1) @@ -57,7 +57,7 @@ GEM apipie-rails (0.5.17) rails (>= 4.1) arel (9.0.0) - ast (2.4.0) + ast (2.4.2) attr_required (1.0.1) awesome_print (1.8.0) axiom-types (0.1.1) @@ -167,7 +167,7 @@ GEM mime-types (~> 3.0) multi_xml (>= 0.5.2) httpclient (2.8.3) - i18n (1.10.0) + i18n (1.12.0) concurrent-ruby (~> 1.0) icalendar (2.7.1) ice_cube (~> 0.16) @@ -176,7 +176,6 @@ GEM image_processing (1.12.2) mini_magick (>= 4.9.5, < 5) ruby-vips (>= 2.0.17, < 3) - jaro_winkler (1.5.4) jbuilder (2.10.0) activesupport (>= 5.0.0) jbuilder_cache_multi (0.1.0) @@ -222,7 +221,7 @@ GEM mini_magick (4.10.1) mini_mime (1.1.2) mini_portile2 (2.8.0) - minitest (5.15.0) + minitest (5.16.2) minitest-reporters (1.4.2) ansi builder @@ -233,7 +232,7 @@ GEM multi_xml (0.6.0) multipart-post (2.1.1) nio4r (2.5.8) - nokogiri (1.13.6) + nokogiri (1.13.8) mini_portile2 (~> 2.8.0) racc (~> 1.4) notify_with (0.0.2) @@ -274,8 +273,8 @@ GEM httparty (~> 0.20) orm_adapter (0.5.0) parallel (1.19.1) - parser (2.7.0.4) - ast (~> 2.4.0) + parser (3.1.2.0) + ast (~> 2.4.1) pdf-core (0.7.0) pdf-reader (2.4.0) Ascii85 (~> 1.0.0) @@ -287,7 +286,6 @@ GEM pg_search (2.3.2) activerecord (>= 5.2) activesupport (>= 5.2) - powerpack (0.1.2) prawn (2.2.2) pdf-core (~> 0.7.0) ttfunk (~> 1.5) @@ -300,7 +298,7 @@ GEM activesupport (>= 3.0.0) raabro (1.4.0) racc (1.6.0) - rack (2.2.3.1) + rack (2.2.4) rack-oauth2 (1.19.0) activesupport attr_required @@ -309,21 +307,21 @@ GEM rack (>= 2.1.0) rack-proxy (0.7.2) rack - rack-test (1.1.0) - rack (>= 1.0, < 3) + rack-test (2.0.2) + rack (>= 1.3) railroady (1.5.3) - rails (5.2.7.1) - actioncable (= 5.2.7.1) - actionmailer (= 5.2.7.1) - actionpack (= 5.2.7.1) - actionview (= 5.2.7.1) - activejob (= 5.2.7.1) - activemodel (= 5.2.7.1) - activerecord (= 5.2.7.1) - activestorage (= 5.2.7.1) - activesupport (= 5.2.7.1) + rails (5.2.8.1) + actioncable (= 5.2.8.1) + actionmailer (= 5.2.8.1) + actionpack (= 5.2.8.1) + actionview (= 5.2.8.1) + activejob (= 5.2.8.1) + activemodel (= 5.2.8.1) + activerecord (= 5.2.8.1) + activestorage (= 5.2.8.1) + activesupport (= 5.2.8.1) bundler (>= 1.3.0) - railties (= 5.2.7.1) + railties (= 5.2.8.1) sprockets-rails (>= 2.0.0) rails-dom-testing (2.0.3) activesupport (>= 4.2.0) @@ -337,9 +335,9 @@ GEM rails_stdout_logging rails_serve_static_assets (0.0.5) rails_stdout_logging (0.0.5) - railties (5.2.7.1) - actionpack (= 5.2.7.1) - activesupport (= 5.2.7.1) + railties (5.2.8.1) + actionpack (= 5.2.8.1) + activesupport (= 5.2.8.1) method_source rake (>= 0.8.7) thor (>= 0.19.0, < 2.0) @@ -353,19 +351,29 @@ GEM activesupport i18n redis (4.6.0) + regexp_parser (2.5.0) repost (0.3.2) responders (2.4.1) actionpack (>= 4.2.0, < 6.0) railties (>= 4.2.0, < 6.0) + rexml (3.2.5) rolify (5.2.0) - rubocop (0.61.1) - jaro_winkler (~> 1.5.1) + rubocop (1.31.2) + json (~> 2.3) parallel (~> 1.10) - parser (>= 2.5, != 2.5.1.1) - powerpack (~> 0.1) + parser (>= 3.1.0.0) rainbow (>= 2.2.2, < 4.0) + regexp_parser (>= 1.8, < 3.0) + rexml (>= 3.2.5, < 4.0) + rubocop-ast (>= 1.18.0, < 2.0) ruby-progressbar (~> 1.7) - unicode-display_width (~> 1.4.0) + unicode-display_width (>= 1.4.0, < 3.0) + rubocop-ast (1.19.1) + parser (>= 3.1.1.0) + rubocop-rails (2.15.2) + activesupport (>= 4.2.0) + rack (>= 1.1) + rubocop (>= 1.7.0, < 2.0) ruby-progressbar (1.10.1) ruby-rc4 (0.1.5) ruby-vips (2.1.4) @@ -412,7 +420,7 @@ GEM spring-watcher-listen (2.0.1) listen (>= 2.7, < 4.0) spring (>= 1.2, < 3.0) - sprockets (4.0.3) + sprockets (4.1.1) concurrent-ruby (~> 1.0) rack (> 1, < 3) sprockets-rails (3.4.2) @@ -440,7 +448,7 @@ GEM camertron-eprun cldr-plurals-runtime-rb (~> 1.0) tzinfo - tzinfo (1.2.9) + tzinfo (1.2.10) thread_safe (~> 0.1) tzinfo-data (1.2020.4) tzinfo (>= 1.0.0) @@ -529,7 +537,7 @@ DEPENDENCIES puma (= 4.3.12) pundit railroady - rails (~> 5.2.7) + rails (~> 5.2.8) rails-observers rails_12factor rb-readline @@ -537,7 +545,8 @@ DEPENDENCIES repost responders (~> 2.0) rolify - rubocop (~> 0.61.1) + rubocop (~> 1.31) + rubocop-rails rubyXL rubyzip (>= 1.3.0) sassc (= 2.1.0) diff --git a/app/controllers/api/availabilities_controller.rb b/app/controllers/api/availabilities_controller.rb index 9e2c524ee..d79a171c8 100644 --- a/app/controllers/api/availabilities_controller.rb +++ b/app/controllers/api/availabilities_controller.rb @@ -4,15 +4,15 @@ class API::AvailabilitiesController < API::ApiController before_action :authenticate_user!, except: [:public] before_action :set_availability, only: %i[show update reservations lock] - before_action :define_max_visibility, only: %i[machine trainings spaces] + before_action :set_operator_role, only: %i[machine spaces trainings] + before_action :set_customer, only: %i[machine spaces trainings] respond_to :json def index authorize Availability - start_date = ActiveSupport::TimeZone[params[:timezone]].parse(params[:start]) - end_date = ActiveSupport::TimeZone[params[:timezone]].parse(params[:end]).end_of_day + display_window = window @availabilities = Availability.includes(:machines, :tags, :trainings, :spaces) - .where('start_at >= ? AND end_at <= ?', start_date, end_date) + .where('start_at >= ? AND end_at <= ?', display_window[:start], display_window[:end]) @availabilities = @availabilities.where.not(available_type: 'event') unless Setting.get('events_in_calendar') @@ -20,19 +20,14 @@ class API::AvailabilitiesController < API::ApiController end def public - start_date = ActiveSupport::TimeZone[params[:timezone]].parse(params[:start]) - end_date = ActiveSupport::TimeZone[params[:timezone]].parse(params[:end]).end_of_day - @reservations = Reservation.includes(:slots, :statistic_profile) - .references(:slots) - .where('slots.start_at >= ? AND slots.end_at <= ?', start_date, end_date) + display_window = window machine_ids = params[:m] || [] service = Availabilities::PublicAvailabilitiesService.new(current_user) @availabilities = service.public_availabilities( - start_date, - end_date, - @reservations, - machines: machine_ids, spaces: params[:s] + display_window, + { machines: machine_ids, spaces: params[:s], trainings: params[:t] }, + (params[:evt] && params[:evt] == 'true') ) @title_filter = { machine_ids: machine_ids.map(&:to_i) } @@ -47,10 +42,8 @@ class API::AvailabilitiesController < API::ApiController authorize Availability @availability = Availability.new(availability_params) if @availability.save - if params[:availability][:occurrences] - service = Availabilities::CreateAvailabilitiesService.new - service.create(@availability, params[:availability][:occurrences]) - end + service = Availabilities::CreateAvailabilitiesService.new + service.create(@availability, params[:availability][:occurrences]) render :show, status: :created, location: @availability else render json: @availability.errors, status: :unprocessable_entity @@ -78,27 +71,32 @@ class API::AvailabilitiesController < API::ApiController end def machine - @current_user_role = current_user.role - - service = Availabilities::AvailabilitiesService.new(current_user, other: @visi_max_other, year: @visi_max_year) - @slots = service.machines(params[:machine_id], user) + service = Availabilities::AvailabilitiesService.new(current_user) + @machine = Machine.friendly.find(params[:machine_id]) + @slots = service.machines([@machine], @customer, window) end def trainings - service = Availabilities::AvailabilitiesService.new(current_user, other: @visi_max_other, year: @visi_max_year) - @availabilities = service.trainings(params[:training_id], user) + service = Availabilities::AvailabilitiesService.new(current_user) + @trainings = if params[:training_id].is_number? || (params[:training_id].length.positive? && params[:training_id] != 'all') + [Training.friendly.find(params[:training_id])] + else + Training.all + end + @slots = service.trainings(@trainings, @customer, window) end def spaces - @current_user_role = current_user.role - - service = Availabilities::AvailabilitiesService.new(current_user, other: @visi_max_other, year: @visi_max_year) - @slots = service.spaces(params[:space_id], user) + service = Availabilities::AvailabilitiesService.new(current_user) + @space = Space.friendly.find(params[:space_id]) + @slots = service.spaces([@space], @customer, window) end def reservations authorize Availability - @reservation_slots = @availability.slots.includes(reservations: [statistic_profile: [user: [:profile]]]).order('slots.start_at ASC') + @slots_reservations = @availability.slots_reservations + .includes(:slot, reservation: [statistic_profile: [user: [:profile]]]) + .order('slots.start_at ASC') end def export_availabilities @@ -131,12 +129,22 @@ class API::AvailabilitiesController < API::ApiController private - def user - if params[:member_id] - User.find(params[:member_id]) - else - current_user - end + def window + start_date = ActiveSupport::TimeZone[params[:timezone]]&.parse(params[:start]) + end_date = ActiveSupport::TimeZone[params[:timezone]]&.parse(params[:end])&.end_of_day + { start: start_date, end: end_date } + end + + def set_customer + @customer = if params[:member_id] + User.find(params[:member_id]) + else + current_user + end + end + + def set_operator_role + @operator_role = current_user.role end def set_availability @@ -155,43 +163,10 @@ class API::AvailabilitiesController < API::ApiController end def filter_availabilites(availabilities) - availabilities_filtered = [] - availabilities.to_ary.each do |a| - # machine slot - if !a.try(:available_type) - availabilities_filtered << a - else - availabilities_filtered << a if filter_training?(a) - availabilities_filtered << a if filter_space?(a) - availabilities_filtered << a if filter_machine?(a) - availabilities_filtered << a if filter_event?(a) - end - end - availabilities_filtered.delete_if(&method(:remove_full?)) - end - - def filter_training?(availability) - params[:t] && availability.available_type == 'training' && params[:t].include?(availability.trainings.first.id.to_s) - end - - def filter_space?(availability) - params[:s] && availability.available_type == 'space' && params[:s].include?(availability.spaces.first.id.to_s) - end - - def filter_machine?(availability) - params[:m] && availability.available_type == 'machines' && (params[:m].map(&:to_i) & availability.machine_ids).any? - end - - def filter_event?(availability) - params[:evt] && params[:evt] == 'true' && availability.available_type == 'event' + availabilities.delete_if(&method(:remove_full?)) end def remove_full?(availability) params[:dispo] == 'false' && (availability.is_reserved || (availability.try(:full?) && availability.full?)) end - - def define_max_visibility - @visi_max_year = Setting.get('visibility_yearly').to_i.months.since - @visi_max_other = Setting.get('visibility_others').to_i.months.since - end end diff --git a/app/controllers/api/events_controller.rb b/app/controllers/api/events_controller.rb index 0c051743a..26f7300e0 100644 --- a/app/controllers/api/events_controller.rb +++ b/app/controllers/api/events_controller.rb @@ -55,6 +55,8 @@ class API::EventsController < API::ApiController authorize Event @event = Event.new(event_params.permit!) if @event.save + service = Availabilities::CreateAvailabilitiesService.new + service.create_slots(@event.availability) render :show, status: :created, location: @event else render json: @event.errors, status: :unprocessable_entity diff --git a/app/controllers/api/members_controller.rb b/app/controllers/api/members_controller.rb index ad314c520..5ca7f455c 100644 --- a/app/controllers/api/members_controller.rb +++ b/app/controllers/api/members_controller.rb @@ -40,7 +40,7 @@ class API::MembersController < API::ApiController def create authorize :user, :create_member? - @member = User.new(user_params.permit!) + @member = User.new(Members::MembersService.handle_organization(user_params.permit!)) members_service = Members::MembersService.new(@member) if members_service.create(current_user, user_params) diff --git a/app/controllers/api/payzen_controller.rb b/app/controllers/api/payzen_controller.rb index 15b8a9251..69fbaa70d 100644 --- a/app/controllers/api/payzen_controller.rb +++ b/app/controllers/api/payzen_controller.rb @@ -55,9 +55,8 @@ class API::PayzenController < API::PaymentsController def check_cart cart = shopping_cart - unless cart.valid? - render json: { error: 'unable to pay' }, status: :unprocessable_entity and return - end + render json: { error: cart.errors }, status: :unprocessable_entity and return unless cart.valid? + render json: { cart: 'ok' }, status: :ok end diff --git a/app/controllers/api/reservations_controller.rb b/app/controllers/api/reservations_controller.rb index d0ef06b8c..42cab8487 100644 --- a/app/controllers/api/reservations_controller.rb +++ b/app/controllers/api/reservations_controller.rb @@ -43,6 +43,6 @@ class API::ReservationsController < API::ApiController def reservation_params params.require(:reservation).permit(:message, :reservable_id, :reservable_type, :nb_reserve_places, tickets_attributes: %i[event_price_category_id booked], - slots_attributes: %i[id start_at end_at availability_id offered]) + slots_reservations_attributes: %i[id slot_id offered]) end end diff --git a/app/controllers/api/slots_controller.rb b/app/controllers/api/slots_controller.rb deleted file mode 100644 index e901efb2a..000000000 --- a/app/controllers/api/slots_controller.rb +++ /dev/null @@ -1,35 +0,0 @@ -# frozen_string_literal: true - -# API Controller for resources of type Slot -# Slots are used to cut Availabilities into reservable slots. The duration of these slots is configured per -# availability by Availability.slot_duration, or otherwise globally by Setting.get('slot_duration') -class API::SlotsController < API::ApiController - before_action :authenticate_user! - before_action :set_slot, only: %i[update cancel] - respond_to :json - - def update - authorize @slot - if @slot.update(slot_params) - SubscriptionExtensionAfterReservation.new(@slot.reservation).extend_subscription_if_eligible - render :show, status: :created, location: @slot - else - render json: @slot.errors, status: :unprocessable_entity - end - end - - def cancel - authorize @slot - SlotService.new.cancel(@slot) - end - - private - - def set_slot - @slot = Slot.find(params[:id]) - end - - def slot_params - params.require(:slot).permit(:start_at, :end_at, :availability_id) - end -end diff --git a/app/controllers/api/slots_reservations_controller.rb b/app/controllers/api/slots_reservations_controller.rb new file mode 100644 index 000000000..0613ebab1 --- /dev/null +++ b/app/controllers/api/slots_reservations_controller.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +# API Controller for resources of type Slot +# Slots are used to cut Availabilities into reservable slots. The duration of these slots is configured per +# availability by Availability.slot_duration, or otherwise globally by Setting.get('slot_duration') +class API::SlotsReservationsController < API::ApiController + before_action :authenticate_user! + before_action :set_slots_reservation, only: %i[update cancel] + respond_to :json + + def update + authorize @slot_reservation + if @slot_reservation.update(slot_params) + SubscriptionExtensionAfterReservation.new(@slot_reservation.reservation).extend_subscription_if_eligible + render :show, status: :created, location: @slot_reservation + else + render json: @slot_reservation.errors, status: :unprocessable_entity + end + end + + def cancel + authorize @slot_reservation + SlotsReservationsService.cancel(@slot_reservation) + end + + private + + def set_slots_reservation + @slot_reservation = SlotsReservation.find(params[:id]) + end + + def slot_params + params.require(:slots_reservation).permit(:slot_id) + end +end diff --git a/app/controllers/api/stripe_controller.rb b/app/controllers/api/stripe_controller.rb index 011ae9364..05b3a6e2b 100644 --- a/app/controllers/api/stripe_controller.rb +++ b/app/controllers/api/stripe_controller.rb @@ -19,9 +19,8 @@ class API::StripeController < API::PaymentsController res = nil # json of the API answer cart = shopping_cart - unless cart.valid? - render json: { error: 'unable to pay' }, status: :unprocessable_entity and return - end + render json: { error: cart.errors }, status: :unprocessable_entity and return unless cart.valid? + begin amount = debit_amount(cart) # will contains the amount and the details of each invoice lines if params[:payment_method_id].present? @@ -74,7 +73,7 @@ class API::StripeController < API::PaymentsController def setup_subscription cart = shopping_cart - raise InvalidSubscriptionError unless cart.valid? + render json: { error: cart.errors }, status: :unprocessable_entity and return unless cart.valid? service = Stripe::Service.new method = service.attach_method_as_default( diff --git a/app/controllers/api/trainings_controller.rb b/app/controllers/api/trainings_controller.rb index bb536cd88..b8902da78 100644 --- a/app/controllers/api/trainings_controller.rb +++ b/app/controllers/api/trainings_controller.rb @@ -52,8 +52,8 @@ class API::TrainingsController < API::ApiController authorize Training @training = Training.find(params[:id]) @availabilities = @training.availabilities - .includes(slots: { reservations: { statistic_profile: [:trainings, user: [:profile]] } }) - .where('slots.canceled_at': nil) + .includes(slots: { slots_reservations: { reservations: { statistic_profile: [:trainings, user: [:profile]] } } }) + .where('slots_reservations.canceled_at': nil) .order('availabilities.start_at DESC') end diff --git a/app/controllers/social_bot_controller.rb b/app/controllers/social_bot_controller.rb index e3da819ff..a0a4f1937 100644 --- a/app/controllers/social_bot_controller.rb +++ b/app/controllers/social_bot_controller.rb @@ -14,7 +14,7 @@ class SocialBotController < ActionController::Base @training = Training.friendly.find(Regexp.last_match(3).to_s) render :training, status: :ok else - puts "unknown bot request : #{request.original_url}" + Rails.logger.warn "unknown bot request : #{request.original_url}" end end end diff --git a/app/frontend/src/javascript/components/dashboard/reservations/reservations-panel.tsx b/app/frontend/src/javascript/components/dashboard/reservations/reservations-panel.tsx index 7206460b1..bbcb49068 100644 --- a/app/frontend/src/javascript/components/dashboard/reservations/reservations-panel.tsx +++ b/app/frontend/src/javascript/components/dashboard/reservations/reservations-panel.tsx @@ -1,6 +1,6 @@ import React, { ReactNode, useEffect, useState } from 'react'; import { FabPanel } from '../../base/fab-panel'; -import { Reservation, ReservationSlot } from '../../../models/reservation'; +import { Reservation, SlotsReservation } from '../../../models/reservation'; import ReservationAPI from '../../../api/reservation'; import { useTranslation } from 'react-i18next'; import moment from 'moment'; @@ -38,16 +38,16 @@ const ReservationsPanel: React.FC = ({ userId, onError, */ const reservationsByDate = (state: 'past' | 'futur'): Array => { return reservations.filter(r => { - return !!r.slots_attributes.find(s => filterSlot(s, state)); + return !!r.slots_reservations_attributes.find(s => filterSlot(s, state)); }); }; /** - * Check if the given slot if past of futur + * Check if the given slot reservation if past of futur */ - const filterSlot = (slot: ReservationSlot, state: 'past' | 'futur'): boolean => { - return (state === 'past' && moment(slot.start_at).isBefore()) || - (state === 'futur' && moment(slot.start_at).isAfter()); + const filterSlot = (sr: SlotsReservation, state: 'past' | 'futur'): boolean => { + return (state === 'past' && moment(sr.slot_attributes.start_at).isBefore()) || + (state === 'futur' && moment(sr.slot_attributes.start_at).isAfter()); }; /** @@ -95,12 +95,12 @@ const ReservationsPanel: React.FC = ({ userId, onError, return (
  • - {reservation.reservable.name} - {FormatLib.date(reservation.slots_attributes[0].start_at)} + {reservation.reservable.name} - {FormatLib.date(reservation.slots_reservations_attributes[0].slot_attributes.start_at)} {details[reservation.id] && - {reservation.slots_attributes.filter(s => filterSlot(s, state)).map( - slot => - {FormatLib.date(slot.start_at)}, {FormatLib.time(slot.start_at)} - {FormatLib.time(slot.end_at)} + {reservation.slots_reservations_attributes.filter(s => filterSlot(s, state)).map( + slotReservation => + {FormatLib.date(slotReservation.slot_attributes.start_at)}, {FormatLib.time(slotReservation.slot_attributes.start_at)} - {FormatLib.time(slotReservation.slot_attributes.end_at)} )} } @@ -109,7 +109,7 @@ const ReservationsPanel: React.FC = ({ userId, onError, }; const futur = reservationsByDate('futur'); - const past = _.orderBy(reservationsByDate('past'), r => r.slots_attributes[0].start_at, 'desc'); + const past = _.orderBy(reservationsByDate('past'), r => r.slots_reservations_attributes[0].slot_attributes.start_at, 'desc'); return ( diff --git a/app/frontend/src/javascript/components/form/abstract-form-item.tsx b/app/frontend/src/javascript/components/form/abstract-form-item.tsx index 15ef6467a..679c2bde9 100644 --- a/app/frontend/src/javascript/components/form/abstract-form-item.tsx +++ b/app/frontend/src/javascript/components/form/abstract-form-item.tsx @@ -10,13 +10,14 @@ export interface AbstractFormItemProps extends PropsWithChildren boolean), onLabelClick?: (event: React.MouseEvent) => void, + inLine?: boolean, } /** * This abstract component should not be used directly. * Other forms components that are intended to be used with react-hook-form must extend this component. */ -export const AbstractFormItem = ({ id, label, tooltip, className, disabled, error, warning, rules, formState, onLabelClick, children }: AbstractFormItemProps) => { +export const AbstractFormItem = ({ id, label, tooltip, className, disabled, error, warning, rules, formState, onLabelClick, inLine, children }: AbstractFormItemProps) => { const [isDirty, setIsDirty] = useState(false); const [fieldError, setFieldError] = useState<{ message: string }>(error); const [isDisabled, setIsDisabled] = useState(false); @@ -59,14 +60,21 @@ export const AbstractFormItem = ({ id, label, return (