spree_cm_commissioner 2.14.3 → 2.14.4
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 +4 -4
- data/.env.example +4 -6
- data/Gemfile.lock +1 -1
- data/app/controllers/concerns/spree_cm_commissioner/content_cachable.rb +76 -70
- data/app/controllers/spree/admin/api_caches_controller.rb +26 -0
- data/app/controllers/spree/admin/base_controller_decorator.rb +0 -14
- data/app/controllers/spree/admin/guests_controller.rb +43 -4
- data/app/overrides/spree/admin/menus/index/clear_cache_button.html.erb.deface +4 -0
- data/app/overrides/spree/admin/payment_methods/index/clear_cache_button.html.erb.deface +6 -0
- data/app/overrides/spree/admin/products/edit/clear_cache_button.html.erb.deface +1 -1
- data/app/overrides/spree/admin/taxons/edit/clear_cache_button.html.erb.deface +1 -4
- data/app/overrides/spree/admin/vendors/edit/clear_cache_button.html.erb.deface +5 -0
- data/app/serializers/spree/api/v2/base_serializer_decorator.rb +24 -0
- data/app/services/spree_cm_commissioner/api_caches/invalidate.rb +39 -87
- data/app/services/spree_cm_commissioner/api_caches/invalidate_all.rb +61 -0
- data/app/services/spree_cm_commissioner/guests/build_dynamic_field_attributes.rb +34 -0
- data/app/services/spree_cm_commissioner/order/guests/update.rb +2 -24
- data/app/views/spree/admin/guests/_dynamic_fields.html.erb +67 -0
- data/app/views/spree/admin/guests/_form.html.erb +2 -0
- data/app/views/spree/admin/homepage_background/index.html.erb +1 -1
- data/app/views/spree/admin/homepage_section/edit.html.erb +0 -1
- data/app/views/spree/admin/homepage_section/index.html.erb +1 -1
- data/app/views/spree/admin/shared/_api_cache_button.html.erb +5 -0
- data/config/locales/en.yml +11 -0
- data/config/locales/km.yml +11 -0
- data/config/routes.rb +5 -1
- data/lib/spree_cm_commissioner/version.rb +1 -1
- metadata +10 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e5317bd75c6057165a07eae82d0e30f986371767925346783242d8d12a0a8d24
|
|
4
|
+
data.tar.gz: b015f2bebfc1105dee65f7b4b22f63ee909b113541741f9dbc5ed48482ab4b04
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7c59a388a335fcffdbb53ef8ebd470bf9b310a169ac8057ba6b464c6cef8e4ee6425cccfa5bfda366ea9c7fa89aaca854465c914521df040d460ba39994bd0b3
|
|
7
|
+
data.tar.gz: 0d8572c942886d56c1689ceb532e2d643f208d538dfb8fa40e21209198562dbe52fb7bb8a28f7573d0900407b8651421210eb4b99c6ea80aefa14b73677880c4
|
data/.env.example
CHANGED
|
@@ -47,14 +47,12 @@ ORGANIZER_URL=http://127.0.0.1:4000/organizer
|
|
|
47
47
|
# spree_cm_commissioner/distance.rb
|
|
48
48
|
DISTANCE_SIGNING_KEY=hei********************VY
|
|
49
49
|
|
|
50
|
-
#
|
|
50
|
+
# CDN cache durations (s-maxage, in seconds) for listed public APIs; client max-age is always 0, unlisted APIs are never CDN-cached
|
|
51
51
|
# See: app/controllers/concerns/spree_cm_commissioner/content_cachable.rb
|
|
52
52
|
CACHE_AUTO_INVALIDATION_DISABLED="" # Set to yes to disable CDN cache invalidation entirely (kill switch)
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
CACHE_REALTIME_MAX_AGE=300 # High freshness (trips, trip search) - 5 minutes
|
|
57
|
-
CACHE_DEFAULT_MAX_AGE=300 # Default for unlisted controllers - 5 minutes
|
|
53
|
+
CACHE_SERIALIZER_DISABLED="" # Set to yes to turn off Spree's serializer record cache everywhere
|
|
54
|
+
CACHE_STATIC_MAX_AGE=86400 # Static content (CMS pages) - 1 day
|
|
55
|
+
CACHE_REALTIME_MAX_AGE=300 # Everything else listed (homepage, menus, products, events, vendors, trips, seat layouts, countries, provinces, guest card classes) - 5 minutes
|
|
58
56
|
|
|
59
57
|
# Batch size for processing maintenance tasks in app/jobs/spree_cm_commissioner/maintenance_tasks/orchestrate_job.rb
|
|
60
58
|
MAINTENANCE_TASKS_BATCH_SIZE=100
|
data/Gemfile.lock
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# Check following file for spec:
|
|
2
|
-
# spec/controllers/
|
|
2
|
+
# spec/controllers/concerns/spree_cm_commissioner/content_cachable_spec.rb
|
|
3
3
|
module SpreeCmCommissioner
|
|
4
4
|
module ContentCachable
|
|
5
5
|
extend ActiveSupport::Concern
|
|
@@ -8,88 +8,94 @@ module SpreeCmCommissioner
|
|
|
8
8
|
after_action :set_cache_control_for_cdn
|
|
9
9
|
end
|
|
10
10
|
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
11
|
+
# Every CDN-cached API, by full class name, with its GET actions and TTL tier. Anything not
|
|
12
|
+
# listed keeps Rails' default `max-age=0, private, must-revalidate` and never reaches the CDN.
|
|
13
|
+
#
|
|
14
|
+
# :static (CACHE_STATIC_MAX_AGE, 1 day) is for rarely changed content. :fresh
|
|
15
|
+
# (CACHE_REALTIME_MAX_AGE, 5 minutes) is for anything editors change, short enough that an edit
|
|
16
|
+
# shows up without a CloudFront invalidation.
|
|
17
|
+
#
|
|
18
|
+
# Full names, not demodulized ones: this concern runs on every ActionController::API, and short
|
|
19
|
+
# names collide with admin-only APIs (Platform, Operator, V1, webhooks) that must never be marked
|
|
20
|
+
# public. Never list an action whose response depends on the signed-in user, e.g.
|
|
21
|
+
# Tenant::VotePackagesController#claim_status: the CDN would serve one user's answer to everyone
|
|
22
|
+
# sharing the cache key.
|
|
23
|
+
CDN_CACHEABLE_ACTIONS = {
|
|
24
|
+
# JSON-LD
|
|
25
|
+
'Spree::Api::JsonLd::VendorsController' => { actions: %w[index show].freeze, max_age: :fresh },
|
|
21
26
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
27
|
+
# Storefront
|
|
28
|
+
'Spree::Api::V2::Storefront::AccommodationsController' => { actions: %w[index show].freeze, max_age: :fresh },
|
|
29
|
+
'Spree::Api::V2::Storefront::CmsPagesController' => { actions: %w[index show].freeze, max_age: :static },
|
|
30
|
+
'Spree::Api::V2::Storefront::CountriesController' => { actions: %w[index show].freeze, max_age: :fresh },
|
|
31
|
+
'Spree::Api::V2::Storefront::DynamicFieldsController' => { actions: %w[index show].freeze, max_age: :fresh },
|
|
32
|
+
'Spree::Api::V2::Storefront::EventMatchesController' => { actions: %w[index].freeze, max_age: :fresh },
|
|
33
|
+
'Spree::Api::V2::Storefront::EventsController' => { actions: %w[index show].freeze, max_age: :fresh },
|
|
34
|
+
'Spree::Api::V2::Storefront::GuestCardClassesController' => { actions: %w[index show].freeze, max_age: :fresh },
|
|
35
|
+
'Spree::Api::V2::Storefront::HomepageBackgroundController' => { actions: %w[show].freeze, max_age: :fresh },
|
|
36
|
+
'Spree::Api::V2::Storefront::HomepageDataController' => { actions: %w[show].freeze, max_age: :fresh },
|
|
37
|
+
'Spree::Api::V2::Storefront::HomepageSectionsController' => { actions: %w[index].freeze, max_age: :fresh },
|
|
38
|
+
'Spree::Api::V2::Storefront::MenusController' => { actions: %w[index show].freeze, max_age: :fresh },
|
|
39
|
+
'Spree::Api::V2::Storefront::PaymentMethodsController' => { actions: %w[index].freeze, max_age: :fresh },
|
|
40
|
+
'Spree::Api::V2::Storefront::PopularRoutePlacesController' => { actions: %w[index].freeze, max_age: :fresh },
|
|
41
|
+
'Spree::Api::V2::Storefront::ProductsController' => { actions: %w[index show].freeze, max_age: :fresh },
|
|
42
|
+
'Spree::Api::V2::Storefront::ProvincesController' => { actions: %w[index].freeze, max_age: :fresh },
|
|
43
|
+
'Spree::Api::V2::Storefront::RoutePlacesController' => { actions: %w[index].freeze, max_age: :fresh },
|
|
44
|
+
'Spree::Api::V2::Storefront::SeatLayoutsController' => { actions: %w[index show].freeze, max_age: :fresh },
|
|
45
|
+
'Spree::Api::V2::Storefront::TaxonsController' => { actions: %w[index show].freeze, max_age: :fresh },
|
|
46
|
+
'Spree::Api::V2::Storefront::TripSearchController' => { actions: %w[index].freeze, max_age: :fresh },
|
|
47
|
+
'Spree::Api::V2::Storefront::TripsController' => { actions: %w[show].freeze, max_age: :fresh },
|
|
48
|
+
'Spree::Api::V2::Storefront::VendorPhotosController' => { actions: %w[index show].freeze, max_age: :fresh },
|
|
49
|
+
'Spree::Api::V2::Storefront::VendorsController' => { actions: %w[index show].freeze, max_age: :fresh },
|
|
38
50
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
51
|
+
# Tenant
|
|
52
|
+
'Spree::Api::V2::Tenant::CmsPagesController' => { actions: %w[index show].freeze, max_age: :static },
|
|
53
|
+
'Spree::Api::V2::Tenant::EpisodesController' => { actions: %w[index show].freeze, max_age: :fresh },
|
|
54
|
+
'Spree::Api::V2::Tenant::HomepageSectionsController' => { actions: %w[index].freeze, max_age: :fresh },
|
|
55
|
+
'Spree::Api::V2::Tenant::PaymentMethodsController' => { actions: %w[index].freeze, max_age: :fresh },
|
|
56
|
+
'Spree::Api::V2::Tenant::PopularRoutePlacesController' => { actions: %w[index].freeze, max_age: :fresh },
|
|
57
|
+
'Spree::Api::V2::Tenant::ProductsController' => { actions: %w[index show].freeze, max_age: :fresh },
|
|
58
|
+
'Spree::Api::V2::Tenant::ShowContestantsController' => { actions: %w[show].freeze, max_age: :fresh },
|
|
59
|
+
'Spree::Api::V2::Tenant::ShowEliminationSessionsController' => { actions: %w[index].freeze, max_age: :fresh },
|
|
60
|
+
'Spree::Api::V2::Tenant::ShowPeopleController' => { actions: %w[index].freeze, max_age: :fresh },
|
|
61
|
+
'Spree::Api::V2::Tenant::ShowPersonAssignmentsController' => { actions: %w[index].freeze, max_age: :fresh },
|
|
62
|
+
'Spree::Api::V2::Tenant::ShowsController' => { actions: %w[show].freeze, max_age: :fresh },
|
|
63
|
+
'Spree::Api::V2::Tenant::TaxonsController' => { actions: %w[index show].freeze, max_age: :fresh },
|
|
64
|
+
'Spree::Api::V2::Tenant::TripSearchController' => { actions: %w[index].freeze, max_age: :fresh },
|
|
65
|
+
'Spree::Api::V2::Tenant::TripsController' => { actions: %w[show].freeze, max_age: :fresh },
|
|
66
|
+
'Spree::Api::V2::Tenant::VendorPhotosController' => { actions: %w[index].freeze, max_age: :fresh },
|
|
67
|
+
'Spree::Api::V2::Tenant::VendorsController' => { actions: %w[index show].freeze, max_age: :fresh },
|
|
68
|
+
'Spree::Api::V2::Tenant::VotePackagesController' => { actions: %w[index].freeze, max_age: :fresh },
|
|
69
|
+
'Spree::Api::V2::Tenant::VotingContestantsController' => { actions: %w[index].freeze, max_age: :fresh }
|
|
70
|
+
}.freeze
|
|
50
71
|
|
|
51
|
-
#
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
DynamicFieldsController
|
|
57
|
-
].freeze
|
|
58
|
-
|
|
59
|
-
# Override This Method based on your UseCase
|
|
60
|
-
# Or let it auto-detect based on controller name
|
|
61
|
-
def client_max_age
|
|
62
|
-
controller_name = self.class.name.demodulize
|
|
63
|
-
|
|
64
|
-
if STATIC_CONTENT_CONTROLLERS.include?(controller_name)
|
|
65
|
-
ENV.fetch('CACHE_STATIC_MAX_AGE', 1.day.to_i).to_i
|
|
66
|
-
elsif SEMI_STATIC_CONTROLLERS.include?(controller_name)
|
|
67
|
-
ENV.fetch('CACHE_SEMI_STATIC_MAX_AGE', 1.hour.to_i).to_i
|
|
68
|
-
elsif MODERATE_FRESHNESS_CONTROLLERS.include?(controller_name)
|
|
69
|
-
ENV.fetch('CACHE_MODERATE_MAX_AGE', 30.minutes.to_i).to_i
|
|
70
|
-
elsif HIGH_FRESHNESS_CONTROLLERS.include?(controller_name)
|
|
71
|
-
ENV.fetch('CACHE_REALTIME_MAX_AGE', 5.minutes.to_i).to_i
|
|
72
|
-
else
|
|
73
|
-
ENV.fetch('CACHE_DEFAULT_MAX_AGE', 0.minutes.to_i).to_i
|
|
72
|
+
# CDN (s-maxage) TTL, or nil for an unlisted controller. Override in a controller for a different one.
|
|
73
|
+
def shared_max_age
|
|
74
|
+
case cdn_cache_config&.dig(:max_age)
|
|
75
|
+
when :static then ENV.fetch('CACHE_STATIC_MAX_AGE', 1.day.to_i).to_i
|
|
76
|
+
when :fresh then ENV.fetch('CACHE_REALTIME_MAX_AGE', 5.minutes.to_i).to_i
|
|
74
77
|
end
|
|
75
78
|
end
|
|
76
79
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
# (max-age 0) must not be CDN-cached at all.
|
|
80
|
-
def shared_max_age
|
|
81
|
-
client_max_age
|
|
80
|
+
def cdn_cacheable?
|
|
81
|
+
request.get? && cdn_cache_config.present? && cdn_cache_config[:actions].include?(action_name)
|
|
82
82
|
end
|
|
83
83
|
|
|
84
84
|
def set_cache_control_for_cdn
|
|
85
85
|
return if response.committed?
|
|
86
86
|
return if response.status != 200
|
|
87
|
+
return unless cdn_cacheable?
|
|
88
|
+
|
|
89
|
+
# max-age=0 makes the app revalidate with CloudFront on every request, since a CloudFront
|
|
90
|
+
# invalidation can never reach the copy already on a device. Rack::ETag tags each response,
|
|
91
|
+
# so a revalidation is a small 304. s-maxage alone decides how stale a response can get.
|
|
92
|
+
response.headers['Cache-Control'] = "public, max-age=0, s-maxage=#{shared_max_age}"
|
|
93
|
+
end
|
|
87
94
|
|
|
88
|
-
|
|
89
|
-
response.headers['Cache-Control'] = "public, max-age=#{client_max_age}, s-maxage=#{shared_max_age}"
|
|
95
|
+
private
|
|
90
96
|
|
|
91
|
-
|
|
92
|
-
|
|
97
|
+
def cdn_cache_config
|
|
98
|
+
CDN_CACHEABLE_ACTIONS[self.class.name.to_s]
|
|
93
99
|
end
|
|
94
100
|
end
|
|
95
101
|
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Admin
|
|
3
|
+
# Behind every admin clear-cache button (spree/admin/shared/_api_cache_button). Admin-only through
|
|
4
|
+
# BaseController#authorize_admin, which checks :api_caches since there is no model class.
|
|
5
|
+
class ApiCachesController < Spree::Admin::BaseController
|
|
6
|
+
# POST /admin/api_caches/clear?model=Spree::Product&id=1, or without id to clear every record of
|
|
7
|
+
# model on one storefront: tenant_id for that tenant's API, none for the BookMe+ storefront.
|
|
8
|
+
def clear
|
|
9
|
+
result = if params[:id].present?
|
|
10
|
+
SpreeCmCommissioner::ApiCaches::Invalidate.call(model: params[:model], id: params[:id])
|
|
11
|
+
else
|
|
12
|
+
tenant = SpreeCmCommissioner::Tenant.find(params[:tenant_id]) if params[:tenant_id].present?
|
|
13
|
+
SpreeCmCommissioner::ApiCaches::InvalidateAll.call(model: params[:model], tenant: tenant)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
if result.success?
|
|
17
|
+
flash[:success] = Spree.t(:clear_cache_requested)
|
|
18
|
+
else
|
|
19
|
+
flash[:error] = result.error.to_s
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
redirect_back fallback_location: admin_root_path
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -31,20 +31,6 @@ module Spree
|
|
|
31
31
|
nil
|
|
32
32
|
end
|
|
33
33
|
|
|
34
|
-
# POST
|
|
35
|
-
def invalidate_api_caches
|
|
36
|
-
if params[:model].present?
|
|
37
|
-
result = SpreeCmCommissioner::ApiCaches::Invalidate.call(
|
|
38
|
-
model: params[:model],
|
|
39
|
-
id: params[:id]
|
|
40
|
-
)
|
|
41
|
-
|
|
42
|
-
flash[:error] = result.error if result.failure?
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
redirect_back fallback_location: admin_root_path
|
|
46
|
-
end
|
|
47
|
-
|
|
48
34
|
private
|
|
49
35
|
|
|
50
36
|
# See SpreeCmCommissioner::Current#actor -- lets StoreCreditEventDecorator's after_create
|
|
@@ -6,6 +6,7 @@ module Spree
|
|
|
6
6
|
|
|
7
7
|
before_action :load_order
|
|
8
8
|
before_action :load_guest, only: %i[check_in uncheck_in]
|
|
9
|
+
before_action :load_edit_data, only: :edit
|
|
9
10
|
|
|
10
11
|
def model_class
|
|
11
12
|
SpreeCmCommissioner::Guest
|
|
@@ -41,10 +42,17 @@ module Spree
|
|
|
41
42
|
@kyc_fields = @line_item.kyc_fields
|
|
42
43
|
end
|
|
43
44
|
|
|
44
|
-
def
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
def update
|
|
46
|
+
result = SpreeCmCommissioner::Guests::Update.call(guest: @object, guest_params: update_params)
|
|
47
|
+
|
|
48
|
+
if result.success?
|
|
49
|
+
flash[:success] = flash_message_for(@object, :successfully_updated)
|
|
50
|
+
redirect_to location_after_save
|
|
51
|
+
else
|
|
52
|
+
flash.now[:error] = result.error.to_s
|
|
53
|
+
load_edit_data
|
|
54
|
+
render :edit, status: :unprocessable_entity
|
|
55
|
+
end
|
|
48
56
|
end
|
|
49
57
|
|
|
50
58
|
def check_in
|
|
@@ -88,6 +96,37 @@ module Spree
|
|
|
88
96
|
)
|
|
89
97
|
end
|
|
90
98
|
|
|
99
|
+
# Saved through Guests::Update's single transaction so a failing custom field does not leave
|
|
100
|
+
# KYC or seat_number half-saved.
|
|
101
|
+
def update_params
|
|
102
|
+
attrs = permitted_resource_params.to_h.symbolize_keys
|
|
103
|
+
dynamic_fields = submitted_dynamic_fields
|
|
104
|
+
return attrs if dynamic_fields.blank?
|
|
105
|
+
|
|
106
|
+
attrs.merge(
|
|
107
|
+
guest_dynamic_fields_attributes: SpreeCmCommissioner::Guests::BuildDynamicFieldAttributes.call(
|
|
108
|
+
guest: @object, dynamic_fields: dynamic_fields
|
|
109
|
+
)
|
|
110
|
+
)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# The form lists every field of every stage, so blank scalar answers mean "not filled in" and
|
|
114
|
+
# are skipped rather than failing the presence validation (clearing an answer is not supported).
|
|
115
|
+
def submitted_dynamic_fields
|
|
116
|
+
raw = params.require(object_name).permit(dynamic_fields: {})[:dynamic_fields]
|
|
117
|
+
|
|
118
|
+
raw.to_h.reject { |_id, value| value.is_a?(String) && value.strip.empty? }
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def load_edit_data
|
|
122
|
+
@kyc_fields = @object.line_item.kyc_fields
|
|
123
|
+
@check_ins = @object.check_ins.includes(:check_in_session, :check_in_by)
|
|
124
|
+
@eligible_check_in_sessions = @object.eligible_check_in_sessions.order(:position)
|
|
125
|
+
@dynamic_fields = @object.line_item.product.dynamic_fields.includes(:dynamic_field_options)
|
|
126
|
+
.reorder(:data_fill_stage, :position)
|
|
127
|
+
@guest_dynamic_fields = @object.guest_dynamic_fields.to_a
|
|
128
|
+
end
|
|
129
|
+
|
|
91
130
|
def load_guest
|
|
92
131
|
@guest = @order.guests.find(params[:id])
|
|
93
132
|
end
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<!-- insert_after "erb[silent]:contains('content_for :page_actions')" -->
|
|
2
|
+
|
|
3
|
+
<%# A payment method's paths don't depend on the record (vendor_id is a query param), so one clear
|
|
4
|
+
on the list covers them all. This admin lists BookMe+'s storefront; a tenant clears its own
|
|
5
|
+
from the dashboard. %>
|
|
6
|
+
<%= render 'spree/admin/shared/api_cache_button', model: Spree::PaymentMethod.name %>
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
<!-- insert_after "erb[silent]:contains('content_for :page_actions')" -->
|
|
2
2
|
|
|
3
|
-
<%=
|
|
3
|
+
<%= render 'spree/admin/shared/api_cache_button', model: Spree::Product.name, id: @product.id %>
|
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
<!-- insert_after "erb[silent]:contains('content_for :page_actions')" -->
|
|
2
2
|
|
|
3
3
|
<% if @taxon.depth == 1 %>
|
|
4
|
-
|
|
5
|
-
admin_invalidate_api_caches_path(model: Spree::Taxon.name, id: @taxon.id),
|
|
6
|
-
method: :post,
|
|
7
|
-
class: "btn btn-outline-primary" %>
|
|
4
|
+
<%= render 'spree/admin/shared/api_cache_button', model: Spree::Taxon.name, id: @taxon.id %>
|
|
8
5
|
<% end %>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Api
|
|
3
|
+
module V2
|
|
4
|
+
# Spree caches every serializer's records keyed by each record's updated_at, so anything
|
|
5
|
+
# computed from other tables stays stale, and behind CloudFront an invalidation can't purge
|
|
6
|
+
# it. CACHE_SERIALIZER_DISABLED=yes turns it off everywhere.
|
|
7
|
+
module BaseSerializerDecorator
|
|
8
|
+
def self.prepended(base)
|
|
9
|
+
base.singleton_class.prepend(ClassMethods)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
module ClassMethods
|
|
13
|
+
# jsonapi-serializer copies Spree's store into each subclass as it is defined, so this
|
|
14
|
+
# reader, which every subclass resolves through, is the one place to turn it off.
|
|
15
|
+
def cache_store_instance
|
|
16
|
+
ENV['CACHE_SERIALIZER_DISABLED'] == 'yes' ? nil : super
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
Spree::Api::V2::BaseSerializer.prepend(Spree::Api::V2::BaseSerializerDecorator)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
module SpreeCmCommissioner
|
|
2
2
|
module ApiCaches
|
|
3
|
+
# Clears one record's cached API responses. Every record of a model goes through InvalidateAll.
|
|
3
4
|
class Invalidate
|
|
4
5
|
prepend ::Spree::ServiceModule::Base
|
|
5
6
|
|
|
@@ -16,17 +17,39 @@ module SpreeCmCommissioner
|
|
|
16
17
|
'SpreeCmCommissioner::VotingSession' => SpreeCmCommissioner::VotingSession
|
|
17
18
|
}.freeze
|
|
18
19
|
|
|
19
|
-
#
|
|
20
|
-
|
|
20
|
+
# CloudFront bills per path and only allows `*` at the end, so one prefix per namespace covers
|
|
21
|
+
# every segment's homepage_sections and homepage_background, plus homepage_data, instead of a
|
|
22
|
+
# path per BIT_SEGMENT key.
|
|
23
|
+
HOMEPAGE_SECTION_PATTERNS = [
|
|
24
|
+
'/api/v2/storefront/homepage*',
|
|
25
|
+
'/api/v2/tenant/homepage*'
|
|
26
|
+
].freeze
|
|
27
|
+
|
|
28
|
+
HOMEPAGE_BACKGROUND_PATTERNS = ['/api/v2/storefront/homepage*'].freeze
|
|
29
|
+
|
|
30
|
+
# Deliberately broad, and identical for single-record and all-record
|
|
31
|
+
# invalidation: a payment method is either vendor-owned or store-level, and a
|
|
32
|
+
# store-level one is served for every vendor through the fallback in
|
|
33
|
+
# SpreeCmCommissioner::PaymentMethods::Find. From the record alone we cannot
|
|
34
|
+
# tell which vendors are affected.
|
|
35
|
+
#
|
|
36
|
+
# vendor_id is a query param, so the trailing `*` is what clears every
|
|
37
|
+
# vendor's cached variant of this path in one go.
|
|
38
|
+
#
|
|
39
|
+
# Payment methods change a handful of times a year, so a broad invalidation
|
|
40
|
+
# is cheap.
|
|
41
|
+
PAYMENT_METHOD_PATTERNS = [
|
|
42
|
+
'/api/v2/tenant/payment_methods*',
|
|
43
|
+
'/api/v2/storefront/payment_methods*'
|
|
44
|
+
].freeze
|
|
45
|
+
|
|
46
|
+
# eg. 'Spree::Product', 123
|
|
47
|
+
def call(model:, id:)
|
|
21
48
|
model_class = SUPPORTED_MODELS[model]
|
|
22
49
|
|
|
23
50
|
return failure(nil, 'Model is not supported') if model_class.nil?
|
|
24
51
|
|
|
25
|
-
|
|
26
|
-
invalidate_single_record(model_class: model_class, id: id)
|
|
27
|
-
else
|
|
28
|
-
invalidate_all_records(model_class: model_class)
|
|
29
|
-
end
|
|
52
|
+
invalidate_single_record(model_class: model_class, id: id)
|
|
30
53
|
end
|
|
31
54
|
|
|
32
55
|
# eg. 'Spree::Product', 123 => ['/api/v2/tenant/products/123', '/api/v2/tenant/products/slug-of-product', ...]
|
|
@@ -38,7 +61,10 @@ module SpreeCmCommissioner
|
|
|
38
61
|
|
|
39
62
|
refresh_record_cache_key(record)
|
|
40
63
|
|
|
41
|
-
|
|
64
|
+
# The record's own class first (a Show found as a Spree::Taxon keeps its show paths), then the
|
|
65
|
+
# model asked for, since STI subclasses such as Spree::Gateway::Payway have no entry of their own.
|
|
66
|
+
patterns = single_api_pattern[record.class.name] || single_api_pattern[model_class.name]
|
|
67
|
+
api_patterns = patterns.map do |pattern|
|
|
42
68
|
pattern.gsub(/:(\w+)/) do
|
|
43
69
|
key = Regexp.last_match(1)
|
|
44
70
|
record.public_send(key).to_s
|
|
@@ -50,26 +76,13 @@ module SpreeCmCommissioner
|
|
|
50
76
|
success(record: record, api_patterns: api_patterns)
|
|
51
77
|
end
|
|
52
78
|
|
|
53
|
-
# eg. 'Spree::Product' => ['/api/v2/tenant/products/*', '/api/v2/storefront/products/*']
|
|
54
|
-
def invalidate_all_records(model_class:)
|
|
55
|
-
model_class.find_each do |record|
|
|
56
|
-
refresh_record_cache_key(record)
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
enqueue_invalidate(invalidate_uris[model_class.name])
|
|
60
|
-
|
|
61
|
-
success(api_patterns: invalidate_uris[model_class.name])
|
|
62
|
-
end
|
|
63
|
-
|
|
64
79
|
def single_api_pattern
|
|
65
80
|
{
|
|
66
|
-
'SpreeCmCommissioner::HomepageSection' =>
|
|
67
|
-
'SpreeCmCommissioner::HomepageBackground' =>
|
|
81
|
+
'SpreeCmCommissioner::HomepageSection' => HOMEPAGE_SECTION_PATTERNS,
|
|
82
|
+
'SpreeCmCommissioner::HomepageBackground' => HOMEPAGE_BACKGROUND_PATTERNS,
|
|
68
83
|
'SpreeCmCommissioner::SeatLayout' => ['/api/v2/storefront/seat_layouts/:id*'],
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
'/api/v2/storefront/homepage_data*'
|
|
72
|
-
],
|
|
84
|
+
# The app reads menus from homepage_data; nothing calls /menus any more.
|
|
85
|
+
'Spree::Menu' => ['/api/v2/storefront/homepage_data*'],
|
|
73
86
|
'Spree::Taxon' => [
|
|
74
87
|
'/api/v2/tenant/taxons/:id*',
|
|
75
88
|
'/api/v2/tenant/taxons/:permalink*',
|
|
@@ -92,50 +105,12 @@ module SpreeCmCommissioner
|
|
|
92
105
|
'/api/v2/storefront/accommodations/:id*',
|
|
93
106
|
'/api/v2/storefront/accommodations/:slug*'
|
|
94
107
|
],
|
|
95
|
-
'Spree::PaymentMethod' =>
|
|
96
|
-
}
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
def invalidate_uris
|
|
100
|
-
{
|
|
101
|
-
'SpreeCmCommissioner::HomepageSection' => homepage_section_patterns,
|
|
102
|
-
'SpreeCmCommissioner::HomepageBackground' => homepage_background_patterns,
|
|
103
|
-
'SpreeCmCommissioner::SeatLayout' => ['/api/v2/storefront/seat_layouts/*'],
|
|
104
|
-
'Spree::Menu' => ['/api/v2/storefront/menus/*'],
|
|
105
|
-
'Spree::Taxon' => ['/api/v2/storefront/taxons/*'],
|
|
106
|
-
'Spree::Product' => [
|
|
107
|
-
'/api/v2/tenant/products/*',
|
|
108
|
-
'/api/v2/storefront/products/*'
|
|
109
|
-
],
|
|
110
|
-
'Spree::Vendor' => [
|
|
111
|
-
'/api/v2/tenant/vendors/*',
|
|
112
|
-
'/api/v2/storefront/vendors/*',
|
|
113
|
-
'/api/v2/storefront/accommodations/*'
|
|
114
|
-
],
|
|
115
|
-
'Spree::PaymentMethod' => payment_method_patterns
|
|
108
|
+
'Spree::PaymentMethod' => PAYMENT_METHOD_PATTERNS
|
|
116
109
|
}
|
|
117
110
|
end
|
|
118
111
|
|
|
119
112
|
private
|
|
120
113
|
|
|
121
|
-
# Deliberately broad, and identical for single-record and all-record
|
|
122
|
-
# invalidation: a payment method is either vendor-owned or store-level, and a
|
|
123
|
-
# store-level one is served for every vendor through the fallback in
|
|
124
|
-
# SpreeCmCommissioner::PaymentMethods::Find. From the record alone we cannot
|
|
125
|
-
# tell which vendors are affected.
|
|
126
|
-
#
|
|
127
|
-
# vendor_id is a query param, so the trailing `*` is what clears every
|
|
128
|
-
# vendor's cached variant of this path in one go.
|
|
129
|
-
#
|
|
130
|
-
# Payment methods change a handful of times a year, so a broad invalidation
|
|
131
|
-
# is cheap.
|
|
132
|
-
def payment_method_patterns
|
|
133
|
-
[
|
|
134
|
-
'/api/v2/tenant/payment_methods*',
|
|
135
|
-
'/api/v2/storefront/payment_methods*'
|
|
136
|
-
]
|
|
137
|
-
end
|
|
138
|
-
|
|
139
114
|
# eg. updated_at: Fri, 26 Dec 2025 10:40:56.505450000 +07 +07:00
|
|
140
115
|
def refresh_record_cache_key(record)
|
|
141
116
|
# rubocop:disable Rails/SkipsModelValidations
|
|
@@ -147,29 +122,6 @@ module SpreeCmCommissioner
|
|
|
147
122
|
def enqueue_invalidate(patterns)
|
|
148
123
|
SpreeCmCommissioner::InvalidateCacheRequestJob.perform_later(patterns: patterns)
|
|
149
124
|
end
|
|
150
|
-
|
|
151
|
-
# Every key of BIT_SEGMENT (general, ticket, ..., ferry).
|
|
152
|
-
def homepage_segment
|
|
153
|
-
SpreeCmCommissioner::HomepageSectionBitwise::BIT_SEGMENT.keys
|
|
154
|
-
end
|
|
155
|
-
|
|
156
|
-
def homepage_section_patterns
|
|
157
|
-
homepage_segment.flat_map do |segment|
|
|
158
|
-
[
|
|
159
|
-
"/api/v2/storefront/homepage/#{segment}/homepage_sections*",
|
|
160
|
-
"/api/v2/tenant/homepage/#{segment}/homepage_sections*"
|
|
161
|
-
]
|
|
162
|
-
end
|
|
163
|
-
end
|
|
164
|
-
|
|
165
|
-
def homepage_background_patterns
|
|
166
|
-
patterns = homepage_segment.map do |segment|
|
|
167
|
-
"/api/v2/storefront/homepage/#{segment}/homepage_background*"
|
|
168
|
-
end
|
|
169
|
-
|
|
170
|
-
patterns << '/api/v2/storefront/homepage_data*'
|
|
171
|
-
patterns
|
|
172
|
-
end
|
|
173
125
|
end
|
|
174
126
|
end
|
|
175
127
|
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
module SpreeCmCommissioner
|
|
2
|
+
module ApiCaches
|
|
3
|
+
# Clears every record of a model for one storefront. `tenant` is required so a clear-all never
|
|
4
|
+
# pays for both namespaces by accident: nil is the BookMe+ storefront (/api/v2/storefront/
|
|
5
|
+
# paths), a SpreeCmCommissioner::Tenant is that tenant's API (/api/v2/tenant/ paths).
|
|
6
|
+
#
|
|
7
|
+
# Tenant APIs take the tenant from the OAuth token, not the URL, so every tenant shares one path
|
|
8
|
+
# today; taking the record keeps callers the same if tenant paths ever carry it.
|
|
9
|
+
#
|
|
10
|
+
# eg. InvalidateAll.call(model: 'SpreeCmCommissioner::HomepageSection', tenant: nil)
|
|
11
|
+
class InvalidateAll
|
|
12
|
+
prepend ::Spree::ServiceModule::Base
|
|
13
|
+
|
|
14
|
+
TENANT_PREFIX = '/api/v2/tenant/'.freeze
|
|
15
|
+
|
|
16
|
+
API_PATTERNS = {
|
|
17
|
+
'SpreeCmCommissioner::HomepageSection' => Invalidate::HOMEPAGE_SECTION_PATTERNS,
|
|
18
|
+
'SpreeCmCommissioner::HomepageBackground' => Invalidate::HOMEPAGE_BACKGROUND_PATTERNS,
|
|
19
|
+
'SpreeCmCommissioner::SeatLayout' => ['/api/v2/storefront/seat_layouts/*'],
|
|
20
|
+
'Spree::Menu' => ['/api/v2/storefront/homepage_data*'],
|
|
21
|
+
'Spree::Taxon' => ['/api/v2/storefront/taxons/*'],
|
|
22
|
+
'Spree::Product' => [
|
|
23
|
+
'/api/v2/tenant/products/*',
|
|
24
|
+
'/api/v2/storefront/products/*'
|
|
25
|
+
],
|
|
26
|
+
'Spree::Vendor' => [
|
|
27
|
+
'/api/v2/tenant/vendors/*',
|
|
28
|
+
'/api/v2/storefront/vendors/*',
|
|
29
|
+
'/api/v2/storefront/accommodations/*'
|
|
30
|
+
],
|
|
31
|
+
'Spree::PaymentMethod' => Invalidate::PAYMENT_METHOD_PATTERNS
|
|
32
|
+
}.freeze
|
|
33
|
+
|
|
34
|
+
def call(model:, tenant:)
|
|
35
|
+
model_class = Invalidate::SUPPORTED_MODELS[model] if API_PATTERNS.key?(model)
|
|
36
|
+
return failure(nil, 'Model is not supported') if model_class.nil?
|
|
37
|
+
return failure(nil, 'tenant must be a SpreeCmCommissioner::Tenant or nil') unless tenant.nil? || tenant.is_a?(SpreeCmCommissioner::Tenant)
|
|
38
|
+
|
|
39
|
+
refresh_records_cache_key(model_class, tenant)
|
|
40
|
+
|
|
41
|
+
api_patterns = API_PATTERNS[model].select { |pattern| pattern.start_with?(TENANT_PREFIX) == tenant.present? }
|
|
42
|
+
|
|
43
|
+
# Empty when the model has no API in this namespace, e.g. homepage backgrounds for a tenant,
|
|
44
|
+
# and CloudFront rejects an empty batch.
|
|
45
|
+
SpreeCmCommissioner::InvalidateCacheRequestJob.perform_later(patterns: api_patterns) if api_patterns.any?
|
|
46
|
+
|
|
47
|
+
success(api_patterns: api_patterns)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
# updated_at keys Spree's serializer and collection caches, so only this storefront's records
|
|
53
|
+
# are touched when the model is tenant-scoped.
|
|
54
|
+
def refresh_records_cache_key(model_class, tenant)
|
|
55
|
+
scope = model_class.column_names.include?('tenant_id') ? model_class.where(tenant_id: tenant&.id) : model_class.all
|
|
56
|
+
|
|
57
|
+
scope.find_each(&:touch)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
module SpreeCmCommissioner
|
|
2
|
+
module Guests
|
|
3
|
+
# Option-based fields (checkbox/radio/selection) become one GuestDynamicField row per selected
|
|
4
|
+
# option id; Guests::Update's own auto-cleanup destroys rows for deselected options. Fields not
|
|
5
|
+
# enabled on the guest's product are dropped.
|
|
6
|
+
class BuildDynamicFieldAttributes
|
|
7
|
+
def self.call(guest:, dynamic_fields:)
|
|
8
|
+
new(guest, dynamic_fields).call
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def initialize(guest, dynamic_fields)
|
|
12
|
+
@guest = guest
|
|
13
|
+
@dynamic_fields = dynamic_fields
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def call
|
|
17
|
+
return [] if @dynamic_fields.blank?
|
|
18
|
+
|
|
19
|
+
enabled_fields = @guest.line_item.product.dynamic_fields.index_by(&:id)
|
|
20
|
+
|
|
21
|
+
@dynamic_fields.flat_map do |dynamic_field_id, value|
|
|
22
|
+
dynamic_field = enabled_fields[dynamic_field_id.to_s.to_i]
|
|
23
|
+
next [] unless dynamic_field
|
|
24
|
+
|
|
25
|
+
if dynamic_field.requires_dynamic_field_options?
|
|
26
|
+
Array(value).compact_blank.map { |option_id| { dynamic_field_id: dynamic_field.id, dynamic_field_option_id: option_id } }
|
|
27
|
+
else
|
|
28
|
+
[{ dynamic_field_id: dynamic_field.id, value: value }]
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -21,32 +21,10 @@ module SpreeCmCommissioner
|
|
|
21
21
|
editable_attrs = guest.kyc_fields.flat_map { |field| SpreeCmCommissioner::KycBitwise::KYC_FIELD_ATTRIBUTES[field] || [] }
|
|
22
22
|
|
|
23
23
|
filtered = attrs.slice(*editable_attrs)
|
|
24
|
-
filtered[:guest_dynamic_fields_attributes] =
|
|
24
|
+
filtered[:guest_dynamic_fields_attributes] =
|
|
25
|
+
SpreeCmCommissioner::Guests::BuildDynamicFieldAttributes.call(guest: guest, dynamic_fields: attrs[:dynamic_fields])
|
|
25
26
|
filtered
|
|
26
27
|
end
|
|
27
|
-
|
|
28
|
-
# Builds the guest_dynamic_fields_attributes array SpreeCmCommissioner::Guests::Update
|
|
29
|
-
# expects, restricted to dynamic fields actually enabled for this guest's product (one
|
|
30
|
-
# `pluck`/load, not a query per submitted field). Option-based fields (checkbox/
|
|
31
|
-
# radio/selection) submit one or more selected option ids and become one
|
|
32
|
-
# GuestDynamicField row per selected option; Guests::Update's own auto-cleanup
|
|
33
|
-
# destroys any existing rows for options that were deselected.
|
|
34
|
-
def build_dynamic_field_attributes(guest, dynamic_fields)
|
|
35
|
-
return [] if dynamic_fields.blank?
|
|
36
|
-
|
|
37
|
-
enabled_fields = guest.line_item.product.dynamic_fields.index_by(&:id)
|
|
38
|
-
|
|
39
|
-
dynamic_fields.flat_map do |dynamic_field_id, value|
|
|
40
|
-
dynamic_field = enabled_fields[dynamic_field_id.to_s.to_i]
|
|
41
|
-
next [] unless dynamic_field
|
|
42
|
-
|
|
43
|
-
if dynamic_field.requires_dynamic_field_options?
|
|
44
|
-
Array(value).compact_blank.map { |option_id| { dynamic_field_id: dynamic_field.id, dynamic_field_option_id: option_id } }
|
|
45
|
-
else
|
|
46
|
-
[{ dynamic_field_id: dynamic_field.id, value: value }]
|
|
47
|
-
end
|
|
48
|
-
end
|
|
49
|
-
end
|
|
50
28
|
end
|
|
51
29
|
end
|
|
52
30
|
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
<% answers_by_field_id = @guest_dynamic_fields.group_by(&:dynamic_field_id) %>
|
|
2
|
+
<% i18n_scope = 'views.admin.guests.dynamic_fields' %>
|
|
3
|
+
|
|
4
|
+
<div class="row">
|
|
5
|
+
<div class="col-12">
|
|
6
|
+
<div class="card mb-3">
|
|
7
|
+
<div class="card-header">
|
|
8
|
+
<h5 class="card-title mb-0 h6"><%= I18n.t('title', scope: i18n_scope) %></h5>
|
|
9
|
+
</div>
|
|
10
|
+
|
|
11
|
+
<div class="card-body">
|
|
12
|
+
<% @dynamic_fields.group_by(&:data_fill_stage).each do |stage, dynamic_fields| %>
|
|
13
|
+
<h6 class="text-muted"><%= I18n.t(stage, scope: "#{i18n_scope}.stages") %></h6>
|
|
14
|
+
|
|
15
|
+
<% dynamic_fields.each do |dynamic_field| %>
|
|
16
|
+
<% answers = answers_by_field_id[dynamic_field.id] || [] %>
|
|
17
|
+
<% answer = answers.first %>
|
|
18
|
+
<% input_name = "#{f.object_name}[dynamic_fields][#{dynamic_field.id}]" %>
|
|
19
|
+
<% input_id = "dynamic_field_#{dynamic_field.id}" %>
|
|
20
|
+
<% selected_option_ids = answers.map(&:dynamic_field_option_id) %>
|
|
21
|
+
|
|
22
|
+
<div class="form-group">
|
|
23
|
+
<%= label_tag input_id, dynamic_field.label %>
|
|
24
|
+
|
|
25
|
+
<% case dynamic_field.data_type.to_sym %>
|
|
26
|
+
<% when :text %>
|
|
27
|
+
<%= text_field_tag input_name, answer&.value, id: input_id, class: 'form-control' %>
|
|
28
|
+
<% when :textarea %>
|
|
29
|
+
<%= text_area_tag input_name, answer&.value, id: input_id, rows: 3, class: 'form-control' %>
|
|
30
|
+
<% when :number %>
|
|
31
|
+
<%= number_field_tag input_name, answer&.value, id: input_id, min: 0, class: 'form-control' %>
|
|
32
|
+
<% when :boolean %>
|
|
33
|
+
<%= select_tag input_name,
|
|
34
|
+
options_for_select([[I18n.t('yes', scope: i18n_scope), 'true'], [I18n.t('no', scope: i18n_scope), 'false']], answer&.value),
|
|
35
|
+
include_blank: true, id: input_id, class: 'form-control' %>
|
|
36
|
+
<% when :radio %>
|
|
37
|
+
<% dynamic_field.dynamic_field_options.each do |option| %>
|
|
38
|
+
<div class="radio">
|
|
39
|
+
<label>
|
|
40
|
+
<%= radio_button_tag input_name, option.id, selected_option_ids.include?(option.id) %>
|
|
41
|
+
<%= option.value %>
|
|
42
|
+
</label>
|
|
43
|
+
</div>
|
|
44
|
+
<% end %>
|
|
45
|
+
<% when :selection, :checkbox %>
|
|
46
|
+
<% if dynamic_field.checkbox? || dynamic_field.multiple_select %>
|
|
47
|
+
<% dynamic_field.dynamic_field_options.each do |option| %>
|
|
48
|
+
<div class="checkbox">
|
|
49
|
+
<label>
|
|
50
|
+
<%= check_box_tag "#{input_name}[]", option.id, selected_option_ids.include?(option.id), id: nil %>
|
|
51
|
+
<%= option.value %>
|
|
52
|
+
</label>
|
|
53
|
+
</div>
|
|
54
|
+
<% end %>
|
|
55
|
+
<% else %>
|
|
56
|
+
<%= select_tag input_name,
|
|
57
|
+
options_from_collection_for_select(dynamic_field.dynamic_field_options, :id, :value, selected_option_ids.first),
|
|
58
|
+
include_blank: true, id: input_id, class: 'form-control' %>
|
|
59
|
+
<% end %>
|
|
60
|
+
<% end %>
|
|
61
|
+
</div>
|
|
62
|
+
<% end %>
|
|
63
|
+
<% end %>
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<% end %>
|
|
4
4
|
|
|
5
5
|
<% content_for :page_actions do %>
|
|
6
|
-
<%=
|
|
6
|
+
<%= render 'spree/admin/shared/api_cache_button', model: SpreeCmCommissioner::HomepageBackground.name %>
|
|
7
7
|
<%= button_link_to Spree.t(:new_homepage_background), new_admin_homepage_feed_homepage_background_path, { class: "btn-success", icon: 'add.svg', id: 'admin_new_homepage_background' } %>
|
|
8
8
|
<% end %>
|
|
9
9
|
|
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
<%= link_to plural_resource_name(SpreeCmCommissioner::HomepageSection), spree.admin_homepage_feed_homepage_section_index_url %> /
|
|
5
5
|
<%= Spree.t(:edit_section_relatable) %>
|
|
6
6
|
</div>
|
|
7
|
-
<%= button_link_to Spree.t(:clear_cache), admin_invalidate_api_caches_path(model: SpreeCmCommissioner::HomepageSection.name, id: @object.id), method: :post, class: "btn btn-outline-primary" %>
|
|
8
7
|
</div>
|
|
9
8
|
<% end %>
|
|
10
9
|
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
<% end %>
|
|
19
19
|
|
|
20
20
|
<% content_for :page_actions do %>
|
|
21
|
-
<%=
|
|
21
|
+
<%= render 'spree/admin/shared/api_cache_button', model: SpreeCmCommissioner::HomepageSection.name, tenant_id: selected_tenant&.id %>
|
|
22
22
|
<%= button_link_to Spree.t(:new_homepage_section), new_admin_homepage_feed_homepage_section_path(tenant_id: params[:tenant_id]), { class: "btn-success", icon: 'add.svg', id: 'admin_new_homepage_section' } %>
|
|
23
23
|
<% end %>
|
|
24
24
|
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<%# locals: model (an ApiCaches::Invalidate::SUPPORTED_MODELS key), id (one record), tenant_id (with
|
|
2
|
+
no id, clears every record of model on that tenant's API; neither clears the BookMe+ storefront). %>
|
|
3
|
+
<% id ||= nil %>
|
|
4
|
+
<% tenant_id ||= nil %>
|
|
5
|
+
<%= button_link_to Spree.t(:clear_cache), clear_admin_api_caches_path(model: model, id: id, tenant_id: tenant_id), method: :post, class: 'btn btn-outline-primary' %>
|
data/config/locales/en.yml
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
# See https://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points.
|
|
3
3
|
en:
|
|
4
4
|
views:
|
|
5
|
+
admin:
|
|
6
|
+
guests:
|
|
7
|
+
dynamic_fields:
|
|
8
|
+
title: "Custom fields"
|
|
9
|
+
stages:
|
|
10
|
+
pre_registration: "Before registration"
|
|
11
|
+
post_registration: "After registration"
|
|
12
|
+
during_check_in: "During check-in"
|
|
13
|
+
"yes": "Yes"
|
|
14
|
+
"no": "No"
|
|
5
15
|
manage_team:
|
|
6
16
|
add:
|
|
7
17
|
user_not_found: "User not found"
|
|
@@ -265,6 +275,7 @@ en:
|
|
|
265
275
|
riel: "៛"
|
|
266
276
|
|
|
267
277
|
spree:
|
|
278
|
+
clear_cache_requested: "Clearing cache. Changes appear in the app within about a minute."
|
|
268
279
|
default_inventory: "Default Inventory"
|
|
269
280
|
title: "Title"
|
|
270
281
|
confirm_clone_customer_notification: "Are you sure you want to clone \"%{title}\"?"
|
data/config/locales/km.yml
CHANGED
|
@@ -3,6 +3,16 @@
|
|
|
3
3
|
|
|
4
4
|
km:
|
|
5
5
|
views:
|
|
6
|
+
admin:
|
|
7
|
+
guests:
|
|
8
|
+
dynamic_fields:
|
|
9
|
+
title: "ព័ត៌មានបន្ថែម"
|
|
10
|
+
stages:
|
|
11
|
+
pre_registration: "មុនចុះឈ្មោះ"
|
|
12
|
+
post_registration: "ក្រោយចុះឈ្មោះ"
|
|
13
|
+
during_check_in: "ពេលចូលរួម"
|
|
14
|
+
"yes": "បាទ/ចាស"
|
|
15
|
+
"no": "ទេ"
|
|
6
16
|
manage_team:
|
|
7
17
|
add:
|
|
8
18
|
user_not_found: "រកមិនឃើញអ្នកប្រើប្រាស់"
|
|
@@ -222,6 +232,7 @@ km:
|
|
|
222
232
|
riel: "៛"
|
|
223
233
|
|
|
224
234
|
spree:
|
|
235
|
+
clear_cache_requested: "កំពុងសម្អាតឃ្លាំងទិន្នន័យ។ ការផ្លាស់ប្តូរនឹងបង្ហាញក្នុងកម្មវិធីក្នុងរយៈពេលប្រហែលមួយនាទី។"
|
|
225
236
|
default_inventory: "ស្តុកលំនាំដើម"
|
|
226
237
|
title: "ចំណងជើង"
|
|
227
238
|
confirm_clone_customer_notification: "តើអ្នកប្រាកដថាចង់ចម្លង \"%{title}\" មែនទេ?"
|
data/config/routes.rb
CHANGED
|
@@ -20,7 +20,11 @@ Spree::Core::Engine.add_routes do
|
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
namespace :admin do
|
|
23
|
-
|
|
23
|
+
resources :api_caches, only: [] do
|
|
24
|
+
collection do
|
|
25
|
+
post :clear
|
|
26
|
+
end
|
|
27
|
+
end
|
|
24
28
|
|
|
25
29
|
get '/system', to: redirect('/admin/system/waiting_room')
|
|
26
30
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: spree_cm_commissioner
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.14.
|
|
4
|
+
version: 2.14.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- You
|
|
@@ -824,6 +824,7 @@ files:
|
|
|
824
824
|
- app/controllers/concerns/spree_cm_commissioner/turnstile_protectable.rb
|
|
825
825
|
- app/controllers/concerns/spree_cm_commissioner/waiting_room_authorization.rb
|
|
826
826
|
- app/controllers/spree/admin/account_deletions_controller.rb
|
|
827
|
+
- app/controllers/spree/admin/api_caches_controller.rb
|
|
827
828
|
- app/controllers/spree/admin/base_controller_decorator.rb
|
|
828
829
|
- app/controllers/spree/admin/base_import_orders_controller.rb
|
|
829
830
|
- app/controllers/spree/admin/blazer_queries_controller.rb
|
|
@@ -1873,6 +1874,7 @@ files:
|
|
|
1873
1874
|
- app/overrides/spree/admin/cms_pages/index/cms_pages_tabs.html.erb.deface
|
|
1874
1875
|
- app/overrides/spree/admin/menu_items/_form/badge_field.html.erb.deface
|
|
1875
1876
|
- app/overrides/spree/admin/menus/edit/add_import_sprite_button.html.erb.deface
|
|
1877
|
+
- app/overrides/spree/admin/menus/index/clear_cache_button.html.erb.deface
|
|
1876
1878
|
- app/overrides/spree/admin/oauth_applications/_form/tenant_fields.html.erb.deface
|
|
1877
1879
|
- app/overrides/spree/admin/option_types/_form/attr_type.html.erb.deface
|
|
1878
1880
|
- app/overrides/spree/admin/option_types/_form/checkbox.html.erb.deface
|
|
@@ -1897,6 +1899,7 @@ files:
|
|
|
1897
1899
|
- app/overrides/spree/admin/orders/index/line_items_row.html.erb.deface
|
|
1898
1900
|
- app/overrides/spree/admin/orders/index/row_actions.html.erb.deface
|
|
1899
1901
|
- app/overrides/spree/admin/payment_methods/_form/display_on_field.html.erb.deface
|
|
1902
|
+
- app/overrides/spree/admin/payment_methods/index/clear_cache_button.html.erb.deface
|
|
1900
1903
|
- app/overrides/spree/admin/payment_methods/index/pagination.html.erb.deface
|
|
1901
1904
|
- app/overrides/spree/admin/payment_methods/index/preserve_tab_in_search.html.erb.deface
|
|
1902
1905
|
- app/overrides/spree/admin/payments/_form/amount_text_field.html.erb.deface
|
|
@@ -2039,6 +2042,7 @@ files:
|
|
|
2039
2042
|
- app/overrides/spree/admin/vendors/_form/star_rating.html.erb.deface
|
|
2040
2043
|
- app/overrides/spree/admin/vendors/_form/term_of_use.html.erb.deface
|
|
2041
2044
|
- app/overrides/spree/admin/vendors/_form/vendor_min_max_price.html.erb.deface
|
|
2045
|
+
- app/overrides/spree/admin/vendors/edit/clear_cache_button.html.erb.deface
|
|
2042
2046
|
- app/overrides/spree/admin/vendors/edit/vendor_tabs.html.erb.deface
|
|
2043
2047
|
- app/overrides/spree/admin/vendors/index/index.html.erb.deface
|
|
2044
2048
|
- app/overrides/spree/admin/webhooks_subscribers/_form/api_key.html.erb.deface
|
|
@@ -2089,6 +2093,7 @@ files:
|
|
|
2089
2093
|
- app/serializables/spree_cm_commissioner/pie_chart_event_aggregator.rb
|
|
2090
2094
|
- app/serializers/.gitkeep
|
|
2091
2095
|
- app/serializers/concerns/spree_cm_commissioner/api/v2/resource_serializer_concern_decorator.rb
|
|
2096
|
+
- app/serializers/spree/api/v2/base_serializer_decorator.rb
|
|
2092
2097
|
- app/serializers/spree/api/v2/platform/create_date_specific_item_adjustment_serializer.rb
|
|
2093
2098
|
- app/serializers/spree/api/v2/platform/create_guest_adjustment_serializer.rb
|
|
2094
2099
|
- app/serializers/spree/api/v2/platform/create_line_item_adjustment_serializer.rb
|
|
@@ -2315,6 +2320,7 @@ files:
|
|
|
2315
2320
|
- app/services/spree_cm_commissioner/agency_users/create.rb
|
|
2316
2321
|
- app/services/spree_cm_commissioner/agency_users/destroy.rb
|
|
2317
2322
|
- app/services/spree_cm_commissioner/api_caches/invalidate.rb
|
|
2323
|
+
- app/services/spree_cm_commissioner/api_caches/invalidate_all.rb
|
|
2318
2324
|
- app/services/spree_cm_commissioner/assign_product_to_section/update.rb
|
|
2319
2325
|
- app/services/spree_cm_commissioner/audit_events/cleanup_expired.rb
|
|
2320
2326
|
- app/services/spree_cm_commissioner/audit_logger.rb
|
|
@@ -2363,6 +2369,7 @@ files:
|
|
|
2363
2369
|
- app/services/spree_cm_commissioner/google_wallets/hotel_class_creator.rb
|
|
2364
2370
|
- app/services/spree_cm_commissioner/google_wallets/hotel_class_updater.rb
|
|
2365
2371
|
- app/services/spree_cm_commissioner/google_wallets/hotel_object_builder.rb
|
|
2372
|
+
- app/services/spree_cm_commissioner/guests/build_dynamic_field_attributes.rb
|
|
2366
2373
|
- app/services/spree_cm_commissioner/guests/claim_invite_guest_service.rb
|
|
2367
2374
|
- app/services/spree_cm_commissioner/guests/create.rb
|
|
2368
2375
|
- app/services/spree_cm_commissioner/guests/finalize.rb
|
|
@@ -2810,6 +2817,7 @@ files:
|
|
|
2810
2817
|
- app/views/spree/admin/guest_qr_codes/_guest_qr_codes_print_template.html.erb
|
|
2811
2818
|
- app/views/spree/admin/guest_qr_codes/index.html.erb
|
|
2812
2819
|
- app/views/spree/admin/guests/_check_in_status.html.erb
|
|
2820
|
+
- app/views/spree/admin/guests/_dynamic_fields.html.erb
|
|
2813
2821
|
- app/views/spree/admin/guests/_form.html.erb
|
|
2814
2822
|
- app/views/spree/admin/guests/_table.html.erb
|
|
2815
2823
|
- app/views/spree/admin/guests/edit.html.erb
|
|
@@ -2970,6 +2978,7 @@ files:
|
|
|
2970
2978
|
- app/views/spree/admin/service_fee_rules/edit.html.erb
|
|
2971
2979
|
- app/views/spree/admin/service_fee_rules/index.html.erb
|
|
2972
2980
|
- app/views/spree/admin/service_fee_rules/new.html.erb
|
|
2981
|
+
- app/views/spree/admin/shared/_api_cache_button.html.erb
|
|
2973
2982
|
- app/views/spree/admin/shared/_availability_percentage.html.erb
|
|
2974
2983
|
- app/views/spree/admin/shared/_cms_pages_tabs.html.erb
|
|
2975
2984
|
- app/views/spree/admin/shared/_customer_notification_tabs.html.erb
|