spree_cm_commissioner 1.8.10 → 1.9.1

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 (60) hide show
  1. checksums.yaml +4 -4
  2. data/.env.example +4 -0
  3. data/Gemfile.lock +25 -1
  4. data/app/controllers/spree/admin/user_events_controller.rb +9 -7
  5. data/app/controllers/spree/api/v2/storefront/vattanac_banks_controller.rb +25 -0
  6. data/app/controllers/spree/api/v2/tenant/change_passwords_controller.rb +32 -0
  7. data/app/controllers/spree/api/v2/tenant/id_cards_controller.rb +63 -0
  8. data/app/controllers/spree/api/v2/tenant/reset_passwords_controller.rb +35 -0
  9. data/app/controllers/spree/api/v2/tenant/tickets_controller.rb +25 -0
  10. data/app/controllers/spree/api/v2/tenant/user_contacts_controller.rb +33 -0
  11. data/app/controllers/spree_cm_commissioner/admin/roles_controller_decorator.rb +13 -0
  12. data/app/interactors/spree_cm_commissioner/firebase_email_fetcher.rb +25 -0
  13. data/app/interactors/spree_cm_commissioner/firebase_id_token_provider.rb +12 -1
  14. data/app/interactors/spree_cm_commissioner/user_id_token_authenticator.rb +11 -0
  15. data/app/interactors/spree_cm_commissioner/user_registration_with_id_token.rb +3 -3
  16. data/app/interactors/spree_cm_commissioner/user_vattanac_bank_web_app_authenticator.rb +30 -0
  17. data/app/interactors/spree_cm_commissioner/vattanac_bank_initiator.rb +116 -0
  18. data/app/mailers/spree_cm_commissioner/crew_invite_mailer.rb +1 -1
  19. data/app/models/spree_cm_commissioner/order_decorator.rb +1 -1
  20. data/app/models/spree_cm_commissioner/role_decorator.rb +9 -3
  21. data/app/models/spree_cm_commissioner/taxon_decorator.rb +0 -1
  22. data/app/models/spree_cm_commissioner/trip.rb +7 -0
  23. data/app/models/spree_cm_commissioner/trip_connection.rb +10 -5
  24. data/app/models/spree_cm_commissioner/user_identity_provider.rb +1 -1
  25. data/app/models/spree_cm_commissioner/variant_decorator.rb +19 -2
  26. data/app/models/spree_cm_commissioner/variant_options.rb +14 -0
  27. data/app/models/spree_cm_commissioner/vendor_decorator.rb +4 -0
  28. data/app/overrides/spree/admin/users/_form/roles_fields.html.erb.deface +2 -0
  29. data/app/queries/spree_cm_commissioner/tickets_searcher_query.rb +21 -0
  30. data/app/queries/spree_cm_commissioner/trip_query.rb +145 -0
  31. data/app/serializers/spree/v2/tenant/id_card_serializer.rb +12 -0
  32. data/app/serializers/spree/v2/tenant/reset_password_serializer.rb +8 -0
  33. data/app/serializers/spree/v2/tenant/ticket_serializer.rb +27 -0
  34. data/app/serializers/spree/v2/tenant/user_contact_serializer.rb +11 -0
  35. data/app/services/spree_cm_commissioner/aes_encryption_service.rb +52 -0
  36. data/app/services/spree_cm_commissioner/role_permissions_constructor.rb +42 -0
  37. data/app/services/spree_cm_commissioner/role_permissions_loader.rb +57 -0
  38. data/app/services/spree_cm_commissioner/rsa_service.rb +27 -0
  39. data/app/services/spree_cm_commissioner/user_authenticator.rb +4 -0
  40. data/app/views/spree/admin/user_events/_events.html.erb +1 -1
  41. data/app/views/spree/admin/user_events/index.html.erb +7 -3
  42. data/app/views/spree_cm_commissioner/crew_invite_mailer/_mailer_stylesheets.html.erb +2 -7
  43. data/app/views/spree_cm_commissioner/crew_invite_mailer/send_crew_invite_email.html.erb +10 -3
  44. data/config/locales/en.yml +3 -0
  45. data/config/routes.rb +9 -2
  46. data/db/migrate/20250328072717_add_description_to_spree_roles.rb +1 -1
  47. data/db/migrate/20250328072841_add_vendor_id_to_spree_roles.rb +1 -1
  48. data/db/migrate/20250403025619_add_presentation_to_spree_role.rb +5 -0
  49. data/db/migrate/20250403042255_add_unique_constraint_to_name_in_spree_roles.rb +7 -0
  50. data/db/migrate/20250407092556_add_variant_id_to_cm_trips.rb +7 -0
  51. data/lib/spree_cm_commissioner/test_helper/factories/product_factory.rb +9 -0
  52. data/lib/spree_cm_commissioner/test_helper/factories/role_permission_factory.rb +11 -0
  53. data/lib/spree_cm_commissioner/test_helper/factories/trip_factory.rb +6 -0
  54. data/lib/spree_cm_commissioner/test_helper/factories/user_identity_provider_factory.rb +4 -0
  55. data/lib/spree_cm_commissioner/trip_query_result.rb +14 -0
  56. data/lib/spree_cm_commissioner/user_session_jwt_token.rb +5 -0
  57. data/lib/spree_cm_commissioner/version.rb +1 -1
  58. data/lib/spree_cm_commissioner.rb +2 -0
  59. data/spree_cm_commissioner.gemspec +1 -0
  60. metadata +44 -2
