spree_cm_commissioner 1.19.0 → 1.21.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dbe05a913d5525e83a57e29a837c4c1d5b9eec1607daba170f5ce305079b340b
4
- data.tar.gz: '096d9299fd1faf8d19d7ca1ba00c82fbef35f1e1a938e48b0742f38a1f017aa8'
3
+ metadata.gz: 7043a8acbe6da011c50dcf6d5f9c4fa4c09d44f0c273c7d8c5f35e9da6430628
4
+ data.tar.gz: '049dc2649fbb41b2e43945741baf9c7815ddc11b38a83cec8157c2eac5ed8431'
5
5
  SHA512:
6
- metadata.gz: a5b1efce06f85b8b0439b3d9bba50066547f386c7960fe808014ee29f52598f307407d4f3e40f8320b337adcb1776882bc5d31f9f30da2b8600ecef19e74b3db
7
- data.tar.gz: 1a8bbf21cce85c8d0ce52acd9e1fe9a54f48e90a010829514f1a2e192542f4b03eb9ca40fc6e91d4a2450cd2b477d380aa9bd0dfdc23f748a07e78381f7fe810
6
+ metadata.gz: 17233d10bfd72385a100faf262a0c236017970a4429e33098a7992dd64c217ff02c06168931f3472ef8e5a3245786df65c72f0123f2cacca58223b66c5ca6a3f
7
+ data.tar.gz: 68bac4de8bf2b866b75ccbcfef5c53b50786427d7cbe87e4d17fa0b284186cfbd08d71fbe8c5fe496dfce6b1e6340a6bb0be4cbac00103388c7545397ad417d1
data/Gemfile.lock CHANGED
@@ -34,7 +34,7 @@ GIT
34
34
  PATH
35
35
  remote: .
36
36
  specs:
37
- spree_cm_commissioner (1.19.0)
37
+ spree_cm_commissioner (1.21.0)
38
38
  activerecord-multi-tenant
39
39
  activerecord_json_validator (~> 2.1, >= 2.1.3)
40
40
  aws-sdk-cloudfront
@@ -572,6 +572,10 @@ GEM
572
572
  nokogiri (1.15.4)
573
573
  mini_portile2 (~> 2.8.2)
574
574
  racc (~> 1.4)
575
+ nokogiri (1.15.4-x86_64-darwin)
576
+ racc (~> 1.4)
577
+ nokogiri (1.15.4-x86_64-linux)
578
+ racc (~> 1.4)
575
579
  noticed (1.6.3)
576
580
  http (>= 4.0.0)
577
581
  rails (>= 5.2.0)
@@ -20,7 +20,7 @@ module Spree
20
20
 
21
21
  if guest.save
22
22
  @invite_guest.update(claimed_status: :claimed) if @line_item.guests.count == @invite_guest.quantity
23
- send_guest_claimed_invitation_telegram_alert_to_vendor(guest)
23
+ send_guest_claimed_invitation_telegram_alert_to_vendor(guest) if guest.event.vendor.preferred_telegram_chat_id.present?
24
24
 
25
25
  render json: SpreeCmCommissioner::V2::Storefront::GuestSerializer.new(guest.reload).serializable_hash
26
26
  else
