spree_cm_commissioner 2.1.8 → 2.1.9.pre.pre1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/.env.example +75 -29
  3. data/Gemfile.lock +1 -1
  4. data/app/controllers/spree/api/v2/storefront/access_tokens_controller.rb +10 -0
  5. data/app/controllers/spree/api/v2/storefront/account/guest_dynamic_fields_controller.rb +82 -0
  6. data/app/controllers/spree/api/v2/storefront/account/mark_guest_info_complete_controller.rb +39 -0
  7. data/app/controllers/spree/api/v2/storefront/account/orders_controller_decorator.rb +34 -5
  8. data/app/controllers/spree/api/v2/storefront/seat_layouts_controller.rb +1 -1
  9. data/app/controllers/spree/api/v2/tenant/access_tokens_controller.rb +10 -0
  10. data/app/interactors/spree_cm_commissioner/stock/inventory_item_resetter.rb +1 -1
  11. data/app/models/concerns/spree_cm_commissioner/event_metadata.rb +39 -0
  12. data/app/models/concerns/spree_cm_commissioner/order_state_machine.rb +27 -21
  13. data/app/models/concerns/spree_cm_commissioner/store_metadata.rb +97 -0
  14. data/app/models/spree_cm_commissioner/guest.rb +38 -4
  15. data/app/models/spree_cm_commissioner/guest_dynamic_field.rb +1 -0
  16. data/app/models/spree_cm_commissioner/inventory_item.rb +1 -1
  17. data/app/models/spree_cm_commissioner/invite_team.rb +8 -0
  18. data/app/models/spree_cm_commissioner/redis_stock/cached_inventory_items_builder.rb +2 -2
  19. data/app/models/spree_cm_commissioner/redis_stock/inventory_updater.rb +2 -2
  20. data/app/models/spree_cm_commissioner/seat_layout.rb +3 -3
  21. data/app/models/spree_cm_commissioner/taxon_decorator.rb +2 -20
  22. data/app/models/spree_cm_commissioner/trip.rb +5 -7
  23. data/app/models/spree_cm_commissioner/vehicle.rb +5 -7
  24. data/app/overrides/spree/admin/taxons/_form/background_color_and_foreground_color.html.erb.deface +6 -6
  25. data/app/overrides/spree/admin/taxons/_form/check_in_flows.html.erb.deface +14 -8
  26. data/app/serializers/spree/v2/storefront/order_serializer_decorator.rb +63 -0
  27. data/app/serializers/spree/v2/storefront/taxon_serializer_decorator.rb +1 -1
  28. data/app/serializers/spree_cm_commissioner/v2/operator/dashboard_crew_event_serializer.rb +1 -0
  29. data/config/initializers/spree_permitted_attributes.rb +5 -4
  30. data/config/routes.rb +6 -0
  31. data/docs/api/scoped-access-token-endpoints.md +84 -0
  32. data/lib/spree_cm_commissioner/version.rb +1 -1
  33. data/lib/spree_cm_commissioner.rb +34 -12
  34. metadata +12 -5
  35. data/app/models/concerns/spree_cm_commissioner/event_check_in_flowable.rb +0 -30
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 36165e2d7e9f037f00a0590045ae9c6171825a79f0499ec4cb70af91a246b0d5
4
- data.tar.gz: b6d46f79ab01845de456585f50881dbca8b0b28d208e2db386a1833a9dfe1b30
3
+ metadata.gz: a278c011c4129a11c968c695ac8ae1a22996991ab69f8700bdafa6d9002c470e
4
+ data.tar.gz: 5354f593b70e02b4956f27006a82cc06341a2630ee45306b9e8f33a4c246eb9f
5
5
  SHA512:
6
- metadata.gz: bd7ac99403f71109009b3d06dff1fc5a72b61b833d00e27f2a7513c552f7005782ff819a5e69ca67d64160865af6fabf4dd9554f7253b3cbd85ea4ba283b555a
7
- data.tar.gz: f7d227c43eb5ecfc72eeebbf715e651f576f998a769106a1303e647680a9b4e82820b1e31e3c3b10299a1ee79fa6da9fde4fce5ea9ec62074b62d5b6e937d560
6
+ metadata.gz: 140d7641c8e2542f82e64f8d8bf5758b31b5c3a6daf9232bd57bc9835c27685aba70182cf173df43fb805edc08b0968c80612d726469fd1780ad05a05d38d596
7
+ data.tar.gz: fe162b2e261ace9661ea9b2e05a699617ff5da1a707dab0cad139b72c6fb0242966a9728c37e2a653213665c34767177ab8a4ee1d11c8c02ecf82088631386bb
data/.env.example CHANGED
@@ -1,29 +1,75 @@
1
- # aws
2
- AWS_ACCESS_KEY_ID=""
3
- AWS_BUCKET_NAME=""
4
- AWS_REGION=""
5
- AWS_SECRET_ACCESS_KEY=
6
- AWS_CLUSTER_NAME="" # fetch all tasks
7
-
8
- AWS_CF_MEDIA_DOMAIN="medias.bookme.plus"
9
- AWS_CF_PUBLIC_KEY_ID="KKC****QFJ2"
10
- AWS_OUTPUT_BUCKET_NAME="output-production-cm"
11
-
12
- AWS_CF_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----
13
- -----END PRIVATE KEY-----"
14
-
15
- # for GET request with following host, it will be cached.
16
- CONTENT_HOST_URL=https://content.bookme.plus
17
- CONTENT_CACHE_MAX_AGE=7200
18
-
19
- WAITING_ROOM_SESSION_SIGNATURE="e6b2********************26e3"
20
- WAITING_ROOM_SESSION_EXPIRE_DURATION_IN_SECOND=
21
- WAITING_ROOM_MIN_SESSIONS_COUNT=5
22
- WAITING_ROOM_DISABLED=no
23
-
24
- # Vattanac Bank
25
- VATTANAC_AES_SECRET_KEY= ""
26
- VATTANAC_PUBLIC_KEY=""
27
-
28
- # Organizer URL
29
- ORGANIZER_URL=http://127.0.0.1:4000/organizer
1
+ # Rails Configuration
2
+ RAILS_ENV=development # Rails environment (development, test, production)
3
+ RAILS_MASTER_KEY= # Rails master key for encrypted credentials
4
+ RAILS_MIN_THREADS=5 # Minimum number of threads for Puma server
5
+ RAILS_MAX_THREADS=5 # Maximum number of threads for Puma server
6
+ RAILS_SERVE_STATIC_FILES= # Enable static file serving (useful for production)
7
+ RAILS_LOG_TO_STDOUT=enabled # Enable Rails logging to STDOUT
8
+
9
+ # Server Configuration
10
+ PORT=3000 # Port for the server to listen on
11
+ PIDFILE=tmp/pids/server.pid # Process ID file location
12
+ WEB_CONCURRENCY=2 # Number of web workers for Puma
13
+ DEFAULT_URL_HOST=http://localhost:4000 # Default host URL for the application
14
+ ORGANIZER_URL= # Organizer platform URL (e.g., https://organizer.bookme.plus)
15
+
16
+ # Redis Configuration
17
+ REDIS_URL=redis://localhost:6379/12 # Redis connection URL
18
+ REDIS_POOL_SIZE=5 # Redis connection pool size
19
+ REDIS_TIMEOUT=5 # Redis connection timeout in seconds
20
+
21
+ # AWS Configuration
22
+ AWS_REGION=ap-southeast-1 # AWS region for services
23
+ AWS_ACCESS_KEY_ID= # AWS access key ID for authentication
24
+ AWS_SECRET_ACCESS_KEY= # AWS secret access key for authentication
25
+ AWS_BUCKET_NAME=contigoasia-production # AWS S3 bucket name for storage
26
+ AWS_OUTPUT_BUCKET_NAME=output-production-cm # AWS S3 output bucket name for media processing
27
+ AWS_CLUSTER_NAME=production-cm-web # AWS ECS cluster name for waiting room system
28
+
29
+ # AWS CloudFront Configuration
30
+ ASSETS_SYNC_CF_DIST_ID=E3DV5NICAX5NCH # CloudFront distribution ID for asset synchronization
31
+ AWS_CF_MEDIA_DOMAIN=medias.bookme.plus # CloudFront media domain (e.g., medias.bookme.plus)
32
+ AWS_CF_PUBLIC_KEY_ID= # CloudFront public key ID for signed URLs
33
+ AWS_CF_PRIVATE_KEY= # CloudFront private key for signed URLs (PEM format)
34
+
35
+ # Cache Configuration
36
+ CONTENT_CACHE_MAX_AGE=180 # Maximum age for CDN content cache in seconds (default: 86400)
37
+
38
+ # Email Configuration
39
+ NO_REPLY_EMAIL=noreply@example.com # No-reply email address for outgoing emails
40
+
41
+ # SMS Configuration
42
+ SMS_SENDER_ID=SMS Info # Sender ID for SMS messages (default: 'SMS Info')
43
+
44
+ # Payment Gateway Configuration
45
+ VATTANAC_AES_SECRET_KEY= # Vattanac Bank AES secret key for encryption
46
+ BOOKMEPLUS_PRIVATE_KEY= # BookMe+ private key for Vattanac Bank integration
47
+ VATTANAC_PUBLIC_KEY= # Vattanac Bank public key for signature verification
48
+
49
+ # Google Services Configuration
50
+ GOOGLE_MAP_KEY=AIzaSyBt-OSnDZze6hxGjfiapSxpcyGzBo_COv8 # Google Maps API key for nearby places and location services
51
+ ISSUER_ID=3388000000022336356 # Google Wallet issuer ID for digital passes
52
+
53
+ # Waiting Room System Configuration
54
+ WAITING_ROOM_DISABLED=no # Disable waiting room functionality (set to 'yes' to disable)
55
+ WAITING_ROOM_SESSION_SIGNATURE=4234f0a3-c42f-40b5-9057-7c006793cea4 # JWT signature key for waiting room session tokens
56
+ WAITING_ROOM_SESSION_EXPIRE_DURATION_IN_SECOND=180 # Session expiration duration in seconds (default: 180 / 3 minutes)
57
+ WAITING_ROOM_MIN_SESSIONS_COUNT=5 # Minimum sessions count for waiting room calculation (default: 5)
58
+ WAITING_ROOM_SERVERS_COUNT=2 # Number of running servers for capacity calculation (default: 2)
59
+ WAITING_ROOM_MAX_THREAD_COUNT=10 # Maximum thread count per server (default: 10)
60
+ WAITING_ROOM_MULTIPLIER=150 # Multiplier for waiting room capacity calculation (default: 150)
61
+
62
+ # Exception Notification Configuration
63
+ EXCEPTION_NOTIFY_ENABLE=yes # Enable exception notifications (set to 'yes' to enable)
64
+ EXCEPTION_TELEGRAM_BOT_TOKEN=6441690414:AAFBpevRdBaRTXmalJR2vcSdPNzYoDnMEFk # Telegram bot token for exception notifications
65
+ EXCEPTION_NOTIFIER_TELEGRAM_CHANNEL_ID=-1001807686211 # Telegram channel ID for exception notifications
66
+
67
+ # Telegram Bot Configuration
68
+ DEFAULT_TELEGRAM_BOT_API_TOKEN=6441690414:AAFBpevRdBaRTXmalJR2vcSdPNzYoDnMEFk # Default Telegram bot API token
69
+ PIN_CODE_DEBUG_NOTIFIY_TELEGRAM_ENABLE=yes # Enable PIN code debug notifications via Telegram (set to 'yes' to enable)
70
+
71
+ # Business Rules Configuration
72
+ ACCOMMODATION_MAX_STAY_DAYS=10 # Maximum number of days allowed for accommodation stays (default: 10)
73
+
74
+ # Development/Testing Configuration
75
+ BUNDLE_GEMFILE= # Gemfile location for bundler
data/Gemfile.lock CHANGED
@@ -34,7 +34,7 @@ GIT
34
34
  PATH
35
35
  remote: .
36
36
  specs:
37
- spree_cm_commissioner (2.1.8)
37
+ spree_cm_commissioner (2.1.9.pre.pre1)
38
38
  activerecord-multi-tenant
39
39
  activerecord_json_validator (~> 2.1, >= 2.1.3)
40
40
  aws-sdk-cloudfront
@@ -0,0 +1,10 @@
1
+ module Spree
2
+ module Api
3
+ module V2
4
+ module Storefront
5
+ class AccessTokensController < Doorkeeper::TokensController
6
+ end
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,82 @@
1
+ module Spree
2
+ module Api
3
+ module V2
4
+ module Storefront
5
+ module Account
6
+ class GuestDynamicFieldsController < ::Spree::Api::V2::ResourceController
7
+ before_action :require_spree_current_user
8
+ before_action :load_guest
9
+
10
+ def update_dynamic_field
11
+ phase = params[:phase]
12
+
13
+ return render_error_payload('Invalid phase', 422) unless %w[pre_registration post_registration].include?(phase)
14
+
15
+ begin
16
+ fields = dynamic_fields_params
17
+ rescue ActionController::ParameterMissing
18
+ return render_error_payload('No dynamic fields provided', 422)
19
+ end
20
+
21
+ return render_error_payload('No dynamic fields provided', 422) if fields.blank?
22
+
23
+ ActiveRecord::Base.transaction do
24
+ fields.each do |field|
25
+ process_dynamic_field(field, phase)
26
+ end
27
+
28
+ @guest.save_and_move_to_next_stage
29
+ end
30
+
31
+ render_serialized_payload { serialize_guest(@guest) }
32
+ rescue ActiveRecord::RecordInvalid => e
33
+ render_error_payload(e.record.errors, 422)
34
+ rescue ActiveRecord::RecordNotFound => e
35
+ render_error_payload(e.message, 404)
36
+ end
37
+
38
+ private
39
+
40
+ def dynamic_fields_params
41
+ params.require(:dynamic_fields).map do |field|
42
+ field.permit(:id, :value)
43
+ end
44
+ end
45
+
46
+ def load_guest
47
+ @guest = SpreeCmCommissioner::Guest.find(params[:guest_id])
48
+
49
+ unless spree_current_user.guests.exists?(id: @guest.id) ||
50
+ @guest.line_item.order.user == spree_current_user
51
+ render_error_payload('Unauthorized', 401)
52
+ end
53
+ end
54
+
55
+ def process_dynamic_field(field, phase)
56
+ dynamic_field = SpreeCmCommissioner::DynamicField.find(field[:id])
57
+ guest_field = @guest.guest_dynamic_fields.find_or_initialize_by(dynamic_field_id: dynamic_field.id)
58
+
59
+ return if guest_field.new_record? && dynamic_field.data_fill_stage != phase
60
+
61
+ if dynamic_field.requires_dynamic_field_options?
62
+ option = dynamic_field.dynamic_field_options.find_by(value: field[:value])
63
+ raise ActiveRecord::RecordNotFound, "Invalid value for dynamic field #{dynamic_field.label}" unless option
64
+
65
+ guest_field.dynamic_field_option = option
66
+ guest_field.value = option.value
67
+ else
68
+ guest_field.value = field[:value]
69
+ end
70
+
71
+ guest_field.save!
72
+ end
73
+
74
+ def serialize_guest(guest)
75
+ SpreeCmCommissioner::V2::Storefront::GuestSerializer.new(guest).serializable_hash
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,39 @@
1
+ module Spree
2
+ module Api
3
+ module V2
4
+ module Storefront
5
+ module Account
6
+ class MarkGuestInfoCompleteController < ::Spree::Api::V2::ResourceController
7
+ before_action :require_spree_current_user
8
+
9
+ def update
10
+ update_user_metadata
11
+ render_success_response
12
+ end
13
+
14
+ private
15
+
16
+ def update_user_metadata
17
+ spree_current_user.public_metadata['has_incomplete_guest_info'] = false
18
+ spree_current_user.save!
19
+ end
20
+
21
+ def render_success_response
22
+ render_serialized_payload(200) do
23
+ {
24
+ success: true,
25
+ message: 'Guest information marked as complete',
26
+ has_incomplete_guest_info: false
27
+ }
28
+ end
29
+ end
30
+
31
+ def model_class
32
+ Spree::User
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -4,12 +4,41 @@ module Spree
4
4
  module Storefront
5
5
  module Account
6
6
  module OrdersControllerDecorator
7
+ def self.prepended(base)
8
+ base.include Spree::Api::V2::Storefront::OrderConcern
9
+ end
10
+
7
11
  def collection
8
- if params[:request_state].present?
9
- spree_current_user.orders.filter_by_request_state
10
- else
11
- collection_finder.new(user: spree_current_user, store: current_store, state: params.delete(:state)).execute
12
- end
12
+ @collection =
13
+ if params[:incomplete_guest_info] == 'true'
14
+ find_orders_with_incomplete_guest_info
15
+ elsif params[:request_state].present?
16
+ spree_current_user.orders.filter_by_request_state
17
+ else
18
+ collection_finder.new(user: spree_current_user, store: current_store, state: params.delete(:state)).execute
19
+ end
20
+
21
+ @collection.page(params[:page]).per(params[:per_page])
22
+ end
23
+
24
+ private
25
+
26
+ def find_orders_with_incomplete_guest_info
27
+ orders = spree_current_user.orders
28
+ .complete
29
+ .includes(line_items: { guests: [:guest_dynamic_fields] })
30
+
31
+ incomplete_order_ids = orders.select do |order|
32
+ order.line_items.any? do |line_item|
33
+ line_item.guests.any? do |guest|
34
+ guest.pre_registration_completed? &&
35
+ guest.post_registration_fields? &&
36
+ !guest.post_registration_completed?
37
+ end
38
+ end
39
+ end.map(&:id)
40
+
41
+ spree_current_user.orders.where(id: incomplete_order_ids)
13
42
  end
14
43
 
15
44
  def collection_finder
@@ -1,4 +1,4 @@
1
- # This endpoint is only used for events (via `event.seat_layout_id` to call this API).
1
+ # This endpoint is only used for events (via `event.preload_seat_layout_id` to call this API).
2
2
  # For transit, seat layouts are included directly in the Trip API (no separate call needed).
3
3
  #
4
4
  # In both cases, seat availability must always be checked using:
@@ -0,0 +1,10 @@
1
+ module Spree
2
+ module Api
3
+ module V2
4
+ module Tenant
5
+ class AccessTokensController < Doorkeeper::TokensController
6
+ end
7
+ end
8
+ end
9
+ end
10
+ end
@@ -35,7 +35,7 @@ module SpreeCmCommissioner
35
35
  end
36
36
 
37
37
  def clear_inventory_cache
38
- SpreeCmCommissioner.redis_pool.with do |redis|
38
+ SpreeCmCommissioner.inventory_redis_pool.with do |redis|
39
39
  redis.del(inventory_item.redis_key)
40
40
  end
41
41
  end
@@ -0,0 +1,39 @@
1
+ module SpreeCmCommissioner
2
+ module EventMetadata
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ store_public_metadata :background_color, :string
7
+ store_public_metadata :foreground_color, :string
8
+
9
+ store_public_metadata :group_check_in_enabled, :boolean, default: true
10
+ store_public_metadata :individual_check_in_enabled, :boolean, default: true
11
+
12
+ # By default, for most events, manual search is allowed for operators.
13
+ # For certain large events, admin can disable this manually.
14
+ store_public_metadata :allow_manual_search_for_operator, :boolean, default: true
15
+
16
+ # This model has no seat_layout column (polymorphic association), so we store the preload_seat_layout_id ID in public_metadata.
17
+ # This lets us check if a seat layout exists without triggering a database query.
18
+ # The ID is automatically updated whenever the seat_layout is saved.
19
+ store_public_metadata :preload_seat_layout_id, :integer
20
+
21
+ before_validation :validate_at_least_one_check_in_flow_presence
22
+ end
23
+
24
+ def check_in_flows
25
+ flows = []
26
+ flows << 'group' if group_check_in_enabled?
27
+ flows << 'individual' if individual_check_in_enabled?
28
+ flows
29
+ end
30
+
31
+ private
32
+
33
+ def validate_at_least_one_check_in_flow_presence
34
+ return if check_in_flows.any?
35
+
36
+ errors.add(:check_in_flows, 'must have at least one flow selected')
37
+ end
38
+ end
39
+ end
@@ -74,17 +74,40 @@ module SpreeCmCommissioner
74
74
  end
75
75
  end
76
76
 
77
+ def mark_user_as_has_incomplete_guest_info
78
+ user&.public_metadata&.[]=('has_incomplete_guest_info', true)
79
+ user&.save!
80
+ end
81
+
77
82
  def has_incomplete_guest_info? # rubocop:disable Naming/PredicateName
78
83
  line_items.any? do |line_item|
79
84
  line_item.guests.any? do |guest|
80
- !guest.post_registration_completed?
85
+ guest_incomplete?(guest)
81
86
  end
82
87
  end
83
88
  end
84
89
 
85
- def mark_user_as_has_incomplete_guest_info
86
- user&.public_metadata&.[]=('has_incomplete_guest_info', true)
87
- user&.save!
90
+ # overrided not to send email yet to user if order needs confirmation
91
+ # it will send after vendors accepted.
92
+ def confirmation_delivered?
93
+ confirmation_delivered || need_confirmation?
94
+ end
95
+
96
+ # allow authorized user to accept all requested line items
97
+ def accepted_by(user)
98
+ transaction do
99
+ accept!
100
+
101
+ line_items.each do |line_item|
102
+ line_item.accepted_by(user)
103
+ end
104
+ end
105
+ end
106
+
107
+ def guest_incomplete?(guest)
108
+ (guest.pre_registration_fields? && !guest.pre_registration_completed?) ||
109
+ (guest.post_registration_fields? && !guest.post_registration_completed?) ||
110
+ (guest.during_check_in_fields? && !guest.check_in_completed?)
88
111
  end
89
112
 
90
113
  def unstock_inventory!
@@ -159,23 +182,6 @@ module SpreeCmCommissioner
159
182
  user.present? && user.email.present?
160
183
  end
161
184
 
162
- # allow authorized user to accept all requested line items
163
- def accepted_by(user)
164
- transaction do
165
- accept!
166
-
167
- line_items.each do |line_item|
168
- line_item.accepted_by(user)
169
- end
170
- end
171
- end
172
-
173
- # overrided not to send email yet to user if order needs confirmation
174
- # it will send after vendors accepted.
175
- def confirmation_delivered?
176
- confirmation_delivered || need_confirmation?
177
- end
178
-
179
185
  # can_accepted? already use by ransack/visitor.rb
180
186
  def can_accept_all?
181
187
  approved? && requested?
@@ -0,0 +1,97 @@
1
+ # Provides a DSL for defining typed key/value attributes stored in JSON columns
2
+ # using ActiveRecord's `store`.
3
+ #
4
+ # Enhancements over ActiveRecord::Store:
5
+ # - Supports default values when a key is missing or nil
6
+ # - Applies type casting when reading from the database
7
+ # - Adds predicate helpers (`foo?`) for boolean attributes
8
+ # - Validates new data types only (does not validate existing DB values)
9
+ #
10
+ # Supported types:
11
+ # - boolean
12
+ # - string
13
+ # - integer
14
+ #
15
+ # Example usage:
16
+ # ```
17
+ # class Spree::Taxon < ApplicationRecord
18
+ # include SpreeCmCommissioner::StoreMetadata
19
+
20
+ # store_public_metadata :completed, :boolean, default: true
21
+ # store_public_metadata :count, :integer, default: 5
22
+ # store_private_metadata :app_token, :string, default: "XYZ"
23
+ # end
24
+ #
25
+ # taxon = Spree::Taxon.new(completed: true, app_token: "ABC", count: 10)
26
+ # taxon.completed # => true, store in public_metadata
27
+ # taxon.completed? # => true, store in public_metadata
28
+ # taxon.count # => 10, store in public_metadata
29
+ # taxon.app_token # => "ABC", store in private_metadata
30
+ # ```
31
+
32
+ module SpreeCmCommissioner
33
+ module StoreMetadata
34
+ extend ActiveSupport::Concern
35
+
36
+ class_methods do
37
+ def store_private_metadata(key, type, default: nil)
38
+ store :private_metadata, accessors: [key], coder: JSON
39
+
40
+ define_metadata_reader(:private_metadata, key, type, default)
41
+ define_metadata_validation(:private_metadata, key, type)
42
+ end
43
+
44
+ def store_public_metadata(key, type, default: nil)
45
+ store :public_metadata, accessors: [key], coder: JSON
46
+
47
+ define_metadata_reader(:public_metadata, key, type, default)
48
+ define_metadata_validation(:public_metadata, key, type)
49
+ end
50
+
51
+ private
52
+
53
+ # Override the store reader to:
54
+ # - Return default if nil
55
+ # - Cast to correct type
56
+ # - Add `?` predicate for booleans
57
+ def define_metadata_reader(column_name, key, type, default)
58
+ define_method(key) do
59
+ metadata = send(column_name) || {}
60
+ raw_value = metadata[key.to_s]
61
+ return default if raw_value.nil?
62
+
63
+ case type
64
+ when :boolean
65
+ ActiveModel::Type::Boolean.new.cast(raw_value)
66
+ when :integer
67
+ raw_value.to_i
68
+ when :string
69
+ raw_value.to_s
70
+ else
71
+ raw_value
72
+ end
73
+ end
74
+
75
+ define_method("#{key}?") { send(key) } if type == :boolean
76
+ end
77
+
78
+ # Validates only new assignments (raw JSON values) for type safety
79
+ def define_metadata_validation(column_name, key, type)
80
+ case type
81
+ when :boolean
82
+ validates key, inclusion: { in: [true, false] }, allow_nil: true
83
+ when :integer
84
+ validate do
85
+ metadata = send(column_name) || {}
86
+ raw_value = metadata[key.to_s]
87
+ next if raw_value.nil?
88
+
89
+ errors.add(key, 'is not a number') unless raw_value.to_s =~ /\A-?\d+\z/
90
+ end
91
+ when :string
92
+ validates key, length: { maximum: 255 }, allow_nil: true
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
@@ -57,10 +57,9 @@ module SpreeCmCommissioner
57
57
  before_create :generate_bib, if: -> { line_item.reload && variant.bib_pre_generation_on_create? }
58
58
  after_create :preload_order_block_ids, if: -> { block_id.present? }
59
59
  after_update :preload_order_block_ids, if: :saved_change_to_block_id?
60
-
61
60
  before_destroy :cancel_reserved_block!, if: -> { reserved_block.present? && reserved_block.active_on_hold? }
62
-
63
61
  after_destroy :preload_order_block_ids
62
+ after_commit :update_user_incomplete_guest_info_status, if: :should_update_incomplete_guest_info?
64
63
 
65
64
  validates :bib_index, uniqueness: true, allow_nil: true
66
65
 
@@ -396,8 +395,6 @@ module SpreeCmCommissioner
396
395
  eligible_check_in_sessions.include?(session)
397
396
  end
398
397
 
399
- private
400
-
401
398
  def product_dynamic_fields
402
399
  @product_dynamic_fields ||= line_item.product.dynamic_fields
403
400
  end
@@ -417,6 +414,43 @@ module SpreeCmCommissioner
417
414
  # Reject if dynamic_field_id is blank or if _destroy is true
418
415
  attributes[:dynamic_field_id].blank? || ActiveModel::Type::Boolean.new.cast(attributes[:_destroy])
419
416
  end
417
+
418
+ def update_user_incomplete_guest_info_status
419
+ return unless user
420
+ return unless line_item&.order&.completed?
421
+
422
+ user.reload
423
+
424
+ has_incomplete = user.orders.complete.any? do |order|
425
+ order_has_incomplete_guests?(order)
426
+ end
427
+
428
+ current_status = user.public_metadata['has_incomplete_guest_info'] || false
429
+
430
+ return unless current_status != has_incomplete
431
+
432
+ user.public_metadata['has_incomplete_guest_info'] = has_incomplete
433
+ user.save!
434
+ end
435
+
436
+ def order_has_incomplete_guests?(order)
437
+ order.line_items.any? do |line_item|
438
+ line_item.guests.any? do |guest|
439
+ guest_incomplete?(guest)
440
+ end
441
+ end
442
+ end
443
+
444
+ def guest_incomplete?(guest)
445
+ (guest.pre_registration_fields? && !guest.pre_registration_completed?) ||
446
+ (guest.post_registration_fields? && !guest.post_registration_completed?) ||
447
+ (guest.during_check_in_fields? && !guest.check_in_completed?)
448
+ end
449
+
450
+ def should_update_incomplete_guest_info?
451
+ saved_change_to_data_fill_stage_phase? ||
452
+ (guest_dynamic_fields.any? && (guest_dynamic_fields.any?(&:saved_changes?) || guest_dynamic_fields.any?(&:destroyed?)))
453
+ end
420
454
  end
421
455
  end
422
456
  # rubocop:enable Metrics/ClassLength
@@ -88,6 +88,7 @@ module SpreeCmCommissioner
88
88
 
89
89
  def sync_guest_data_fill_stage_phase
90
90
  guest&.save_and_move_to_next_stage
91
+ guest&.update_user_incomplete_guest_info_status
91
92
  end
92
93
 
93
94
  def reject_empty_string_for_text_fields
@@ -48,7 +48,7 @@ module SpreeCmCommissioner
48
48
  end
49
49
 
50
50
  def adjust_quantity_in_redis(quantity)
51
- SpreeCmCommissioner.redis_pool.with do |redis|
51
+ SpreeCmCommissioner.inventory_redis_pool.with do |redis|
52
52
  cached_quantity_available = redis.get(redis_key)
53
53
  # ignore if redis doesn't exist
54
54
  return if cached_quantity_available.nil? # rubocop:disable Lint/NonLocalExitFromIterator
@@ -14,6 +14,8 @@ module SpreeCmCommissioner
14
14
 
15
15
  validate :validate_roles
16
16
 
17
+ before_validation :normalize_email
18
+
17
19
  after_create :set_expiration
18
20
  after_create :send_team_invite_email
19
21
 
@@ -42,5 +44,11 @@ module SpreeCmCommissioner
42
44
  def url_valid?
43
45
  expires_at.present? && expires_at > Time.current
44
46
  end
47
+
48
+ private
49
+
50
+ def normalize_email
51
+ self.email = email.strip.downcase if email.present?
52
+ end
45
53
  end
46
54
  end
@@ -12,7 +12,7 @@ module SpreeCmCommissioner
12
12
  keys = inventory_items.map { |item| "inventory:#{item.id}" }
13
13
  return [] unless keys.any?
14
14
 
15
- counts = SpreeCmCommissioner.redis_pool.with { |redis| redis.mget(*keys) }
15
+ counts = SpreeCmCommissioner.inventory_redis_pool.with { |redis| redis.mget(*keys) }
16
16
  inventory_items.map.with_index do |inventory_item, i|
17
17
  ::SpreeCmCommissioner::CachedInventoryItem.new(
18
18
  inventory_key: keys[i],
@@ -30,7 +30,7 @@ module SpreeCmCommissioner
30
30
  return count_in_redis.to_i if count_in_redis.present?
31
31
  return inventory_item.quantity_available unless inventory_item.active?
32
32
 
33
- SpreeCmCommissioner.redis_pool.with do |redis|
33
+ SpreeCmCommissioner.inventory_redis_pool.with do |redis|
34
34
  redis.set(key, inventory_item.quantity_available, ex: inventory_item.redis_expired_in)
35
35
  end
36
36
 
@@ -51,13 +51,13 @@ module SpreeCmCommissioner
51
51
  end
52
52
 
53
53
  def unstock(keys, quantities)
54
- SpreeCmCommissioner.redis_pool.with do |redis|
54
+ SpreeCmCommissioner.inventory_redis_pool.with do |redis|
55
55
  redis.eval(unstock_redis_script, keys: keys, argv: quantities)
56
56
  end.positive?
57
57
  end
58
58
 
59
59
  def restock(keys, quantities)
60
- SpreeCmCommissioner.redis_pool.with do |redis|
60
+ SpreeCmCommissioner.inventory_redis_pool.with do |redis|
61
61
  redis.eval(restock_redis_script, keys: keys, argv: quantities)
62
62
  end.positive?
63
63
  end
@@ -27,13 +27,13 @@ module SpreeCmCommissioner
27
27
 
28
28
  private
29
29
 
30
- # layoutable (taxon, vehicle) must have seat_layout_id attribute,
30
+ # layoutable (taxon, vehicle) must have preload_seat_layout_id attribute,
31
31
  # either as a column or as a key in a JSONB column (e.g., metadata)
32
32
  def sync_seat_layout_id_to_layoutable!
33
33
  if destroyed?
34
- layoutable.update!(seat_layout_id: nil)
34
+ layoutable.update!(preload_seat_layout_id: nil)
35
35
  else
36
- layoutable.update!(seat_layout_id: id)
36
+ layoutable.update!(preload_seat_layout_id: id)
37
37
  end
38
38
  end
39
39
  end
@@ -1,20 +1,10 @@
1
1
  module SpreeCmCommissioner
2
2
  module TaxonDecorator
3
3
  def self.prepended(base) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
4
+ base.include SpreeCmCommissioner::StoreMetadata
4
5
  base.include SpreeCmCommissioner::TaxonKind
5
6
  base.include SpreeCmCommissioner::ParticipationTypeBitwise
6
- base.include SpreeCmCommissioner::EventCheckInFlowable
7
-
8
- base.preference :background_color, :string
9
- base.preference :foreground_color, :string
10
-
11
- # seat_layout_id:
12
- # This model has no seat_layout column (polymorphic association), so we store the seat_layout ID in public_metadata.
13
- # This lets us check if a seat layout exists without triggering a database query.
14
- # The ID is automatically updated whenever the seat_layout is saved.
15
- base.store :public_metadata, accessors: %i[
16
- seat_layout_id
17
- ], coder: JSON
7
+ base.include SpreeCmCommissioner::EventMetadata
18
8
 
19
9
  base.has_many :taxon_vendors, class_name: 'SpreeCmCommissioner::TaxonVendor'
20
10
  base.has_many :vendors, through: :taxon_vendors
@@ -104,14 +94,6 @@ module SpreeCmCommissioner
104
94
  end
105
95
  end
106
96
 
107
- def background_color
108
- preferred_background_color
109
- end
110
-
111
- def foreground_color
112
- preferred_foreground_color
113
- end
114
-
115
97
  def set_kind
116
98
  self.kind = taxonomy.kind
117
99
  end
@@ -1,16 +1,14 @@
1
1
  module SpreeCmCommissioner
2
2
  class Trip < Base
3
+ include SpreeCmCommissioner::StoreMetadata
3
4
  include SpreeCmCommissioner::RouteType
4
5
 
5
6
  attr_accessor :hours, :minutes, :seconds
6
7
 
7
- # seat_layout_id:
8
- # This model has no seat_layout column (polymorphic association), so we store the seat_layout ID in public_metadata.
9
- # This lets us check if a seat layout exists without triggering a database query.
10
- # The ID is automatically updated whenever the seat_layout is saved.
11
- store :public_metadata, accessors: %i[
12
- seat_layout_id
13
- ], coder: JSON
8
+ # This model has no seat_layout column (polymorphic association), so we store the preload_seat_layout_id ID in public_metadata.
9
+ # This lets us check if a seat layout exists without triggering a database query.
10
+ # The ID is automatically updated whenever the seat_layout is saved.
11
+ store_public_metadata :preload_seat_layout_id, :integer
14
12
 
15
13
  before_validation :convert_duration_to_seconds
16
14
 
@@ -1,14 +1,12 @@
1
1
  module SpreeCmCommissioner
2
2
  class Vehicle < Base
3
+ include SpreeCmCommissioner::StoreMetadata
3
4
  include SpreeCmCommissioner::VehicleType
4
5
 
5
- # seat_layout_id:
6
- # This model has no seat_layout column (polymorphic association), so we store the seat_layout ID in public_metadata.
7
- # This lets us check if a seat layout exists without triggering a database query.
8
- # The ID is automatically updated whenever the seat_layout is saved.
9
- store :public_metadata, accessors: %i[
10
- seat_layout_id
11
- ], coder: JSON
6
+ # This model has no seat_layout column (polymorphic association), so we store the preload_seat_layout_id ID in public_metadata.
7
+ # This lets us check if a seat layout exists without triggering a database query.
8
+ # The ID is automatically updated whenever the seat_layout is saved.
9
+ store_public_metadata :preload_seat_layout_id, :integer
12
10
 
13
11
  belongs_to :vendor, class_name: 'Spree::Vendor'
14
12
 
@@ -4,17 +4,17 @@
4
4
 
5
5
  <div class="row <%= "d-none" unless @taxon.depth == 1 %>">
6
6
  <div class="col-md-6">
7
- <%= f.label :preferred_background_color, Spree.t('background_color'), class: 'form-label' %>
8
- <%= f.text_field :preferred_background_color, class: 'form-control color-picker', placeholder: '#FFFFFF', value: @object.preferred_background_color %>
7
+ <%= f.label :background_color, Spree.t('background_color'), class: 'form-label' %>
8
+ <%= f.text_field :background_color, class: 'form-control color-picker', placeholder: '#FFFFFF', value: @object.background_color %>
9
9
  <div class="color-preview" style="margin-top: 10px;">
10
- <%= content_tag :div, nil, style: "background-color: #{@object.preferred_background_color}; width: 60px; height: 60px; border: 1px solid #ccc; box-sizing: border-box; display: block;" %>
10
+ <%= content_tag :div, nil, style: "background-color: #{@object.background_color}; width: 60px; height: 60px; border: 1px solid #ccc; box-sizing: border-box; display: block;" %>
11
11
  </div>
12
12
  </div>
13
13
  <div class="col-md-6">
14
- <%= f.label :preferred_foreground_color, Spree.t('foreground_color'), class: 'form-label' %>
15
- <%= f.text_field :preferred_foreground_color, class: 'form-control color-picker', placeholder: '#FFFFFF', value: @object.preferred_foreground_color %>
14
+ <%= f.label :foreground_color, Spree.t('foreground_color'), class: 'form-label' %>
15
+ <%= f.text_field :foreground_color, class: 'form-control color-picker', placeholder: '#FFFFFF', value: @object.foreground_color %>
16
16
  <div class="color-preview" style="margin-top: 10px;">
17
- <%= content_tag :div, nil, style: "background-color: #{@object.preferred_foreground_color}; width: 60px; height: 60px; border: 1px solid #ccc; box-sizing: border-box; display: block;" %>
17
+ <%= content_tag :div, nil, style: "background-color: #{@object.foreground_color}; width: 60px; height: 60px; border: 1px solid #ccc; box-sizing: border-box; display: block;" %>
18
18
  </div>
19
19
  </div>
20
20
  </div>
@@ -2,17 +2,23 @@
2
2
 
3
3
  <%# Sections don't use video banner, so we hide the form here to avoid confusing admin. %>
4
4
 
5
- <%= f.field_container :preferred_group_check_in_enabled, class: ['custom-control', 'custom-checkbox', 'my-4'] do %>
6
- <%= f.check_box :preferred_group_check_in_enabled, class: 'custom-control-input' %>
7
- <%= f.label :preferred_group_check_in_enabled, Spree.t(:enable_group_check_in), class: 'custom-control-label' %>
8
- <%= f.error_message_on :preferred_group_check_in_enabled %>
5
+ <%= f.field_container :group_check_in_enabled, class: ['custom-control', 'custom-checkbox', 'my-4'] do %>
6
+ <%= f.check_box :group_check_in_enabled, class: 'custom-control-input' %>
7
+ <%= f.label :group_check_in_enabled, Spree.t(:enable_group_check_in), class: 'custom-control-label' %>
8
+ <%= f.error_message_on :group_check_in_enabled %>
9
9
  <small class="form-text text-muted">
10
10
  Enabled by default for most events where users can purchase multiple tickets or use group scanning. For large events (like PSK, ASK, etc.) or invitation-based events, you should disable this to ensure each ticket or invitation is scanned individually.
11
11
  </small>
12
12
  <% end if @taxon.event? && @taxon.depth == 1 %> %>
13
13
 
14
- <%= f.field_container :preferred_individual_check_in_enabled, class: ['custom-control', 'custom-checkbox', 'my-4'] do %>
15
- <%= f.check_box :preferred_individual_check_in_enabled, class: 'custom-control-input' %>
16
- <%= f.label :preferred_individual_check_in_enabled, Spree.t(:enable_individual_check_in), class: 'custom-control-label' %>
17
- <%= f.error_message_on :preferred_individual_check_in_enabled %>
14
+ <%= f.field_container :individual_check_in_enabled, class: ['custom-control', 'custom-checkbox', 'my-4'] do %>
15
+ <%= f.check_box :individual_check_in_enabled, class: 'custom-control-input' %>
16
+ <%= f.label :individual_check_in_enabled, Spree.t(:enable_individual_check_in), class: 'custom-control-label' %>
17
+ <%= f.error_message_on :individual_check_in_enabled %>
18
+ <% end if @taxon.event? && @taxon.depth == 1 %> %>
19
+
20
+ <%= f.field_container :allow_manual_search_for_operator, class: ['custom-control', 'custom-checkbox', 'my-4'] do %>
21
+ <%= f.check_box :allow_manual_search_for_operator, class: 'custom-control-input' %>
22
+ <%= f.label :allow_manual_search_for_operator, Spree.t(:allow_manual_search_for_operator), class: 'custom-control-label' %>
23
+ <%= f.error_message_on :allow_manual_search_for_operator %>
18
24
  <% end if @taxon.event? && @taxon.depth == 1 %> %>
@@ -0,0 +1,63 @@
1
+ module Spree
2
+ module V2
3
+ module Storefront
4
+ module OrderSerializerDecorator
5
+ def self.prepended(base)
6
+ base.attribute :has_incomplete_guest_info do |order|
7
+ order.user.public_metadata['has_incomplete_guest_info'] || false
8
+ end
9
+
10
+ base.attribute :guest_info_status do |order|
11
+ {
12
+ incomplete: order_has_incomplete_guests?(order),
13
+ can_mark_complete: can_mark_complete?(order)
14
+ }
15
+ end
16
+ end
17
+
18
+ def self.order_has_incomplete_guests?(order)
19
+ order.line_items.any? do |line_item|
20
+ line_item.guests.any? do |guest|
21
+ guest_incomplete?(guest)
22
+ end
23
+ end
24
+ end
25
+
26
+ def self.guest_incomplete?(guest)
27
+ (guest.pre_registration_fields? && !guest.pre_registration_completed?) ||
28
+ (guest.post_registration_fields? && !guest.post_registration_completed?) ||
29
+ (guest.during_check_in_fields? && !guest.check_in_completed?)
30
+ end
31
+
32
+ def self.can_mark_complete?(order)
33
+ event_ended?(order) || all_guest_info_complete?(order)
34
+ end
35
+
36
+ def self.event_ended?(order)
37
+ order.line_items.any? { |line_item| event_ended_for_line_item?(line_item) }
38
+ end
39
+
40
+ def self.event_ended_for_line_item?(line_item)
41
+ event = line_item.event
42
+ event&.to_date.present? && event.to_date < Time.zone.today
43
+ end
44
+
45
+ def self.all_guest_info_complete?(order)
46
+ order.line_items.all? { |line_item| line_item_guests_complete?(line_item) }
47
+ end
48
+
49
+ def self.line_item_guests_complete?(line_item)
50
+ line_item.guests.all? { |guest| guest_complete?(guest) }
51
+ end
52
+
53
+ def self.guest_complete?(guest)
54
+ guest.pre_registration_completed? &&
55
+ (!guest.post_registration_fields? || guest.post_registration_completed?) &&
56
+ (!guest.during_check_in_fields? || guest.check_in_completed?)
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+
63
+ Spree::V2::Storefront::OrderSerializer.prepend(Spree::V2::Storefront::OrderSerializerDecorator)
@@ -18,7 +18,7 @@ module Spree
18
18
  :purchasable_on, :vendor_id, :available_on, :hide_video_banner
19
19
 
20
20
  # To get full seat layout details, call the seat_layouts API with this ID.
21
- base.attributes :seat_layout_id
21
+ base.attribute :seat_layout_id, &:preload_seat_layout_id
22
22
 
23
23
  base.attribute :purchasable_on_app do |taxon|
24
24
  taxon.purchasable_on == 'app' || taxon.purchasable_on == 'both'
@@ -5,6 +5,7 @@ module SpreeCmCommissioner
5
5
  attributes :id, :name, :permalink,
6
6
  :from_date, :to_date,
7
7
  :check_in_flows,
8
+ :allow_manual_search_for_operator,
8
9
  :updated_at
9
10
 
10
11
  has_many :children_classifications, serializer: :classification
@@ -31,10 +31,11 @@ module Spree
31
31
  from_date
32
32
  kind
33
33
  subtitle
34
- preferred_background_color
35
- preferred_foreground_color
36
- preferred_group_check_in_enabled
37
- preferred_individual_check_in_enabled
34
+ background_color
35
+ foreground_color
36
+ group_check_in_enabled
37
+ individual_check_in_enabled
38
+ allow_manual_search_for_operator
38
39
  show_badge_status
39
40
  purchasable_on
40
41
  available_on
data/config/routes.rb CHANGED
@@ -477,6 +477,7 @@ Spree::Core::Engine.add_routes do
477
477
  end
478
478
 
479
479
  namespace :tenant do
480
+ resource :token, controller: :access_tokens, only: [:create]
480
481
  resources :vendors
481
482
  resources :products
482
483
  resources :taxons, only: %i[index show], id: /.+/
@@ -560,6 +561,7 @@ Spree::Core::Engine.add_routes do
560
561
  end
561
562
 
562
563
  namespace :storefront do
564
+ resource :token, controller: :access_tokens, only: [:create]
563
565
  resources :waiting_room_sessions, only: :create
564
566
  resources :vattanac_banks, only: %i[create]
565
567
  resource :cart, controller: :cart, only: %i[show create destroy] do
@@ -599,6 +601,10 @@ Spree::Core::Engine.add_routes do
599
601
 
600
602
  namespace :account do
601
603
  resource :preferred_payment_method, controller: :preferred_payment_method, only: %i[show update]
604
+ patch :mark_guest_info_complete, to: 'mark_guest_info_complete#update'
605
+ resources :guests, only: [] do
606
+ patch 'dynamic_fields/:phase', to: 'guest_dynamic_fields#update_dynamic_field', as: :update_dynamic_fields
607
+ end
602
608
  end
603
609
 
604
610
  resource :s3_signed_urls
@@ -0,0 +1,84 @@
1
+ # Scoped Access Token Endpoints
2
+
3
+ This document describes the new scoped access token endpoints that provide better security and clarity for different API usage patterns.
4
+
5
+ ## Overview
6
+
7
+ Previously, all access tokens were retrieved via the generic `/oauth/token` endpoint, which made it difficult to apply security rules and prevent abuse. The new scoped endpoints provide:
8
+
9
+ - **Clear separation** between storefront and tenant API usage
10
+ - **Enhanced security** with application-specific validations
11
+ - **Better monitoring** and logging capabilities
12
+ - **Easier maintenance** and debugging
13
+
14
+ ## Endpoints
15
+
16
+ ### Storefront Access Token Endpoint
17
+
18
+ **POST** `/api/v2/storefront/token`
19
+
20
+ Issues access tokens for storefront applications.
21
+
22
+ #### Example Response
23
+
24
+ ```json
25
+ {
26
+ "access_token": "abc123...",
27
+ "token_type": "Bearer",
28
+ "expires_in": 86400,
29
+ "refresh_token": "def456...",
30
+ "created_at": 1640995200
31
+ }
32
+ ```
33
+
34
+ ### Tenant Access Token Endpoint
35
+
36
+ **POST** `/api/v2/tenant/token`
37
+
38
+ Issues access tokens for tenant applications (third-party integrations, partner apps, etc.).
39
+
40
+ #### Example Response
41
+
42
+ ```json
43
+ {
44
+ "access_token": "xyz789...",
45
+ "token_type": "Bearer",
46
+ "expires_in": 86400,
47
+ "refresh_token": "uvw012...",
48
+ "created_at": 1640995200
49
+ }
50
+ ```
51
+
52
+ ## Migration Guide
53
+
54
+ ### For Storefront Applications
55
+
56
+ 1. **Update endpoint URL**: Change from `/oauth/token` to `/api/v2/storefront/token`
57
+ 2. **No other changes required**: All existing parameters and flows work identically
58
+
59
+ ### For Tenant Applications
60
+
61
+ 1. **Update endpoint URL**: Change from `/oauth/token` to `/api/v2/tenant/token`
62
+ 2. **No other changes required**: All existing parameters and flows work identically
63
+
64
+ ### Legacy Support
65
+
66
+ The original `/oauth/token` endpoint remains available.
67
+
68
+ ## Implementation
69
+
70
+ ### Controller Inheritance
71
+
72
+ Both controllers inherit from `Doorkeeper::TokensController`.
73
+
74
+ ### Route Configuration
75
+
76
+ ```ruby
77
+ namespace :storefront do
78
+ resource :token, controller: :access_tokens, only: [:create]
79
+ end
80
+
81
+ namespace :tenant do
82
+ resource :token, controller: :access_tokens, only: [:create]
83
+ end
84
+ ```
@@ -1,5 +1,5 @@
1
1
  module SpreeCmCommissioner
2
- VERSION = '2.1.8'.freeze
2
+ VERSION = '2.1.9-pre1'.freeze
3
3
 
4
4
  module_function
5
5
 
@@ -52,27 +52,49 @@ require 'byebug' if Rails.env.development? || Rails.env.test?
52
52
 
53
53
  module SpreeCmCommissioner
54
54
  class << self
55
- # Allows overriding the default Redis connection pool with a custom one
56
- attr_writer :redis_pool
55
+ # Allows overriding the default Redis connection pools with custom ones
56
+ attr_writer :inventory_redis_pool, :external_integrations_redis_pool
57
57
 
58
- def redis_pool
59
- @redis_pool ||= default_redis_pool
58
+ # Inventory Redis pool for inventory management
59
+ def inventory_redis_pool
60
+ @inventory_redis_pool ||= default_inventory_redis_pool
60
61
  end
61
62
 
62
- # Resets the Redis pool, useful for testing or reinitialization
63
- def reset_redis_pool
64
- @redis_pool = nil
63
+ # External integrations Redis pool for external API integrations
64
+ def external_integrations_redis_pool
65
+ @external_integrations_redis_pool ||= default_external_integrations_redis_pool
66
+ end
67
+
68
+ # Resets all Redis pools, useful for testing or reinitialization
69
+ def reset_redis_pools
70
+ @inventory_redis_pool = nil
71
+ @external_integrations_redis_pool = nil
65
72
  end
66
73
 
67
74
  private
68
75
 
69
- def default_redis_pool
70
- pool_size = ENV.fetch('REDIS_POOL_SIZE', '5').to_i
71
- timeout = ENV.fetch('REDIS_TIMEOUT', '5').to_i
72
- redis_url = ENV.fetch('REDIS_URL', 'redis://localhost:6379/12')
76
+ def default_inventory_redis_pool
77
+ create_redis_pool(
78
+ url: ENV.fetch('REDIS_INVENTORY_URL', ENV.fetch('REDIS_URL', 'redis://localhost:6379/12')),
79
+ pool_size: ENV.fetch('REDIS_INVENTORY_POOL_SIZE', ENV.fetch('REDIS_POOL_SIZE', '5')).to_i,
80
+ timeout: ENV.fetch('REDIS_TIMEOUT', '5').to_i
81
+ )
82
+ end
83
+
84
+ def default_external_integrations_redis_pool
85
+ create_redis_pool(
86
+ url: ENV.fetch('REDIS_EXTERNAL_INTEGRATIONS_URL', ENV.fetch('REDIS_URL', 'redis://localhost:6379/13')),
87
+ pool_size: ENV.fetch('REDIS_EXTERNAL_INTEGRATIONS_POOL_SIZE', ENV.fetch('REDIS_POOL_SIZE', '5')).to_i,
88
+ timeout: ENV.fetch('REDIS_TIMEOUT', '5').to_i
89
+ )
90
+ end
91
+
92
+ def create_redis_pool(url:, pool_size:, timeout:)
93
+ ssl_params = {}
94
+ ssl_params.merge!({ verify_mode: OpenSSL::SSL::VERIFY_NONE }) if ENV['HEROKU_APP_ID'].present?
73
95
 
74
96
  ConnectionPool.new(size: pool_size, timeout: timeout) do
75
- Redis.new(url: redis_url, timeout: timeout)
97
+ Redis.new(url: url, timeout: timeout, ssl_params: ssl_params)
76
98
  end
77
99
  end
78
100
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spree_cm_commissioner
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.8
4
+ version: 2.1.9.pre.pre1
5
5
  platform: ruby
6
6
  authors:
7
7
  - You
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-10-01 00:00:00.000000000 Z
11
+ date: 2025-10-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: spree
@@ -909,8 +909,11 @@ files:
909
909
  - app/controllers/spree/api/v2/platform/seat_number_layouts_controller.rb
910
910
  - app/controllers/spree/api/v2/platform/taxons_controller_decorator.rb
911
911
  - app/controllers/spree/api/v2/resource_controller_decorator.rb
912
+ - app/controllers/spree/api/v2/storefront/access_tokens_controller.rb
912
913
  - app/controllers/spree/api/v2/storefront/accommodations/variants_controller.rb
913
914
  - app/controllers/spree/api/v2/storefront/accommodations_controller.rb
915
+ - app/controllers/spree/api/v2/storefront/account/guest_dynamic_fields_controller.rb
916
+ - app/controllers/spree/api/v2/storefront/account/mark_guest_info_complete_controller.rb
914
917
  - app/controllers/spree/api/v2/storefront/account/orders_controller_decorator.rb
915
918
  - app/controllers/spree/api/v2/storefront/account/preferred_payment_method_controller.rb
916
919
  - app/controllers/spree/api/v2/storefront/account_checker_controller.rb
@@ -978,6 +981,7 @@ files:
978
981
  - app/controllers/spree/api/v2/storefront/waiting_room_sessions_controller.rb
979
982
  - app/controllers/spree/api/v2/storefront/wished_items_controller.rb
980
983
  - app/controllers/spree/api/v2/storefront/wishlists_controller_decorator.rb
984
+ - app/controllers/spree/api/v2/tenant/access_tokens_controller.rb
981
985
  - app/controllers/spree/api/v2/tenant/account/orders_controller.rb
982
986
  - app/controllers/spree/api/v2/tenant/account_checker_controller.rb
983
987
  - app/controllers/spree/api/v2/tenant/account_controller.rb
@@ -1309,7 +1313,7 @@ files:
1309
1313
  - app/mailers/spree_cm_commissioner/pin_code_mailer.rb
1310
1314
  - app/mailers/spree_cm_commissioner/team_invite_mailer.rb
1311
1315
  - app/models/.gitkeep
1312
- - app/models/concerns/spree_cm_commissioner/event_check_in_flowable.rb
1316
+ - app/models/concerns/spree_cm_commissioner/event_metadata.rb
1313
1317
  - app/models/concerns/spree_cm_commissioner/homepage_section_bitwise.rb
1314
1318
  - app/models/concerns/spree_cm_commissioner/json_preference_validator.rb
1315
1319
  - app/models/concerns/spree_cm_commissioner/kyc_bitwise.rb
@@ -1329,6 +1333,7 @@ files:
1329
1333
  - app/models/concerns/spree_cm_commissioner/product_type.rb
1330
1334
  - app/models/concerns/spree_cm_commissioner/route_type.rb
1331
1335
  - app/models/concerns/spree_cm_commissioner/service_calendar_type.rb
1336
+ - app/models/concerns/spree_cm_commissioner/store_metadata.rb
1332
1337
  - app/models/concerns/spree_cm_commissioner/store_preference.rb
1333
1338
  - app/models/concerns/spree_cm_commissioner/taxon_kind.rb
1334
1339
  - app/models/concerns/spree_cm_commissioner/tenant_preference.rb
@@ -1738,6 +1743,7 @@ files:
1738
1743
  - app/serializers/spree/v2/storefront/notification_serializer.rb
1739
1744
  - app/serializers/spree/v2/storefront/option_type_serializer_decorator.rb
1740
1745
  - app/serializers/spree/v2/storefront/option_value_serializer_decorator.rb
1746
+ - app/serializers/spree/v2/storefront/order_serializer_decorator.rb
1741
1747
  - app/serializers/spree/v2/storefront/place_serializer.rb
1742
1748
  - app/serializers/spree/v2/storefront/product_serializer_decorator.rb
1743
1749
  - app/serializers/spree/v2/storefront/role_serializer.rb
@@ -2773,6 +2779,7 @@ files:
2773
2779
  - db/migrate/20250930094228_add_application_id_to_cm_pin_codes.rb
2774
2780
  - db/migrate/20250930100657_add_settings_to_spree_oauth_applications.rb
2775
2781
  - docker-compose.yml
2782
+ - docs/api/scoped-access-token-endpoints.md
2776
2783
  - docs/option_types/attr_types.md
2777
2784
  - docs/private_key.pem
2778
2785
  - docs/public_key.pem
@@ -2931,9 +2938,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
2931
2938
  version: '2.7'
2932
2939
  required_rubygems_version: !ruby/object:Gem::Requirement
2933
2940
  requirements:
2934
- - - ">="
2941
+ - - ">"
2935
2942
  - !ruby/object:Gem::Version
2936
- version: '0'
2943
+ version: 1.3.1
2937
2944
  requirements:
2938
2945
  - none
2939
2946
  rubygems_version: 3.4.1
@@ -1,30 +0,0 @@
1
- module SpreeCmCommissioner
2
- module EventCheckInFlowable
3
- extend ActiveSupport::Concern
4
-
5
- included do
6
- preference :group_check_in_enabled, :boolean, default: true
7
- preference :individual_check_in_enabled, :boolean, default: true
8
-
9
- before_validation :validate_at_least_one_check_in_flow_presence
10
- end
11
-
12
- def check_in_flows
13
- flows = []
14
- flows << 'group' if group_check_in_enabled?
15
- flows << 'individual' if individual_check_in_enabled?
16
- flows
17
- end
18
-
19
- def group_check_in_enabled? = preferred_group_check_in_enabled
20
- def individual_check_in_enabled? = preferred_individual_check_in_enabled
21
-
22
- private
23
-
24
- def validate_at_least_one_check_in_flow_presence
25
- return if check_in_flows.any?
26
-
27
- errors.add(:check_in_flows, 'must have at least one flow selected')
28
- end
29
- end
30
- end