@@ -4,6 +4,7 @@ module SpreeCmCommissioner
4
4
  attr_accessor :hours, :minutes, :seconds
5
5
 
6
6
  before_validation :convert_duration_to_seconds
7
+ belongs_to :variant, class_name: 'Spree::Variant'
7
8
 
8
9
  belongs_to :route, class_name: 'Spree::Product'
9
10
  belongs_to :vehicle, class_name: 'SpreeCmCommissioner::Vehicle'
@@ -39,6 +40,12 @@ module SpreeCmCommissioner
39
40
  { hours: hours, minutes: minutes, seconds: seconds }
40
41
  end
41
42
 
43
+ def arrival_time
44
+ return nil if departure_time.nil? || duration.nil?
45
+
46
+ departure_time + duration.seconds
47
+ end
48
+
42
49
  private
43
50
 
44
51
  def origin_and_destination_cannot_be_the_same
@@ -1,7 +1,5 @@
1
1
  module SpreeCmCommissioner
2
2
  class TripConnection < ApplicationRecord
3
- attr_accessor :hours, :minutes
4
-
5
3
  belongs_to :from_trip, class_name: 'Spree::Variant'
6
4
  belongs_to :to_trip, class_name: 'Spree::Variant'
7
5
 
@@ -13,11 +11,18 @@ module SpreeCmCommissioner
13
11
  def calculate_connection_time_minutes
14
12
  return if from_trip.nil? || to_trip.nil?
15
13
 
16
- connection_time_in_minutes = (to_trip.options.departure_time - from_trip.options.arrival_time) / 60
14
+ arrival_seconds = from_trip.trip.arrival_time.seconds_since_midnight
15
+ departure_seconds = to_trip.trip.departure_time.seconds_since_midnight
16
+
17
+ layover_seconds = departure_seconds - arrival_seconds
18
+ layover_seconds += 86_400 if layover_seconds.negative?
19
+
20
+ connection_time_in_minutes = layover_seconds / 60
21
+
17
22
  if connection_time_in_minutes.between?(0, 180)
18
- self.connection_time_minutes = connection_time_in_minutes
23
+ self.connection_time_minutes = connection_time_in_minutes.to_i
19
24
  else
20
- errors.add(:base, 'Connection time should be between 0 and 180 minutes')
25
+ errors.add(:base, 'Connection time must be less than 3 hours')
21
26
  end
22
27
  end
23
28
 
@@ -1,6 +1,6 @@
1
1
  module SpreeCmCommissioner
2
2
  class UserIdentityProvider < Base
