spree_cm_commissioner 2.9.1.pre.pre1 → 2.9.1.pre.pre3

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 (24) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +1 -1
  3. data/app/controllers/spree/api/v2/storefront/waiting_room_sessions_controller.rb +8 -1
  4. data/app/controllers/spree/api/v2/tenant/voting_contestants_controller.rb +1 -2
  5. data/app/controllers/spree/api/v2/tenant/waiting_room_sessions_controller.rb +8 -1
  6. data/app/controllers/spree_cm_commissioner/admin/orders_controller_decorator.rb +9 -0
  7. data/app/controllers/spree_cm_commissioner/api/v2/storefront/checkout_controller_decorator.rb +14 -1
  8. data/app/interactors/spree_cm_commissioner/waiting_guests_caller.rb +11 -2
  9. data/app/interactors/spree_cm_commissioner/waiting_room_session_creator.rb +31 -6
  10. data/app/models/spree_cm_commissioner/current.rb +8 -0
  11. data/app/models/spree_cm_commissioner/order_decorator.rb +6 -0
  12. data/app/models/spree_cm_commissioner/voting_contestant.rb +6 -5
  13. data/app/models/spree_cm_commissioner/waiting_room_session.rb +14 -0
  14. data/app/overrides/spree/admin/shared/_order_summary/internal_note.html.erb.deface +23 -0
  15. data/app/overrides/spree/admin/shared/_order_summary/internal_note_modal.html.erb.deface +40 -0
  16. data/app/serializers/spree/v2/tenant/show_episode_serializer.rb +1 -9
  17. data/app/services/spree_cm_commissioner/inventory_holds/validate_limits.rb +1 -1
  18. data/config/locales/en.yml +5 -0
  19. data/config/locales/km.yml +5 -0
  20. data/config/routes.rb +1 -0
  21. data/db/migrate/20260701000001_add_device_info_to_cm_waiting_room_sessions.rb +14 -0
  22. data/db/migrate/20260703065239_add_position_to_cm_voting_contestants.rb +6 -0
  23. data/lib/spree_cm_commissioner/version.rb +1 -1
  24. metadata +7 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a0545736f33006daa20a5af6f1e283c85343fab4e3a163cdf4e69135ed1a4f0b
4
- data.tar.gz: 0edd768156e510c947f7b938059d757f208eafedc037c9a635b68c43d69c6af8
3
+ metadata.gz: 84c27f4187a23acbe9422e5b33cba6cca077dee5fe7a5e879acd3a5d06ee20c9
4
+ data.tar.gz: e46311b9f88d5a32ddb2272506e99dc1aee786acf800163a833134d942705bc1
5
5
  SHA512:
6
- metadata.gz: 70559b4b0fb659a404d8ea33ec8fd96f69665937ebf325e871cbb42a22189fdd04639eed09e69923348cc91a0e8eabaaa82ec234d5faa8f130aba66fe2793e97
7
- data.tar.gz: a86a66a9bdacd25b9388d53acee40e936d8f9c62aca772ca3b8caed2388d8d19580498f63bb2fe54657766fa45c291f6a988bd7f3e9ab55e7248d31547b4961f
6
+ metadata.gz: f679ac4607b2c7a6b01d6151605cd7339ff57ad721bb18708cff888362b4c19bb9b7f9847016b3e1f93fd0ce31a74635eb6635de83f6764b89873fc44aeb8d2e
7
+ data.tar.gz: 0705c2ddb0b22a1bd2581645cfe18f2488b7aaf827ba90873130c782ac253fd95a50aa9795f982b863fc401744eb8654bc4c225caebec1af08965bab8144c72f
data/Gemfile.lock CHANGED
@@ -34,7 +34,7 @@ GIT
34
34
  PATH
35
35
  remote: .
36
36
  specs:
37
- spree_cm_commissioner (2.9.1.pre.pre1)
37
+ spree_cm_commissioner (2.9.1.pre.pre3)
38
38
  activerecord-multi-tenant
39
39
  activerecord_json_validator (~> 2.1, >= 2.1.3)
40
40
  aws-sdk-cloudfront
@@ -14,7 +14,8 @@ module Spree
14
14
  tenant_id: nil,
15
15
  remote_ip: request.remote_ip,
16
16
  waiting_guest_firebase_doc_id: params[:waiting_guest_firebase_doc_id],
17
- page_path: params[:page_path]
17
+ page_path: params[:page_path],
18
+ device_attributes: permitted_device_attributes
18
19
  )
19
20
 
20
21
  if context.success?
@@ -27,6 +28,12 @@ module Spree
27
28
  def resource_serializer
28
29
  SpreeCmCommissioner::V2::Storefront::WaitingRoomSessionSerializer
29
30
  end
31
+
32
+ private
33
+
34
+ def permitted_device_attributes
35
+ params.permit(*SpreeCmCommissioner::WaitingRoomSessionCreator::PERMITTED_DEVICE_ATTRIBUTES)
36
+ end
30
37
  end
31
38
  end
32
39
  end
@@ -12,8 +12,7 @@ module Spree
12
12
  .includes(:voting_session, :show_contestant,
13
13
  :show_contestant_images, :show_contestant_videos,
14
14
  :advanced_to, :advanced_from
15
- )
16
- .order(:id)
15
+ ).order(:position)
17
16
  end
18
17
 
19
18
  # override
@@ -10,7 +10,8 @@ module Spree
10
10
  tenant_id: MultiTenant.current_tenant_id,
11
11
  remote_ip: request.remote_ip,
12
12
  waiting_guest_firebase_doc_id: params[:waiting_guest_firebase_doc_id],
13
- page_path: params[:page_path]
13
+ page_path: params[:page_path],
14
+ device_attributes: permitted_device_attributes
14
15
  )
15
16
 
16
17
  if context.success?
@@ -23,6 +24,12 @@ module Spree
23
24
  def resource_serializer
24
25
  Spree::V2::Tenant::WaitingRoomSessionSerializer
25
26
  end
27
+
28
+ private
29
+
30
+ def permitted_device_attributes
31
+ params.permit(*SpreeCmCommissioner::WaitingRoomSessionCreator::PERMITTED_DEVICE_ATTRIBUTES)
32
+ end
26
33
  end
27
34
  end
28
35
  end
@@ -10,6 +10,7 @@ module SpreeCmCommissioner
10
10
  close_adjustments cart channel set_channel
11
11
  accept_all reject_all alert_request_to_vendor
12
12
  notifications fire_notification queue_webhooks_requests update_order_status
13
+ update_internal_note
13
14
  ]
14
15
 
15
16
  base.before_action :initialize_notification_methods, only: %i[
@@ -81,6 +82,14 @@ module SpreeCmCommissioner
81
82
 
82
83
  def notifications; end
83
84
 
85
+ def update_internal_note
86
+ internal_note = params.dig(:order, :internal_note).to_s.strip.presence
87
+ @order.update_columns(internal_note: internal_note, updated_at: Time.current) # rubocop:disable Rails/SkipsModelValidations
88
+
89
+ flash[:success] = Spree.t(:internal_note_updated, default: 'Internal note updated')
90
+ redirect_back fallback_location: spree.edit_admin_order_url(@order)
91
+ end
92
+
84
93
  def update_order_status
85
94
  if @order.next
86
95
  flash[:success] = I18n.t('orders.update_order_status.success')
@@ -7,9 +7,22 @@ module SpreeCmCommissioner
7
7
  base.include SpreeCmCommissioner::OrderConcern
8
8
  base.include SpreeCmCommissioner::TurnstileProtectable
9
9
 
10
- base.before_action -> { verify_turnstile! }, only: %i[advance create_payment]
10
+ base.before_action :set_load_test_bypass_current
11
+ base.before_action -> { verify_turnstile! }, only: %i[create_payment]
11
12
  end
12
13
 
14
+ private
15
+
16
+ def set_load_test_bypass_current
17
+ key = ENV.fetch('LOAD_TEST_BYPASS_KEY', nil)
18
+ return if key.blank?
19
+
20
+ provided = request.headers.fetch('X-Request-Verify', nil).to_s
21
+ SpreeCmCommissioner::Current.load_test_bypass = ActiveSupport::SecurityUtils.secure_compare(provided, key)
22
+ end
23
+
24
+ public
25
+
13
26
  # Override Spree's default behavior:
14
27
  # By default, Spree raises an error if `complete` is called on a completed or confirmed order.
15
28
  # We want to allow this call to succeed, so the API can be used both to:
@@ -12,14 +12,23 @@ module SpreeCmCommissioner
12
12
  # Firestore bounds a batch update by payload size (10 MiB); 500 ops/commit leaves us far under that.
13
13
  FIRESTORE_BATCH_SIZE = (ENV['WAITING_ROOM_FIRESTORE_BATCH_SIZE'] || 500).to_i
14
14
 
15
+ # Release at most this many guests per run, even when far more slots are free. A capacity spike
16
+ # (cold start / lull → active_sessions low → available_slots ≈ max_sessions, e.g. 900) would
17
+ # otherwise release every queued guest at once, dumping 900 simultaneous session-token requests
18
+ # on the server. Capping paces entry at ~MAX_CALLS_PER_RUN/min (the caller runs every minute);
19
+ # the rest stay queued and drain over the following runs. Matches the stamper's MAX_ETA_BATCH_SIZE
20
+ # so the per-batch ETA the app shows reflects the real release rate.
21
+ MAX_CALLS_PER_RUN = (ENV['WAITING_ROOM_MAX_CALLS_PER_RUN'] || 50).to_i
22
+
15
23
  def call
16
24
  started_at = Time.zone.now
17
25
  available_slots = fetch_available_slots
26
+ call_limit = [available_slots, MAX_CALLS_PER_RUN].min
18
27
 
19
28
  # Always run through to mark_as — even with no slots — so the lobby keeps a fresh heartbeat
20
29
  # (logs.waiting_guests_caller_job) during peak/full periods instead of going stale
21
30
  # and looking like a dead cron.
22
- long_waiting_guests = available_slots.positive? ? fetch_long_waiting_guests(available_slots) : []
31
+ long_waiting_guests = call_limit.positive? ? fetch_long_waiting_guests(call_limit) : []
23
32
  calling_all(long_waiting_guests) if long_waiting_guests.any?
24
33
 
25
34
  mark_as(
@@ -28,7 +37,7 @@ module SpreeCmCommissioner
28
37
  has_queue: waiting_exists?([previous_records_path, records_path]),
29
38
  avg_wait_to_enter_seconds: compute_avg_wait_to_enter_seconds(long_waiting_guests),
30
39
  avg_queue_to_enter_seconds: compute_avg_queue_to_enter_seconds(long_waiting_guests),
31
- recent_slots_per_call: available_slots.positive? ? available_slots : nil,
40
+ recent_slots_per_call: call_limit.positive? ? call_limit : nil,
32
41
  active_sessions: @active_sessions,
33
42
  max_sessions: @max_sessions,
34
43
  logs: call_logs(started_at: started_at, called_count: long_waiting_guests.size)
@@ -3,10 +3,21 @@ require 'zlib'
3
3
 
4
4
  module SpreeCmCommissioner
5
5
  class WaitingRoomSessionCreator < BaseInteractor
6
- delegate :remote_ip, :waiting_guest_firebase_doc_id, :page_path, :tenant_id, to: :context
6
+ delegate :remote_ip, :waiting_guest_firebase_doc_id, :page_path, :tenant_id,
7
+ :device_attributes, to: :context
7
8
 
8
9
  SESSION_CREATION_LOCK_KEY = 'waiting_room_session_creation'.freeze
9
10
 
11
+ # Device attributes the client may set on the session. device_id and
12
+ # device_fingerprint are real columns; the rest are virtual columns backed by
13
+ # public/private metadata (see WaitingRoomSession). Anything else the client
14
+ # sends is dropped so we never persist arbitrary payloads.
15
+ PERMITTED_DEVICE_ATTRIBUTES = %w[
16
+ device_id device_fingerprint
17
+ os_name os_version device_model device_manufacturer device_type
18
+ device_environment app_version app_build timezone network_type
19
+ ].freeze
20
+
10
21
  def call
11
22
  return context.fail!(message: 'must_provide_waiting_guest_firebase_doc_id') if waiting_guest_firebase_doc_id.blank?
12
23
  return context.fail!(message: 'must_provide_remote_ip') if remote_ip.blank?
@@ -53,15 +64,29 @@ module SpreeCmCommissioner
53
64
  # create or renew
54
65
  context.room_session = SpreeCmCommissioner::WaitingRoomSession.where(guest_identifier: waiting_guest_firebase_doc_id).first_or_initialize
55
66
  context.room_session.assign_attributes(
56
- jwt_token: context.jwt_token,
57
- expired_at: expired_at,
58
- remote_ip: remote_ip,
59
- page_path: page_path,
60
- tenant_id: tenant_id
67
+ {
68
+ jwt_token: context.jwt_token,
69
+ expired_at: expired_at,
70
+ remote_ip: remote_ip,
71
+ page_path: page_path,
72
+ tenant_id: tenant_id
73
+ }.merge(sanitized_device_attributes)
61
74
  )
75
+
62
76
  context.room_session.save!
63
77
  end
64
78
 
79
+ # Whitelisted, flat device attributes. Real columns (device_id,
80
+ # device_fingerprint) and virtual columns (StoreMetadata) are assigned the
81
+ # same way; the model routes each to the right store.
82
+ def sanitized_device_attributes
83
+ attributes = device_attributes
84
+ attributes = attributes.to_unsafe_h if attributes.respond_to?(:to_unsafe_h)
85
+ return {} unless attributes.is_a?(Hash)
86
+
87
+ attributes.symbolize_keys.slice(*PERMITTED_DEVICE_ATTRIBUTES.map(&:to_sym))
88
+ end
89
+
65
90
  def log_to_firebase
66
91
  SpreeCmCommissioner::WaitingRoomSessionFirebaseLoggerJob.perform_later(
67
92
  room_session_id: context.room_session.id,
@@ -0,0 +1,8 @@
1
+ module SpreeCmCommissioner
2
+ class Current < ActiveSupport::CurrentAttributes
3
+ # Set to true for the duration of a load-test request so that rate-limiting
4
+ # services deeper in the call chain (e.g. InventoryHolds::ValidateLimits) can
5
+ # skip their checks without needing the request object threaded through them.
6
+ attribute :load_test_bypass
7
+ end
8
+ end
@@ -71,6 +71,12 @@ module SpreeCmCommissioner
71
71
  end
72
72
  end
73
73
 
74
+ # override
75
+ def after_resume
76
+ super
77
+ update_columns(canceled_at: nil, canceler_id: nil) # rubocop:disable Rails/SkipsModelValidations
78
+ end
79
+
74
80
  # override to allow storing cancellation reason in one DB write
75
81
  def canceled_by(user, cancellation_reason: nil)
76
82
  transaction do
@@ -23,19 +23,20 @@ module SpreeCmCommissioner
23
23
  before_destroy :prevent_if_has_advanced_to
24
24
  before_destroy :prevent_if_pre_vote_session
25
25
 
26
+ acts_as_list scope: :voting_session_id
27
+
26
28
  validates :eliminated_via, presence: true, if: :eliminated?
27
29
 
28
- # Uniqueness validation: vote_number must be unique per contestant per voting_session
29
- # Scope: [show_contestant_id, voting_session_id]
30
+ # Uniqueness validation: vote_number must be unique among contestants within the same voting_session
31
+ # Scope: voting_session_id
30
32
  #
31
33
  # | contestant_id | session_id | vote_number | Status | Reason |
32
34
  # |---------------|-----------|-------------|--------|---------------------------|
33
35
  # | 1 | 1 | 1 | ✓ OK | First record |
34
- # | 1 | 1 | 1 | ✗ FAIL | Duplicate in same scope |
36
+ # | 2 | 1 | 1 | ✗ FAIL | Duplicate in same session |
35
37
  # | 1 | 1 | 2 | ✓ OK | Can update/change number |
36
- # | 2 | 1 | 1 | ✓ OK | Different contestant |
37
38
  # | 1 | 2 | 1 | ✓ OK | Different session |
38
- validates :vote_number, uniqueness: { scope: %i[show_contestant_id voting_session_id], allow_nil: true }
39
+ validates :vote_number, uniqueness: { scope: :voting_session_id, allow_nil: true }
39
40
 
40
41
  delegate :name, :contestant_number, :category, :gender, :eliminated_at, to: :show_contestant, allow_nil: true
41
42
  delegate :can_vote?, to: :voting_session, allow_nil: true
@@ -1,5 +1,19 @@
1
1
  module SpreeCmCommissioner
2
2
  class WaitingRoomSession < Base
3
+ include SpreeCmCommissioner::StoreMetadata
4
+
5
+ store_public_metadata :os_name, :string # e.g. "ios", "android", "web"
6
+ store_public_metadata :os_version, :string # e.g. "17.4", "14"
7
+ store_public_metadata :device_model, :string # e.g. "iPhone15,2", "Pixel 8"
8
+ store_public_metadata :device_manufacturer, :string # e.g. "Apple", "Google", "Samsung"
9
+ store_public_metadata :device_type, :string # e.g. "phone", "tablet", "web", "desktop"
10
+ store_public_metadata :device_environment, :string # e.g. "physical", "emulator", "simulator"
11
+ store_public_metadata :app_version, :string # e.g. "2.3.1"
12
+ store_public_metadata :app_build, :string # e.g. "450"
13
+
14
+ store_private_metadata :timezone, :string # e.g. "ICT", "+07", "UTC"
15
+ store_private_metadata :network_type, :string # e.g. "wifi", "cellular", "ethernet", "none"
16
+
3
17
  validates :jwt_token, presence: true
4
18
  validates :expired_at, presence: true
5
19
  validates :remote_ip, presence: true
@@ -0,0 +1,23 @@
1
+ <!-- insert_after "li:has(#order_channel)" -->
2
+
3
+ <li class="list-group-item d-flex justify-content-between align-items-center" data-hook="admin_order_tab_internal_note">
4
+ <small><%= Spree.t(:internal_note, default: 'Internal Note') %></small>
5
+ <span class="d-flex align-items-center" style="gap: .35rem; min-width: 0;">
6
+ <% if @order.internal_note.present? %>
7
+ <span class="small text-muted text-truncate text-right" style="max-width: 150px;" title="<%= @order.internal_note %>"><%= @order.internal_note %></span>
8
+ <% if can?(:update, @order) %>
9
+ <%= link_to '#', class: 'btn btn-light btn-sm with-tip icon-link p-1',
10
+ title: Spree.t(:edit),
11
+ data: { toggle: 'modal', target: "#internal-note-modal-#{@order.number}" } do %>
12
+ <%= svg_icon name: 'pencil-square.svg', width: '13', height: '13' %>
13
+ <% end %>
14
+ <% end %>
15
+ <% elsif can?(:update, @order) %>
16
+ <%= link_to Spree.t(:add_internal_note, default: 'Add note'), '#',
17
+ class: 'small',
18
+ data: { toggle: 'modal', target: "#internal-note-modal-#{@order.number}" } %>
19
+ <% else %>
20
+ <span class="small text-muted font-italic"><%= Spree.t(:no_internal_note, default: 'No note') %></span>
21
+ <% end %>
22
+ </span>
23
+ </li>
@@ -0,0 +1,40 @@
1
+ <!-- insert_after "#order_tab_summary" -->
2
+
3
+ <% if can?(:update, @order) %>
4
+ <div id="internal-note-modal-<%= @order.number %>" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="internalNoteModalLabel-<%= @order.number %>" aria-hidden="true">
5
+ <div class="modal-dialog" role="document">
6
+ <div class="modal-content">
7
+ <div class="modal-header">
8
+ <h5 class="modal-title mr-auto" id="internalNoteModalLabel-<%= @order.number %>"><%= Spree.t(:internal_note, default: 'Internal Note') %></h5>
9
+ <button type="button" class="close" data-dismiss="modal" aria-label="<%= Spree.t(:close) %>">
10
+ <span aria-hidden="true">&times;</span>
11
+ </button>
12
+ </div>
13
+
14
+ <%= form_with model: @order, url: update_internal_note_admin_order_path(@order), method: :patch, local: true do |f| %>
15
+ <div class="modal-body">
16
+ <div class="form-group text-left text-start">
17
+ <%= f.text_area :internal_note, rows: 5, class: 'form-control' %>
18
+ </div>
19
+ </div>
20
+ <div class="modal-footer d-flex justify-end">
21
+ <button type="button" class="btn btn-light" data-dismiss="modal"><%= Spree.t(:close) %></button>
22
+ <%= f.submit Spree.t(:save_internal_note, default: 'Save Note'), class: 'btn btn-primary' %>
23
+ </div>
24
+ <% end %>
25
+ </div>
26
+ </div>
27
+ </div>
28
+
29
+ <%# The summary renders inside #contextualSideMenu (position: sticky/fixed), whose %>
30
+ <%# stacking context sits below Bootstrap's body-level .modal-backdrop. Relocate the %>
31
+ <%# modal to <body> so it layers above the backdrop and stays interactive. %>
32
+ <script>
33
+ (function () {
34
+ var modal = document.getElementById('internal-note-modal-<%= @order.number %>');
35
+ if (modal && modal.parentNode !== document.body) {
36
+ document.body.appendChild(modal);
37
+ }
38
+ })();
39
+ </script>
40
+ <% end %>
@@ -2,15 +2,7 @@ module Spree
2
2
  module V2
3
3
  module Tenant
4
4
  class ShowEpisodeSerializer < BaseSerializer
5
- attributes :episode_number, :name, :scheduled_at, :ends_at
6
-
7
- attribute :status do |episode|
8
- if episode.current?
9
- 'current'
10
- elsif episode.upcoming?
11
- 'upcoming'
12
- end
13
- end
5
+ attributes :episode_number, :name, :scheduled_at, :ends_at, :status, :preview
14
6
 
15
7
  has_many :voting_sessions, serializer: Spree::V2::Tenant::VotingSessionSerializer
16
8
  has_many :vote_packages, serializer: Spree::V2::Tenant::VotePackageSerializer
@@ -15,7 +15,7 @@ module SpreeCmCommissioner
15
15
  # Validates all inventory hold limits for the given order
16
16
  def call(order:)
17
17
  validate_user_hold_limits!(order) if order.user.present?
18
- validate_ip_rate_limit!(order)
18
+ validate_ip_rate_limit!(order) unless SpreeCmCommissioner::Current.load_test_bypass
19
19
  validate_cooldown!(order)
20
20
 
21
21
  success(nil)
@@ -216,6 +216,11 @@ en:
216
216
  riel: "៛"
217
217
 
218
218
  spree:
219
+ internal_note: "Internal Note"
220
+ add_internal_note: "Add note"
221
+ no_internal_note: "No note"
222
+ save_internal_note: "Save Note"
223
+ internal_note_updated: "Internal note updated"
219
224
  voting_contestant_advancer:
220
225
  reversion_error: "Cannot eliminate here yet. This contestant appears in a later session."
221
226
  is_open_dated: "Is Open Dated Trip?"
@@ -200,6 +200,11 @@ km:
200
200
  riel: "៛"
201
201
 
202
202
  spree:
203
+ internal_note: "ចំណាំផ្ទៃក្នុង"
204
+ add_internal_note: "បន្ថែមចំណាំ"
205
+ no_internal_note: "គ្មានចំណាំ"
206
+ save_internal_note: "រក្សាទុកចំណាំ"
207
+ internal_note_updated: "បានរក្សាទុកចំណាំផ្ទៃក្នុង"
203
208
  voting_contestant_advancer:
204
209
  reversion_error: "មិនអាចដកចេញនៅវគ្គនេះបានទេ។ បេក្ខជននេះមាននៅវគ្គបោះឆ្នោតក្រោយ។"
205
210
  registered_by:
data/config/routes.rb CHANGED
@@ -308,6 +308,7 @@ Spree::Core::Engine.add_routes do
308
308
  put :fire_notification
309
309
  put :queue_webhooks_requests
310
310
  put :update_order_status
311
+ patch :update_internal_note
311
312
  end
312
313
 
313
314
  resources :line_items, only: %i[edit update]
@@ -0,0 +1,14 @@
1
+ class AddDeviceInfoToCmWaitingRoomSessions < ActiveRecord::Migration[7.0]
2
+ def change
3
+ # Indexed identifiers used for dedup / abuse detection.
4
+ add_column :cm_waiting_room_sessions, :device_id, :string, if_not_exists: true
5
+ add_column :cm_waiting_room_sessions, :device_fingerprint, :string, if_not_exists: true
6
+
7
+ # Typed device attributes live here via SpreeCmCommissioner::StoreMetadata.
8
+ add_column :cm_waiting_room_sessions, :public_metadata, :jsonb, default: {}, if_not_exists: true
9
+ add_column :cm_waiting_room_sessions, :private_metadata, :jsonb, default: {}, if_not_exists: true
10
+
11
+ add_index :cm_waiting_room_sessions, :device_id, if_not_exists: true
12
+ add_index :cm_waiting_room_sessions, :device_fingerprint, if_not_exists: true
13
+ end
14
+ end
@@ -0,0 +1,6 @@
1
+ class AddPositionToCmVotingContestants < ActiveRecord::Migration[7.0]
2
+ def change
3
+ add_column :cm_voting_contestants, :position, :integer, default: nil, if_not_exists: true
4
+ add_index :cm_voting_contestants, :position, if_not_exists: true
5
+ end
6
+ end
@@ -1,5 +1,5 @@
1
1
  module SpreeCmCommissioner
2
- VERSION = '2.9.1.pre.pre1'.freeze
2
+ VERSION = '2.9.1.pre.pre3'.freeze
3
3
 
4
4
  module_function
5
5
 
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.9.1.pre.pre1
4
+ version: 2.9.1.pre.pre3
5
5
  platform: ruby
6
6
  authors:
7
7
  - You
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-07-06 00:00:00.000000000 Z
11
+ date: 2026-07-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: spree
@@ -1512,6 +1512,7 @@ files:
1512
1512
  - app/models/spree_cm_commissioner/configuration.rb
1513
1513
  - app/models/spree_cm_commissioner/crew_invite.rb
1514
1514
  - app/models/spree_cm_commissioner/crew_invite_link.rb
1515
+ - app/models/spree_cm_commissioner/current.rb
1515
1516
  - app/models/spree_cm_commissioner/customer.rb
1516
1517
  - app/models/spree_cm_commissioner/customer_notification.rb
1517
1518
  - app/models/spree_cm_commissioner/customer_promotion_rule.rb
@@ -1828,6 +1829,8 @@ files:
1828
1829
  - app/overrides/spree/admin/shared/_main_menu/sidebar_customer_notification.html.erb.deface
1829
1830
  - app/overrides/spree/admin/shared/_main_menu/sidebar_homepage.html.erb.deface
1830
1831
  - app/overrides/spree/admin/shared/_order_summary/adjustment_total.html.erb.deface
1832
+ - app/overrides/spree/admin/shared/_order_summary/internal_note.html.erb.deface
1833
+ - app/overrides/spree/admin/shared/_order_summary/internal_note_modal.html.erb.deface
1831
1834
  - app/overrides/spree/admin/shared/_order_summary/order_status.html.erb.deface
1832
1835
  - app/overrides/spree/admin/shared/_order_summary/request_state.html.erb.deface
1833
1836
  - app/overrides/spree/admin/shared/_order_tabs/guests.html.erb.deface
@@ -3407,6 +3410,8 @@ files:
3407
3410
  - db/migrate/20260623082233_create_cm_claims.rb
3408
3411
  - db/migrate/20260624175658_add_unique_index_to_cm_claims.rb
3409
3412
  - db/migrate/20260629000001_add_term_of_use_to_spree_vendors.rb
3413
+ - db/migrate/20260701000001_add_device_info_to_cm_waiting_room_sessions.rb
3414
+ - db/migrate/20260703065239_add_position_to_cm_voting_contestants.rb
3410
3415
  - docker-compose.yml
3411
3416
  - docs/api/scoped-access-token-endpoints.md
3412
3417
  - docs/option_types/attr_types.md