@@ -1,26 +1,17 @@
1
- # šŸŽŸļø --- [TICKET CLAIMED] ---
2
- #
3
- # Order Number:
4
- # <code>R621016753</code>
5
- #
6
- # āœ… <b>Ticket Successfully Claimed!</b>
7
- #
8
- # [ x1 ]
9
- # <b>5km Running Ticket</b>
10
- # <i>šŸ‘‰ Distance: 5km, T-shirt: M</i>
11
- # <i>šŸ—“ļø Nov 15, 2023</i>
12
- # <i>šŸŖ Temple Run with Sai</i>
13
- #
14
- # [ x2 ]
15
- # <b>5km Running Ticket</b>
16
- # <i>šŸ‘‰ Distance: 5km, T-shirt: S</i>
17
- # <i>šŸ—“ļø Nov 15, 2023 -> Nov 17, 2023</i>
18
- # <i>šŸŖ Temple Run with Sai</i>
19
- #
20
- # <b>šŸ™ Claimed By</b>
21
- # Name: Vaneath Awesome
22
- # Tel: <code>+85570760548</code>
23
- # Email: <code>admin_dev@cm.com</code>
1
+ # <p>šŸ“£ --- <b>[NEW GUEST CLAIMED INVITATION]</b> ---</p>
2
+
3
+ # <p>✨ <b>Ticket Successfully Claimed!</b> ✨</p>
4
+
5
+ # <p><b>šŸ™ Claimed By</b><br>
6
+ # Name: Orng SreyMoch<br>
7
+ # Tel: <code>0966827830</code></p>
8
+
9
+ # <p><b>Run Ticket + 2 way bus ticket + Tent</b><br>
10
+ # <i>šŸ‘‰ Distance: 5km, Tent: tent (1 people), and T-shirt: S</i><br>
11
+ # <i>šŸ—“ļø May 30, 2025 -> Jun 30, 2025</i></p>
12
+
13
+ # <p><b>šŸ“‘ Order Number:</b><br>
14
+ # <code>R172627517</code></p>
24
15
 
25
16
  module SpreeCmCommissioner
26
17
  class InviteGuestClaimedTelegramMessageFactory < TelegramMessageFactory
@@ -44,77 +35,39 @@ module SpreeCmCommissioner
44
35
  # override
45
36
  def body
46
37
  text = []
47
- text << "Order Number:\n<code>#{order.number}</code>\n"
48
- text << "\nāœ… <b>Ticket Successfully Claimed!</b>\n"
49
- text << selected_line_items.map { |item| line_item_content(item) }.compact.join("\n\n")
50
- text.compact.join("\n")
38
+ text << '<p>šŸ“£ --- <b>[NEW GUEST CLAIMED INVITATION]</b> ---</p>'
39
+ text << '<p>✨ <b>Ticket Successfully Claimed!</b> ✨</p>'
40
+ text << '<p><b>šŸ™ Claimed By</b><br>'
41
+ text << "Name: #{guest.first_name} #{guest.last_name}<br>"
42
+ text << "Tel: <code>#{guest.phone_number}</code></p>"
43
+
44
+ selected_line_items.each do |item|
45
+ text << line_item_block(item)
46
+ end
47
+
48
+ text << "<p><b>šŸ“‘ Order Number:</b><br><code>#{order.number}</code></p>"
49
+
50
+ text.join("\n")
51
51
  end
52
52
 
53
- def line_item_content(line_item)
54
- text = []
53
+ def line_item_block(item)
54
+ parts = []
55
+ parts << "<p><b>#{item.product.name}</b><br>"
55
56
 
56
- text << bold(line_item.product.name.to_s)
57
- text << "Quantity: #{line_item.quantity}"
58
- text << italic("šŸ‘‰ #{line_item.options_text}") if line_item.options_text.present?
59
- text << italic(pretty_date_for(line_item)) if pretty_date_for(line_item).present?
60
- text << italic("šŸŖ #{line_item.vendor.name}") if line_item.vendor&.name.present? && vendor.blank?
57
+ details = []
58
+ details << "šŸ‘‰ #{item.options_text}" if item.options_text.present?
59
+ details << pretty_date_for(item) if item.date_present?
61
60
 
62
- text.compact.join("\n")
61
+ parts << details.map { |d| "<i>#{d}</i><br>" }.join
62
+ parts << '</p>'
63
+ parts.join("\n")
63
64
  end
64
65
 
65
66
  def pretty_date_for(line_item)
66
- return nil unless line_item.date_present?
67
-
68
67
  from_date = pretty_date(line_item.from_date)
69
68
  to_date = pretty_date(line_item.to_date)
70
69
 