3
- enum identity_type: { :google => 0, :apple => 1, :facebook => 2, :telegram => 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
6
 
@@ -21,10 +21,11 @@ module SpreeCmCommissioner
21
21
  base.has_many :variant_guest_card_class, class_name: 'SpreeCmCommissioner::VariantGuestCardClass'
22
22
  base.has_many :guest_card_classes, class_name: 'SpreeCmCommissioner::GuestCardClass', through: :variant_guest_card_class
23
23
 
24
- base.has_many :trip_stops, class_name: 'SpreeCmCommissioner::TripStop', dependent: :destroy, foreign_key: :trip_id
25
24
  base.scope :subscribable, -> { active.joins(:product).where(product: { subscribable: true, status: :active }) }
26
-
25
+ base.has_one :trip,
26
+ class_name: 'SpreeCmCommissioner::Trip'
27
27
  base.accepts_nested_attributes_for :option_values
28
+ base.after_commit :sync_trip, if: -> { product.product_type == 'transit' }
28
29
  end
29
30
 
30
31
  def delivery_required?
@@ -110,6 +111,22 @@ module SpreeCmCommissioner
110
111
  end
111
112
  end
112
113
  end
114
+
115
+ def sync_trip
116
+ return unless product.product_type == 'transit'
117
+
118
+ trip = SpreeCmCommissioner::Trip.find_or_initialize_by(variant_id: id)
119
+
120
+ trip.assign_attributes(
121
+ product_id: product_id,
122
+ origin_id: options.origin,
123
+ destination_id: options.destination,
124
+ departure_time: options.departure_time,
125
+ duration: options.total_duration_in_seconds,
126
+ vehicle_id: options.vehicle
127
+ )
128
+ trip.save
129
+ end
113
130
  end
114
131
  end
115
132
 
@@ -166,5 +166,19 @@ module SpreeCmCommissioner
166
166
  Time.zone.parse(time) if time.present?
167
167
  end
168
168
  end
169
+
170
+ def arrival_time
171
+ @arrival_time ||= departure_time + total_duration_in_minutes.minutes
172
+ end
173
+
174
+ def total_duration_in_minutes
175
+ total_duration_in_minutes = 0
176
+
177
+ total_duration_in_minutes += duration_in_hours * 60 if duration_in_hours.present?
178
+ total_duration_in_minutes += duration_in_minutes if duration_in_minutes.present?
179
+ total_duration_in_minutes += duration_in_seconds / 60 if duration_in_seconds.present?
180
+
181
+ total_duration_in_minutes
182
+ end
169
183
  end
170
184
  end
@@ -177,6 +177,10 @@ module SpreeCmCommissioner
177
177
  def generate_code
178
178
  self.code = (code.presence || name[0, 3].upcase)
179
179
  end
180
+
181
+ def organizer_url
182
+ "#{Spree::Store.default.formatted_url}/organizers/#{slug}"
183
+ end
180
184
  end
181
185
  end
182
186
 
@@ -0,0 +1,2 @@
1
+ <!-- replace 'erb[loud]:contains("f.collection_check_boxes :spree_role_ids")' -->
2
+ <%= f.collection_check_boxes :spree_role_ids, Spree::Role.non_vendor, :id, :name do |role_form| %>
@@ -0,0 +1,21 @@
1
+ module SpreeCmCommissioner
2
+ class TicketsSearcherQuery
3
+ attr_reader :params, :tenant_id
4
+
5
+ def initialize(params, tenant_id)
6
+ @params = params
7
+ @tenant_id = tenant_id
8
+ end
9
+
10
+ def call
11
+ return Spree::Product.none if tenant_id.blank?
12
+
13
+ search = Spree::Product.ransack(
14
+ tenant_id_eq: tenant_id,
15
+ name_i_cont_all: params[:term]&.split
16
+ )
17
+
18
+ search.result.distinct
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,145 @@
1
+ module SpreeCmCommissioner
2
+ class TripQuery
3
+ attr_reader :origin_id, :destination_id, :travel_date
4
+
5
+ def initialize(origin_id:, destination_id:, travel_date: Time.zone.now)
6
+ @origin_id = origin_id
7
+ @destination_id = destination_id
8
+ @travel_date = travel_date
9
+ end
10
+
11
+ def call
12
+ trips = direct_trips
13
+ connections = connected_trips if trips.size < 3
14
+
15
+ trip_results = trips.map { |trip| SpreeCmCommissioner::TripQueryResult.new([trip]) }
16
+
17
+ trip_results += connections if connections
18
+ trip_results
19
+ end
20
+
21
+ def direct_trips
22
+ result = Spree::Variant
23
+ .select('spree_variants.id AS variant_id,
24
+ vendors.id AS vendor_id,
25
+ vendors.name AS vendor_name,
26
+ routes.name AS route_name,
27
+ routes.short_name AS short_name,
28
+ boarding.stop_id AS origin_id,
29
+ drop_off.stop_id AS destination_id,
30
+ boarding.stop_name AS origin,
31
+ drop_off.stop_name AS destination,
32
+ trips.departure_time AS departure_time,
33
+ trips.duration AS duration,
34
+ trips.vehicle_id AS vehicle_id'
35
+ )
36
+ .joins('INNER JOIN cm_trips AS trips ON trips.variant_id = spree_variants.id')
37
+ .joins('INNER JOIN cm_trip_stops AS boarding ON boarding.trip_id = trips.id AND boarding.stop_type = 0')
38
+ .joins('INNER JOIN cm_trip_stops AS drop_off ON drop_off.trip_id = trips.id AND drop_off.stop_type = 1')
39
+ .joins('INNER JOIN spree_vendors AS vendors ON vendors.id = spree_variants.vendor_id')
40
+ .joins('INNER JOIN spree_products AS routes ON routes.id = spree_variants.product_id')
41
+ .where('trips.origin_id = ? AND trips.destination_id = ?', origin_id, destination_id)
42
+
43
+ result.map do |trip|
44
+ trip_result_options = {
45
+ trip_id: trip[:variant_id],
46
+ vendor_id: trip[:vendor_id],
47
+ vendor_name: trip[:vendor_name],
48
+ route_name: trip[:route_name],
49
+ short_name: trip[:short_name],
50
+ detail: trip[:variant_id],
51
+ origin_id: trip[:origin_id],
52
+ origin: trip[:origin],
53
+ destination_id: trip[:destination_id],
54
+ destination: trip[:destination],
55
+ vehicle_id: trip[:vehicle_id],
56
+ departure_time: trip[:departure_time],
57
+ duration: trip[:duration]
58
+ }
59
+ SpreeCmCommissioner::TripResult.new(trip_result_options)
60
+ end
61
+ end
62
+
63
+ def connected_trips # rubocop:disable Metrics/MethodLength
64
+ result = SpreeCmCommissioner::TripConnection
65
+ .joins('
66
+ INNER JOIN spree_variants variant1 ON variant1.id = cm_trip_connections.from_trip_id
67
+ INNER JOIN spree_variants variant2 ON variant2.id = cm_trip_connections.to_trip_id
68
+ INNER JOIN spree_products AS routes1 ON routes1.id = variant1.product_id
69
+ INNER JOIN spree_products AS routes2 ON routes2.id = variant2.product_id
70
+ INNER JOIN cm_trips AS trip1 ON trip1.variant_id = cm_trip_connections.from_trip_id
71
+ INNER JOIN cm_trips AS trip2 ON trip2.variant_id = cm_trip_connections.to_trip_id
72
+ INNER JOIN cm_places trip1_origin ON trip1_origin.id = trip1.origin_id
73
+ INNER JOIN cm_places trip2_origin ON trip2_origin.id = trip2.origin_id
74
+ INNER JOIN cm_places trip2_destination ON trip2_destination.id = trip2.destination_id
75
+ INNER JOIN spree_vendors AS vendor1 ON vendor1.id = variant1.vendor_id
76
+ INNER JOIN spree_vendors AS vendor2 ON vendor2.id = variant2.vendor_id'
77
+ )
78
+ .select('cm_trip_connections.id AS id,
79
+ trip1.variant_id AS trip1_id,
80
+ trip1.origin_id AS trip1_origin_id,
81
+ trip1.destination_id AS trip1_destination_id,
82
+ trip1.departure_time AS trip1_departure_time,
83
+ trip1.duration AS trip1_duration,
84
+ routes1.short_name AS route1_short_name,
85
+ routes1.name AS route1_name,
86
+ trip1.vehicle_id AS trip1_vehicle,
87
+ vendor1.name AS trip1_vendor_name,
88
+ trip2.variant_id AS trip2_id,
89
+ trip2.origin_id AS trip2_origin_id,
90
+ trip2.destination_id AS trip2_destination_id,
91
+ trip2.departure_time AS trip2_departure_time,
92
+ trip2.duration AS trip2_duration,
93
+ trip2.vehicle_id AS trip2_vehicle,
94
+ routes2.short_name AS route2_short_name,
95
+ routes2.name AS route2_name,
96
+ trip1_origin.name AS trip1_origin,
97
+ trip2_origin.name AS trip2_origin,
98
+ trip2_destination.name AS trip2_destination,
99
+ vendor2.name AS trip2_vendor_name'
100
+ )
101
+ .where('trip1_origin.id = ? AND trip2_destination.id = ?', origin_id, destination_id)
102
+
103
+ return [] if result.blank?
104
+
105
+ build_trip_query_result(result)
106
+ end
107
+
108
+ private
109
+
110
+ def build_trip_query_result(connections)
111
+ connections.map do |trip|
112
+ from_trip = {
113
+ trip_id: trip[:trip1_id],
114
+ origin_id: trip[:trip1_origin_id],
115
+ destination_id: trip[:trip1_destination_id],
116
+ departure_time: trip[:trip1_departure_time],
117
+ duration: trip[:trip1_duration],
118
+ vendor_name: trip[:trip1_vendor_name],
119
+ route_name: trip[:route1_name],
120
+ short_name: trip[:route1_short_name],
121
+ vehicle_id: trip[:trip1_vehicle],
122
+ origin: trip[:trip1_origin],
123
+ destination: trip[:trip2_origin]
124
+ }
125
+ to_trip = {
126
+ trip_id: trip[:trip2_id],
127
+ origin_id: trip[:trip2_origin_id],
128
+ destination_id: trip[:trip2_destination_id],
129
+ departure_time: trip[:trip2_departure_time],
130
+ duration: trip[:trip2_duration],
131
+ vendor_name: trip[:trip2_vendor_name],
132
+ route_name: trip[:route2_name],
133
+ short_name: trip[:route2_short_name],
134
+ vehicle_id: trip[:trip2_vehicle],
135
+ origin: trip[:trip2_origin],
136
+ destination: trip[:trip2_destination]
137
+ }
138
+ data = [SpreeCmCommissioner::TripResult.new(from_trip),
139
+ SpreeCmCommissioner::TripResult.new(to_trip)
140
+ ]
141
+ SpreeCmCommissioner::TripQueryResult.new(data, connection_id: trip[:id])
142
+ end
143
+ end
144
+ end
145
+ end
@@ -0,0 +1,12 @@
1
+ module Spree
2
+ module V2
3
+ module Tenant
4
+ class IdCardSerializer < BaseSerializer
5
+ attributes :card_type
6
+
7
+ has_one :front_image, serializer: Spree::V2::Tenant::AssetSerializer
8
+ has_one :back_image, serializer: Spree::V2::Tenant::AssetSerializer
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,8 @@
1
+ module Spree
2
+ module V2
3
+ module Tenant
4
+ class ResetPasswordSerializer < BaseSerializer
5
+ end
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,27 @@
1
+ module Spree
2
+ module V2
3
+ module Tenant
4
+ class TicketSerializer < BaseSerializer
5
+ include ::Spree::Api::V2::DisplayMoneyHelper
6
+
7
+ attributes :name
8
+
9
+ attribute :purchasable do |ticket|
10
+ value = ticket.purchasable?
11
+ [true, false].include?(value) ? value : nil
12
+ end
13
+
14
+ attribute :in_stock do |ticket|
15
+ value = ticket.in_stock?
16
+ [true, false].include?(value) ? value : nil
17
+ end
18
+
19
+ attribute :available, &:available?
20
+
21
+ attribute :display_price do |ticket, params|
22
+ display_price(ticket, params[:currency])
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,11 @@
1
+ module Spree
2
+ module V2
3
+ module Tenant
4
+ class UserContactSerializer < BaseSerializer
5
+ set_type :user
6
+
7
+ attributes :email, :phone_number
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,52 @@
1
+ require 'base64'
2
+ require 'openssl'
3
+
4
+ module SpreeCmCommissioner
5
+ class AesEncryptionService
6
+ ALGORITHM = 'aes-256-gcm'.freeze
7
+ KEY_LENGTH = 32
8
+ IV_LENGTH = 12
9
+ TAG_LENGTH = 16
10
+
11
+ def self.encrypt(plaintext, key)
12
+ validate_key!(key)
13
+
14
+ cipher = OpenSSL::Cipher.new(ALGORITHM)
15
+ cipher.encrypt
16
+ cipher.key = key.b[0, KEY_LENGTH]
17
+ iv = cipher.random_iv
18
+ cipher.iv = iv
19
+
20
+ ciphertext = cipher.update(plaintext) + cipher.final
21
+ tag = cipher.auth_tag
22
+
23
+ combined = iv + ciphertext + tag
24
+ Base64.strict_encode64(combined)
25
+ end
26
+
27
+ def self.decrypt(encrypted_text, key)
28
+ validate_key!(key)
29
+
30
+ combined = Base64.decode64(encrypted_text)
31
+ iv = combined[0, IV_LENGTH]
32
+ tag = combined[-TAG_LENGTH..]
33
+ ciphertext = combined[IV_LENGTH...-TAG_LENGTH]
34
+
35
+ cipher = OpenSSL::Cipher.new(ALGORITHM)
36
+ cipher.decrypt
37
+ cipher.key = key.b[0, KEY_LENGTH]
38
+ cipher.iv = iv
39
+ cipher.auth_tag = tag
40
+
41
+ cipher.update(ciphertext) + cipher.final
42
+ rescue OpenSSL::Cipher::CipherError => e
43
+ raise "Decryption failed: #{e.message}"
44
+ end
45
+
46
+ def self.validate_key!(key)
47
+ return if key.is_a?(String) && key.bytesize >= KEY_LENGTH
48
+
49
+ raise ArgumentError, "Key must be a string of at least #{KEY_LENGTH} bytes"
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,42 @@
1
+ module SpreeCmCommissioner
2
+ class RolePermissionsConstructor
3
+ def initialize(params)
4
+ @params = params[:role_permissions_attributes] || {}
5
+ end
6
+
7
+ def call
8
+ construct_role_permissions
9
+ end
10
+
11
+ private
12
+
13
+ def construct_role_permissions
14
+ role_permissions_attributes = {}
15
+
16
+ @params.each do |index, role_permission_attributes|
17
+ role_permission_attributes = role_permission_attributes.to_h.symbolize_keys
18
+ selected = role_permission_attributes.delete(:selected) == 'true'
19
+ role_permission_persisted = role_permission_attributes.key?(:id)
20
+
21
+ if role_permission_attributes[:permission_attributes]
22
+ permission_attributes = role_permission_attributes[:permission_attributes].to_h.symbolize_keys
23
+ permission_persisted = permission_attributes.key?(:id)
24
+
25
+ if permission_persisted
26
+ role_permission_attributes[:permission_id] = permission_attributes[:id]
27
+ role_permission_attributes.delete(:permission_attributes)
28
+ end
29
+ end
30
+
31
+ if selected
32
+ role_permissions_attributes[index] = role_permission_attributes
33
+ elsif role_permission_persisted
34
+ role_permission_attributes[:_destroy] = 1
35
+ role_permissions_attributes[index] = role_permission_attributes
36
+ end
37
+ end
38
+
39
+ role_permissions_attributes
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,57 @@
1
+ module SpreeCmCommissioner
2
+ class RolePermissionsLoader
3
+ attr_reader :role, :permissions_config, :role_permissions
4
+
5
+ def initialize(role, permissions_config)
6
+ @role = role
7
+ @permissions_config = permissions_config
8
+ @role_permissions = []
9
+ end
10
+
11
+ def call
12
+ load_role_permissions
13
+ role_permissions.uniq(&:slug)
14
+ end
15
+
16
+ private
17
+
18
+ def load_role_permissions
19
+ processed_entries = {}
20
+
21
+ permissions_config[:grouped].each do |entry, groups|
22
+ next if processed_entries[entry]
23
+
24
+ groups.each do |group_name, details|
25
+ next if details['actions'].empty?
26
+
27
+ permission = find_or_initialize_permission(entry, group_name)
28
+ role_permission = build_role_permission(permission)
29
+ @role_permissions << role_permission
30
+ end
31
+
32
+ processed_entries[entry] = true
33
+ end
34
+ end
35
+
36
+ def find_or_initialize_permission(entry, action)
37
+ SpreeCmCommissioner::Permission.where(
38
+ entry: entry,
39
+ action: action
40
+ ).first_or_initialize
41
+ end
42
+
43
+ def build_role_permission(permission)
44
+ if @role.persisted? && permission.persisted?
45
+ SpreeCmCommissioner::RolePermission.where(
46
+ role: @role,
47
+ permission: permission
48
+ ).first_or_initialize
49
+ else
50
+ SpreeCmCommissioner::RolePermission.new(
51
+ role: @role,
52
+ permission: permission
53
+ )
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,27 @@
1
+ require 'base64'
2
+ require 'openssl'
3
+
4
+ module SpreeCmCommissioner
5
+ class RsaService
6
+ def initialize(private_key: nil, public_key: nil)
7
+ @private_key = private_key
8
+ @public_key = public_key
9
+ end
10
+
11
+ def sign(data)
12
+ raise 'Private key is required to sign data' unless @private_key
13
+
14
+ private_key_object = OpenSSL::PKey::RSA.new(@private_key)
15
+ signature = private_key_object.sign(OpenSSL::Digest.new('SHA256'), data)
16
+ "#{data}.#{Base64.strict_encode64(signature)}"
17
+ end
18
+
19
+ def verify(data, signature)
20
+ raise 'Public key is required to verify signature' unless @public_key
21
+
22
+ public_key_object = OpenSSL::PKey::RSA.new(@public_key)
23
+ signature_bytes = Base64.decode64(signature)
24
+ public_key_object.verify(OpenSSL::Digest.new('SHA256'), signature_bytes, data)
25
+ end
26
+ end
27
+ end
@@ -35,6 +35,9 @@ module SpreeCmCommissioner
35
35
  when 'telegram_web_app_auth'
36
36
  options = { telegram_init_data: params[:telegram_init_data], telegram_bot_username: params[:tg_bot] }
37
37
  SpreeCmCommissioner::UserTelegramWebAppAuthenticator.call(options)
38
+ when 'vattanac_bank_web_app_auth'
39
+ options = { session_id: params[:session_id] }
40
+ SpreeCmCommissioner::UserVattanacBankWebAppAuthenticator.call(options)
38
41
  end
39
42
  end
40
43
 
@@ -42,6 +45,7 @@ module SpreeCmCommissioner
42
45
  return 'login_auth' if params.key?(:username) && params.key?(:password)
43
46
  return 'social_auth' if params.key?(:id_token)
44
47
  return 'telegram_web_app_auth' if params.key?(:telegram_init_data) && params.key?(:tg_bot)
48
+ return 'vattanac_bank_web_app_auth' if params.key?(:session_id)
45
49
 
46
50
  raise exception(I18n.t('authenticator.invalid_or_missing_params'))
47
51
  end
@@ -1,5 +1,5 @@
1
1
  <% content_for :page_actions do %>
2
- <%= button_link_to Spree.t(:add_new_events), new_admin_user_event_path, class: "btn-success", icon: 'add.svg', id: 'admin_new_operator_link' %>
2
+ <%= button_link_to Spree.t(:add_new_events), new_admin_user_event_path(user_id: @user.id), class: "btn-success", icon: 'add.svg', id: 'admin_new_operator_link' %>
3
3
  <% end %>
4
4
 
5
5
  <%= render partial: 'spree/admin/users/tabs', locals: { current: :user_events } %>
@@ -3,7 +3,11 @@
3
3
  <% elsif @taxon.present? && @taxonomy.present? %>
4
4
  <%= render partial: 'users', locals: { user_events: @user_events, taxon: @taxon, taxonomy: @taxonomy } %>
5
5
  <% else %>
6
- <small class="form-text text-muted">
7
- <%= raw I18n.t('user_taxon.empty_info') %>
8
- </small>
6
+ <% content_for :page_actions do %>
7
+ <%= button_link_to Spree.t(:add_new_events), new_admin_user_event_path(user_id: @user.id), class: "btn-success", icon: 'add.svg', id: 'admin_new_operator_link' %>
8
+ <% end %>
9
+ <%= render partial: 'spree/admin/users/tabs', locals: { current: :user_events } %>
10
+ <small class="form-text text-muted">
11
+ <%= raw I18n.t('user_taxon.empty_info') %>
12
+ </small>
9
13
  <% end %>
@@ -17,17 +17,11 @@
17
17
  padding-bottom: 25px;
18
18
  }
19
19
 
20
- .logo {
21
- display: block;
22
- width: 150px;
23
- margin-top: 10px;
24
- margin-bottom: 20px;
25
- }
26
-
27
20
  .heading-1 {
28
21
  font-size: 24px;
29
22
  text-align: center;
30
23
  line-height: 1.4;
24
+ margin-top: 10px;
31
25
  margin-bottom: 15px;
32
26
  }
33
27
 
@@ -76,6 +70,7 @@
76
70
  .heading-3 {
77
71
  font-size: 12px;
78
72
  text-align: center;
73
+ margin-bottom: 15px;
79
74
  color: #888;
80
75
  }
81
76
  </style>
@@ -5,13 +5,15 @@
5
5
  <div class="bar-large"></div>
6
6
  <div class="main-content">
7
7
  <div class="content">
8
- <div><%= image_tag "mailer/bookme-plus_light.svg", class: "logo" %></div>
8
+ <div>
9
+ <%= image_tag(main_app.rails_blob_url(Spree::StoreLogo.first.attachment), style: "width: 120px; height: 120px;") %>
10
+ </div>
9
11
  <div class="heading-1">
10
12
  Become a part of the <br> event <strong> <%= @invite_user_event.invite.taxon.name %> </strong>
11
13
  </div>
12
14
  <div class="bar-thin"></div>