71
- if from_date == to_date
72
- "šŸ—“ļø #{from_date}"
73
- else
74
- "šŸ—“ļø #{from_date} -> #{to_date}"
75
- end
76
- end
77
-
78
- # override
79
- def footer
80
- text = []
81
-
82
- text << bold('šŸ™ Claimed By')
83
- text << "Name: #{guest.first_name} #{guest.last_name}" if guest.first_name.present? || guest.last_name.present?
84
- text << "Tel: #{inline_code(guest.intel_phone_number)}" if guest.intel_phone_number.present?
85
-
86
- if show_details_link && order.guests.any?
87
- text << ''
88
- text << 'View Tickets:'
89
- text += generate_guests_links(order.guests)
90
- end
91
-
92
- text.compact.join("\n")
93
- end
94
-
95
- # Result:
96
- # | No. A24 | No. A24 |
97
- # | No. A24 | No. A24 |
98
- # | No. A24 | No. A24 |
99
- # | No. A24 | No. A24 |
100
- # | No. A24 | No. A24 |
101
- # | No. A24 |
102
- def generate_guests_links(guests, guests_per_row = 2)
103
- rows = (guests.size.to_f / guests_per_row).ceil
104
- formatted_rows = []
105
-
106
- rows.times do |i|
107
- row_guests = guests.slice(i * guests_per_row, guests_per_row)
108
- formatted_row = row_guests.map do |guest|
109
- button_label = guest.seat_number.present? ? "No. #{guest.seat_number}" : "No. #{guest.formatted_bib_number || 'N/A'}"
110
- link = Rails.application.routes.url_helpers.guest_cards_url(guest.token)
111
- "| <a href='#{link}'>#{button_label}</a>"
112
- end.join(' ')
113
-
114
- formatted_rows << ("#{formatted_row} |")
115
- end
116
-
117
- formatted_rows
70
+ from_date == to_date ? "šŸ—“ļø #{from_date}" : "šŸ—“ļø #{from_date} -> #{to_date}"
118
71
  end
119
72
  end
120
73
  end
@@ -18,7 +18,6 @@ module SpreeCmCommissioner
18
18
 
19
19
  def send_sms
20
20
  options = {
21
- from: sender_name(context.tenant),
22
21
  to: context.pin_code.contact,
23
22
  body: I18n.t('pincode_sender.sms.body', code: context.pin_code.code, readable_type: context.pin_code.readable_type)
24
23
  }
@@ -42,7 +42,7 @@ module SpreeCmCommissioner
42
42
  end
43
43
 
44
44
  def sms_options
45
- opts = { from: context.from, to: context.to, body: context.body }
45
+ opts = { to: context.to, body: context.body }
46
46
  opts[:to] = sanitize(opts[:to]) if opts[:to].present?
47
47
  opts[:from] = from_number
48
48
  opts
@@ -1,6 +1,6 @@
1
1
  module SpreeCmCommissioner
2
2
  class UserIdTokenAuthenticator < BaseInteractor
3
- # :id_token
3
+ # :id_token, :tenant_id (optional)
4
4
  def call
5
5
  context.user = if checker.user.nil?
6
6
  register_user
@@ -14,7 +14,11 @@ module SpreeCmCommissioner
14
14
  end
15
15
 
16
16
  def register_user
17
- register_context = SpreeCmCommissioner::UserRegistrationWithIdToken.call(id_token: context.id_token)
17
+ register_context = SpreeCmCommissioner::UserRegistrationWithIdToken.call(
18
+ id_token: context.id_token,
19
+ tenant_id: context.tenant_id
20
+ )
21
+
18
22
  register_context.user
19
23
  end
20
24
 
@@ -27,7 +31,7 @@ module SpreeCmCommissioner
27
31
  end
28
32
 
29
33
  def checker
30
- @checker ||= SpreeCmCommissioner::UserIdTokenChecker.call(id_token: context.id_token)
34
+ @checker ||= SpreeCmCommissioner::UserIdTokenChecker.call(id_token: context.id_token, tenant_id: context.tenant_id)
31
35
  @checker
32
36
  end
33
37
  end
@@ -1,6 +1,9 @@
1
1
  module SpreeCmCommissioner
2
2
  class UserIdTokenChecker < BaseInteractor
3
- # :id_token
3
+ # expects:
4
+ # context.id_token
5
+ # context.tenant_id (optional)
6
+
4
7
  def call
5
8
  firebase_id_token_context = SpreeCmCommissioner::FirebaseIdTokenProvider.call(id_token: context.id_token)
6
9
 
@@ -11,9 +14,14 @@ module SpreeCmCommissioner
11
14
  end
12
15
  end
13
16
 
14
- # :identity_type, :sub
17
+ # :identity_type, :sub, :tenant_id
15
18
  def validate_user_by_provider(provider)
16
- identity_checker = SpreeCmCommissioner::UserIdentityChecker.call(provider)
19
+ identity_checker = SpreeCmCommissioner::UserIdentityChecker.call(
20
+ identity_type: provider[:identity_type],
21
+ sub: provider[:sub],
22
+ tenant_id: context.tenant_id
23
+ )
24
+
17
25
  if identity_checker.success?
18
26
  context.user = identity_checker.user
19
27
  else
@@ -1,6 +1,10 @@
1
1
  module SpreeCmCommissioner
2
2
  class UserIdentityChecker < BaseInteractor
3
- # :identity_type, :sub
3
+ # expects:
4
+ # context.identity_type
5
+ # context.sub
6
+ # context.tenant_id (optional)
7
+
4
8
  def call
5
9
  load_user
6
10
 
@@ -11,12 +15,14 @@ module SpreeCmCommissioner
11
15
  end
12
16
 
13
17
  def load_user
14
- user_identity_provider = UserIdentityProvider.where(
15
- identity_type: context.identity_type,
16
- sub: context.sub
17
- ).first
18
+ return if context.sub.blank? || context.identity_type.blank?
19
+
20
+ user_identity_provider = UserIdentityProvider.joins(:user)
21
+ .where(identity_type: context.identity_type, sub: context.sub)
22
+ .where(spree_users: { tenant_id: context.tenant_id })
23
+ .first
18
24
 
19
- context.user = (user_identity_provider.user if user_identity_provider.present?)
25
+ context.user = user_identity_provider&.user
20
26
  end
21
27
  end
22
28
  end
@@ -15,7 +15,13 @@ module SpreeCmCommissioner
15
15
  end
16
16
 
17
17
  def register_user!(name, email)
18
- user = Spree.user_class.new(password: SecureRandom.base64(16), email: email, **name_attributes(name))
18
+ user = Spree.user_class.new(
19
+ password: SecureRandom.base64(16),
20
+ email: email,
21
+ tenant_id: context.tenant_id,
22
+ **name_attributes(name)
23
+ )
24
+
19
25
  if user.save(validate: false)
20
26
  context.user = user
21
27
  else
@@ -1,6 +1,6 @@
1
1
  module SpreeCmCommissioner
2
2
  class SmsPinCodeJob < SpreeCmCommissioner::SmsJob
3
- # options = { from: xxx, to: xxxx, body: xxxx }
3
+ # options = { to: xxxx, body: xxxx }
4
4
  def perform(options)
5
5
  SpreeCmCommissioner::Sms.call(options)
6
6
  end
@@ -3,15 +3,17 @@ module SpreeCmCommissioner
3
3
  enum identity_type: { :google => 0, :apple => 1, :facebook => 2, :telegram => 3, :vattanac_bank => 4 }
4
4
 
5
5
  belongs_to :user, class_name: Spree.user_class.to_s, optional: false
6
+ has_one :tenant, through: :user
6
7
 
7
- validates :sub, presence: true
8
- validates :sub, uniqueness: { scope: :identity_type }
8
+ has_many :user_identity_provider_telegram_bots
9
+ has_many :telegram_bots, through: :user_identity_provider_telegram_bots
9
10
 
11
+ validates :sub, presence: true
10
12
  validates :identity_type, presence: true
11
13
  validates :identity_type, uniqueness: { scope: :user_id }
12
14
 
13
- has_many :user_identity_provider_telegram_bots
14
- has_many :telegram_bots, through: :user_identity_provider_telegram_bots
15
+ # custom validation to ensure that the sub is unique per identity_type and spree_users.tenant
16
+ validate :unique_sub_per_identity_type_and_tenant
15
17
 
16
18
  # sub is a telegram uid, which telegram considered a chatID if
17
19
  # user have /started with bot.
@@ -20,5 +22,25 @@ module SpreeCmCommissioner
20
22
 
21
23
  nil
22
24
  end
25
+
26
+ private
27
+
28
+ def unique_sub_per_identity_type_and_tenant
29
+ return if user.nil? || sub.blank? || identity_type.blank?
30
+
31
+ tenant_id = user.tenant_id
32
+
33
+ # Check for duplicates in the same tenant
34
+ scope = self.class
35
+ .joins(:user)
36
+ .where(sub: sub, identity_type: identity_type)
37
+ .where(spree_users: { tenant_id: tenant_id })
38
+
39
+ scope = scope.where.not(id: id) if persisted?
40
+
41
+ return unless scope.exists?
42
+
43
+ errors.add(:sub, 'must be unique per identity_type and tenant')
44
+ end
23
45
  end
24
46
  end
@@ -11,6 +11,7 @@ module Spree
11
11
  has_one :profile, serializer: Spree::V2::Tenant::AssetSerializer
12
12
  has_many :device_tokens, serializer: Spree::V2::Tenant::UserDeviceTokenSerializer
13
13
  has_many :spree_roles, serializer: Spree::V2::Tenant::RoleSerializer
14
+ has_many :user_identity_providers, serializer: Spree::V2::Tenant::UserIdentityProviderSerializer
14
15
 
15
16
  attribute :store_credits, &:total_available_store_credit
16
17
 
@@ -32,7 +32,7 @@ module SpreeCmCommissioner
32
32
  options = { login: params[:username], password: params[:password], tenant_id: tenant_id }
33
33
  SpreeCmCommissioner::UserPasswordAuthenticator.call(options)
34
34
  when 'social_auth'
35
- options = { id_token: params[:id_token] }
35
+ options = { id_token: params[:id_token], tenant_id: tenant_id }
36
36
  SpreeCmCommissioner::UserIdTokenAuthenticator.call(options)
37
37
  when 'telegram_web_app_auth'
38
38
  options = { telegram_init_data: params[:telegram_init_data], telegram_bot_username: params[:tg_bot] }
@@ -0,0 +1,13 @@
1
+ class RemoveIndexesFromCmUserIdentityProviders < ActiveRecord::Migration[7.0]
2
+ def change
3
+ execute <<-SQL
4
+ ALTER TABLE cm_user_identity_providers
5
+ DROP CONSTRAINT IF EXISTS index_cm_user_identity_providers_on_identity_type_and_sub;
6
+ SQL
7
+
8
+ execute <<-SQL
9
+ ALTER TABLE cm_user_identity_providers
10
+ DROP CONSTRAINT IF EXISTS index_cm_user_identity_providers_on_user_id_and_identity_type;
11
+ SQL
12
+ end
13
+ end
@@ -1,5 +1,5 @@
1
1
  FactoryBot.define do
2
- factory :user_identity_provider, class: SpreeCmCommissioner::UserIdentityProvider do
2
+ factory :cm_user_identity_provider, class: SpreeCmCommissioner::UserIdentityProvider do
3
3
  sequence(:sub) { |n| "sub-#{n}" }
4
4
  identity_type { SpreeCmCommissioner::UserIdentityProvider.identity_types[:google] }
5
5
  email { 'fake@example.com' }
@@ -1,5 +1,5 @@
1
1
  module SpreeCmCommissioner
2
- VERSION = '1.19.0'.freeze
2
+ VERSION = '1.21.0'.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: 1.19.0
4
+ version: 1.21.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - You
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-07-18 00:00:00.000000000 Z
11
+ date: 2025-07-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: spree
@@ -2537,6 +2537,7 @@ files:
2537
2537
  - db/migrate/20250711093045_add_position_to_cm_dynamic_field_options.rb
2538
2538
  - db/migrate/20250714121508_rename_cm_taxon_blazer_query_to_blazer_queryables.rb
2539
2539
  - db/migrate/20250714124323_make_cm_blazer_queryables_polymorphic.rb
2540
+ - db/migrate/20250715103333_remove_indexes_from_cm_user_identity_providers.rb
2540
2541
  - docker-compose.yml
2541
2542
  - docs/option_types/attr_types.md
2542
2543
  - docs/private_key.pem