13
15
  <div class="heading-2">
14
- <%= @invite_user_event.invite.inviter.full_name.presence || @invite_user_event.inviter.email %> invites you to join the <br>
16
+ <%= @invite_user_event.invite.inviter.full_name.presence || @invite_user_event.invite.inviter.email %> invites you to join the <br>
15
17
  <span class="title-bold">Check-in Crew</span> for the event <br>
16
18
  <%= @invite_user_event.invite.taxon.name %>
17
19
  </div>
@@ -20,8 +22,13 @@
20
22
  Get Started
21
23
  </a>
22
24
  </div>
25
+
26
+ <div class="heading-3">
27
+ Use this email <%= @invite_user_event.email %> to log in or <br>create an account on BookMe+ Platform
28
+ </div>
29
+
23
30
  <div class="heading-3">
24
- Please note that this invitation will expire<br>
31
+ This invitation will expire<br>
25
32
  in <%= ((@invite_user_event.invite.expires_at - Time.current) / 1.hour).round %> hours.
26
33
  </div>
27
34
  </div>
@@ -480,6 +480,9 @@ en:
480
480
  bookmeplus_support: "BookMe+ Support"
481
481
  info_notice: "Information Notice"
482
482
  booking_details: "Booking Details"
483
+ crew_invite_mailer:
484
+ subject: "You're Invited to Join the Check-In Crew for %{event_name}!"
485
+
483
486
  invoice:
484
487
  empty_info: "<b>Invoice</b> not create yet"
485
488
  device_token: