spree_cm_commissioner 2.8.15.pre.pre1 → 2.8.15.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 (256) hide show
  1. checksums.yaml +4 -4
  2. data/.env.example +0 -13
  3. data/.github/workflows/test_and_build_gem.yml +1 -2
  4. data/.gitignore +6 -0
  5. data/Gemfile.lock +1 -1
  6. data/app/controllers/concerns/spree_cm_commissioner/content_cachable.rb +2 -0
  7. data/app/controllers/spree/admin/homepage_section_controller.rb +4 -1
  8. data/app/controllers/spree/admin/import_new_orders_controller.rb +0 -25
  9. data/app/controllers/spree/admin/inventory_holds_controller.rb +1 -21
  10. data/app/controllers/spree/admin/system/waiting_room_controller.rb +0 -15
  11. data/app/controllers/spree/admin/taxons_controller_decorator.rb +19 -5
  12. data/app/controllers/spree/api/v2/storefront/account/orders_controller_decorator.rb +0 -5
  13. data/app/controllers/spree/api/v2/storefront/guests_controller.rb +5 -7
  14. data/app/controllers/spree/api/v2/storefront/homepage_sections_controller.rb +1 -0
  15. data/app/controllers/spree/api/v2/storefront/order_histories_controller.rb +2 -14
  16. data/app/controllers/spree/api/v2/storefront/preview_products_controller.rb +48 -0
  17. data/app/controllers/spree/api/v2/storefront/preview_sections_controller.rb +27 -0
  18. data/app/controllers/spree/api/v2/storefront/preview_taxons_controller.rb +18 -0
  19. data/app/controllers/spree/api/v2/storefront/products_controller_decorator.rb +15 -0
  20. data/app/controllers/spree/api/v2/storefront/taxons_controller_decorator.rb +15 -0
  21. data/app/controllers/spree/api/v2/storefront/ticket_transfers_controller.rb +3 -4
  22. data/app/controllers/spree/api/v2/tenant/ad_campaigns_controller.rb +25 -0
  23. data/app/controllers/spree/api/v2/tenant/base_controller.rb +4 -0
  24. data/app/controllers/spree/api/v2/tenant/episodes_controller.rb +38 -0
  25. data/app/controllers/spree/api/v2/tenant/homepage_sections_controller.rb +1 -0
  26. data/app/controllers/spree/api/v2/tenant/order_histories_controller.rb +2 -14
  27. data/app/controllers/spree/api/v2/tenant/preview_products_controller.rb +47 -0
  28. data/app/controllers/spree/api/v2/tenant/preview_sections_controller.rb +26 -0
  29. data/app/controllers/spree/api/v2/tenant/preview_shows_controller.rb +19 -0
  30. data/app/controllers/spree/api/v2/tenant/preview_taxons_controller.rb +19 -0
  31. data/app/controllers/spree/api/v2/tenant/products_controller.rb +1 -1
  32. data/app/controllers/spree/api/v2/tenant/show_contestants_controller.rb +52 -0
  33. data/app/controllers/spree/api/v2/tenant/show_elimination_sessions_controller.rb +57 -0
  34. data/app/controllers/spree/api/v2/tenant/show_people_controller.rb +49 -0
  35. data/app/controllers/spree/api/v2/tenant/show_person_assignments_controller.rb +36 -0
  36. data/app/controllers/spree/api/v2/tenant/shows_controller.rb +36 -0
  37. data/app/controllers/spree/api/v2/tenant/taxons_controller.rb +1 -1
  38. data/app/controllers/spree/api/v2/tenant/vote_packages_controller.rb +81 -0
  39. data/app/controllers/spree/api/v2/tenant/votes_controller.rb +94 -0
  40. data/app/controllers/spree/api/v2/tenant/voting_contestants_controller.rb +43 -0
  41. data/app/controllers/spree/api/v2/tenant/voting_credit_transactions_controller.rb +41 -0
  42. data/app/controllers/spree/api/v2/tenant/voting_credits_controller.rb +31 -0
  43. data/app/controllers/spree_cm_commissioner/admin/products_controller_decorator.rb +19 -0
  44. data/app/finders/spree_cm_commissioner/events/find_matches.rb +1 -0
  45. data/app/finders/spree_cm_commissioner/orders/find.rb +3 -33
  46. data/app/helpers/spree_cm_commissioner/admin/homepage_segment_helper.rb +2 -0
  47. data/app/interactors/spree_cm_commissioner/waiting_guests_caller.rb +20 -146
  48. data/app/interactors/spree_cm_commissioner/waiting_room_latest_system_metadata_puller.rb +3 -5
  49. data/app/interactors/spree_cm_commissioner/waiting_room_session_creator.rb +6 -25
  50. data/app/jobs/spree_cm_commissioner/inventory_items/bulk_adjust_quantities_job.rb +3 -21
  51. data/app/jobs/spree_cm_commissioner/inventory_items/bulk_adjust_quantities_on_hold_job.rb +3 -34
  52. data/app/jobs/spree_cm_commissioner/show_contestants/import_job.rb +9 -0
  53. data/app/jobs/spree_cm_commissioner/vote_fraud_event_job.rb +9 -0
  54. data/app/jobs/spree_cm_commissioner/voting_credit_allocation_job.rb +10 -0
  55. data/app/jobs/spree_cm_commissioner/voting_credit_de_allocation_job.rb +10 -0
  56. data/app/jobs/spree_cm_commissioner/waiting_guests_caller_job.rb +1 -6
  57. data/app/jobs/spree_cm_commissioner/waiting_room_latest_system_metadata_puller_job.rb +1 -1
  58. data/app/mailers/spree/order_mailer_decorator.rb +0 -2
  59. data/app/models/concerns/spree_cm_commissioner/homepage_section_bitwise.rb +2 -1
  60. data/app/models/concerns/spree_cm_commissioner/line_item_guests_concern.rb +2 -16
  61. data/app/models/concerns/spree_cm_commissioner/option_type_attr_type.rb +2 -1
  62. data/app/models/concerns/spree_cm_commissioner/order_holdable.rb +0 -6
  63. data/app/models/concerns/spree_cm_commissioner/order_state_machine.rb +10 -32
  64. data/app/models/concerns/spree_cm_commissioner/product_delegation.rb +0 -2
  65. data/app/models/concerns/spree_cm_commissioner/product_type.rb +1 -1
  66. data/app/models/concerns/spree_cm_commissioner/store_metadata.rb +3 -0
  67. data/app/models/concerns/spree_cm_commissioner/store_preference.rb +0 -1
  68. data/app/models/concerns/spree_cm_commissioner/taxon_kind.rb +3 -1
  69. data/app/models/concerns/spree_cm_commissioner/voting_session_live_stream_concern.rb +41 -0
  70. data/app/models/spree_cm_commissioner/claim.rb +27 -0
  71. data/app/models/spree_cm_commissioner/import.rb +2 -3
  72. data/app/models/spree_cm_commissioner/imports/import_contestant.rb +7 -0
  73. data/app/models/spree_cm_commissioner/imports/import_order.rb +0 -21
  74. data/app/models/spree_cm_commissioner/inventory_hold.rb +1 -1
  75. data/app/models/spree_cm_commissioner/line_item_decorator.rb +26 -0
  76. data/app/models/spree_cm_commissioner/maintenance_tasks/voting_session.rb +36 -0
  77. data/app/models/spree_cm_commissioner/notification.rb +0 -1
  78. data/app/models/spree_cm_commissioner/order_decorator.rb +0 -14
  79. data/app/models/spree_cm_commissioner/payment_decorator.rb +0 -11
  80. data/app/models/spree_cm_commissioner/preview_role.rb +8 -0
  81. data/app/models/spree_cm_commissioner/product_decorator.rb +52 -1
  82. data/app/models/spree_cm_commissioner/role_decorator.rb +4 -1
  83. data/app/models/spree_cm_commissioner/role_user_decorator.rb +8 -0
  84. data/app/models/spree_cm_commissioner/show.rb +171 -0
  85. data/app/models/spree_cm_commissioner/show_contestant.rb +39 -0
  86. data/app/models/spree_cm_commissioner/show_contestant_image.rb +11 -0
  87. data/app/models/spree_cm_commissioner/show_contestant_video.rb +15 -0
  88. data/app/models/spree_cm_commissioner/show_episode.rb +122 -0
  89. data/app/models/spree_cm_commissioner/show_person.rb +15 -0
  90. data/app/models/spree_cm_commissioner/show_person_assignment.rb +21 -0
  91. data/app/models/spree_cm_commissioner/show_person_image.rb +11 -0
  92. data/app/models/spree_cm_commissioner/taxon_decorator.rb +23 -1
  93. data/app/models/spree_cm_commissioner/taxonomy_decorator.rb +19 -1
  94. data/app/models/spree_cm_commissioner/tenant.rb +9 -0
  95. data/app/models/spree_cm_commissioner/user_decorator.rb +5 -0
  96. data/app/models/spree_cm_commissioner/variant_decorator.rb +4 -0
  97. data/app/models/spree_cm_commissioner/variant_options.rb +4 -0
  98. data/app/models/spree_cm_commissioner/vendor_decorator.rb +5 -0
  99. data/app/models/spree_cm_commissioner/vote.rb +16 -0
  100. data/app/models/spree_cm_commissioner/vote_fraud_event.rb +20 -0
  101. data/app/models/spree_cm_commissioner/voting_contestant.rb +100 -0
  102. data/app/models/spree_cm_commissioner/voting_credit.rb +74 -0
  103. data/app/models/spree_cm_commissioner/voting_credit_transaction.rb +55 -0
  104. data/app/models/spree_cm_commissioner/voting_session.rb +217 -0
  105. data/app/models/spree_cm_commissioner/voting_session_stat.rb +8 -0
  106. data/app/overrides/spree/admin/products/_form/allow_gift_transfer.html.erb.deface +1 -1
  107. data/app/overrides/spree/admin/products/_form/allow_transfer.html.erb.deface +1 -1
  108. data/app/overrides/spree/admin/products/_form/preview_checkbox.html.erb.deface +9 -0
  109. data/app/overrides/spree/admin/stores/_form/store_preferences.html.erb.deface +0 -9
  110. data/app/overrides/spree/admin/taxons/_form/assets_form.html.erb.deface +1 -10
  111. data/app/overrides/spree/admin/taxons/_form/preview_checkbox.html.erb.deface +7 -0
  112. data/app/serializers/spree/v2/storefront/homepage_section_serializer.rb +1 -1
  113. data/app/serializers/spree/v2/storefront/product_serializer_decorator.rb +2 -1
  114. data/app/serializers/spree/v2/storefront/role_serializer.rb +1 -1
  115. data/app/serializers/spree/v2/storefront/taxon_serializer_decorator.rb +2 -3
  116. data/app/serializers/spree/v2/tenant/ad_campaign_serializer.rb +13 -0
  117. data/app/serializers/spree/v2/tenant/advertisement_serializer.rb +11 -0
  118. data/app/serializers/spree/v2/tenant/homepage_section_serializer.rb +1 -1
  119. data/app/serializers/spree/v2/tenant/role_serializer.rb +1 -1
  120. data/app/serializers/spree/v2/tenant/show_contestant_serializer.rb +21 -0
  121. data/app/serializers/spree/v2/tenant/show_episode_serializer.rb +20 -0
  122. data/app/serializers/spree/v2/tenant/show_parent_serializer.rb +13 -0
  123. data/app/serializers/spree/v2/tenant/show_person_assignment_serializer.rb +16 -0
  124. data/app/serializers/spree/v2/tenant/show_person_serializer.rb +13 -0
  125. data/app/serializers/spree/v2/tenant/show_serializer.rb +23 -0
  126. data/app/serializers/spree/v2/tenant/video_serializer.rb +9 -0
  127. data/app/serializers/spree/v2/tenant/vote_package_serializer.rb +15 -0
  128. data/app/serializers/spree/v2/tenant/vote_serializer.rb +14 -0
  129. data/app/serializers/spree/v2/tenant/voting_contestant_serializer.rb +23 -0
  130. data/app/serializers/spree/v2/tenant/voting_credit_serializer.rb +10 -0
  131. data/app/serializers/spree/v2/tenant/voting_credit_transaction_serializer.rb +14 -0
  132. data/app/serializers/spree/v2/tenant/voting_session_serializer.rb +21 -0
  133. data/app/services/spree_cm_commissioner/api_caches/invalidate.rb +12 -0
  134. data/app/services/spree_cm_commissioner/cart/add_guest.rb +3 -30
  135. data/app/services/spree_cm_commissioner/cart/remove_guest.rb +1 -26
  136. data/app/services/spree_cm_commissioner/fraud_check.rb +299 -0
  137. data/app/services/spree_cm_commissioner/imports/contestants/create.rb +153 -0
  138. data/app/services/spree_cm_commissioner/imports/orders/create.rb +0 -1
  139. data/app/services/spree_cm_commissioner/inventory_holds/release.rb +0 -18
  140. data/app/services/spree_cm_commissioner/show_contestants/normalize_video_highlights.rb +57 -0
  141. data/app/services/spree_cm_commissioner/show_persons/contestant_assigner.rb +51 -0
  142. data/app/services/spree_cm_commissioner/url_embed/youtube_embed.rb +44 -0
  143. data/app/services/spree_cm_commissioner/vote_counters/audit_counters.rb +43 -0
  144. data/app/services/spree_cm_commissioner/vote_counters/base.rb +31 -0
  145. data/app/services/spree_cm_commissioner/vote_counters/increment.rb +44 -0
  146. data/app/services/spree_cm_commissioner/vote_counters/per_contestant_counter.rb +68 -0
  147. data/app/services/spree_cm_commissioner/vote_counters/rebuild_from_db.rb +70 -0
  148. data/app/services/spree_cm_commissioner/vote_counters/snapshot_to_db.rb +113 -0
  149. data/app/services/spree_cm_commissioner/vote_credit_deductor.rb +68 -0
  150. data/app/services/spree_cm_commissioner/vote_package/create.rb +150 -0
  151. data/app/services/spree_cm_commissioner/vote_package/update.rb +97 -0
  152. data/app/services/spree_cm_commissioner/vote_processor.rb +144 -0
  153. data/app/services/spree_cm_commissioner/voting_contestants/advancer.rb +335 -0
  154. data/app/services/spree_cm_commissioner/voting_contestants/assigner.rb +40 -0
  155. data/app/services/spree_cm_commissioner/voting_contestants/bulk_updater.rb +109 -0
  156. data/app/services/spree_cm_commissioner/voting_credits/admin_grant.rb +37 -0
  157. data/app/services/spree_cm_commissioner/voting_credits/allocate.rb +99 -0
  158. data/app/services/spree_cm_commissioner/voting_credits/claimable_scope_resolver.rb +64 -0
  159. data/app/services/spree_cm_commissioner/voting_credits/credit_calculator.rb +35 -0
  160. data/app/services/spree_cm_commissioner/voting_credits/de_allocate.rb +87 -0
  161. data/app/services/spree_cm_commissioner/voting_leaderboards/calculate_score.rb +67 -0
  162. data/app/services/spree_cm_commissioner/voting_leaderboards/combined_result.rb +190 -0
  163. data/app/services/spree_cm_commissioner/voting_sessions/finalize.rb +105 -0
  164. data/app/services/spree_cm_commissioner/waiting_room_system_metadata_fetcher.rb +7 -9
  165. data/app/services/spree_cm_commissioner/waiting_room_system_metadata_setter.rb +6 -16
  166. data/app/views/spree/admin/homepage_section/_form.html.erb +5 -0
  167. data/app/views/spree/admin/import_new_orders/index.html.erb +0 -4
  168. data/app/views/spree/admin/import_new_orders/show.html.erb +0 -30
  169. data/app/views/spree/admin/imports/index.html.erb +0 -2
  170. data/app/views/spree/admin/inventory_holds/index.html.erb +3 -8
  171. data/app/views/spree/admin/system/waiting_room/show.html.erb +0 -53
  172. data/app/views/spree_cm_commissioner/order_mailer/purchased_items/_items.html.erb +9 -12
  173. data/config/initializers/spree_permitted_attributes.rb +8 -4
  174. data/config/locales/en.yml +49 -18
  175. data/config/locales/km.yml +36 -13
  176. data/config/routes.rb +30 -3
  177. data/db/migrate/20260309230148_create_cm_show_people.rb +14 -0
  178. data/db/migrate/20260309230149_create_cm_show_people_assignments.rb +16 -0
  179. data/db/migrate/20260310082711_create_cm_show_contestants.rb +28 -0
  180. data/db/migrate/20260310082720_create_cm_voting_sessions.rb +21 -0
  181. data/db/migrate/20260310082721_create_cm_voting_contestants.rb +23 -0
  182. data/db/migrate/20260310082734_add_voting_fields_to_spree_taxons.rb +9 -0
  183. data/db/migrate/20260310082735_add_type_to_spree_products.rb +6 -0
  184. data/db/migrate/20260310082749_create_cm_voting_credits.rb +27 -0
  185. data/db/migrate/20260326080200_create_cm_voting_credit_transactions.rb +27 -0
  186. data/db/migrate/20260330160000_create_cm_votes.rb +25 -0
  187. data/db/migrate/20260401072500_add_advanced_from_to_cm_voting_contestants.rb +7 -0
  188. data/db/migrate/20260402000001_add_voting_credit_scope_to_spree_taxons.rb +6 -0
  189. data/db/migrate/20260402000002_rename_scopeable_to_votable_in_cm_voting_credits.rb +12 -0
  190. data/db/migrate/20260403070000_add_name_to_cm_voting_sessions.rb +5 -0
  191. data/db/migrate/20260406000001_add_vendor_id_to_voting_tables.rb +6 -0
  192. data/db/migrate/20260406000001_rename_votes_remaining_to_amount_in_cm_voting_credits.rb +11 -0
  193. data/db/migrate/20260408085255_add_show_id_and_vendor_id_to_cm_voting_sessions.rb +9 -0
  194. data/db/migrate/20260420000001_rename_type_to_credit_type_in_cm_voting_credits.rb +25 -0
  195. data/db/migrate/20260422000001_create_cm_vote_fraud_events.rb +23 -0
  196. data/db/migrate/20260423000001_add_preview_to_taxons_products_and_sections.rb +11 -0
  197. data/db/migrate/20260423000002_create_preview_roles.rb +24 -0
  198. data/db/migrate/20260515120000_add_public_metadata_to_cm_voting_sessions.rb +5 -0
  199. data/db/migrate/20260518090920_add_unique_voter_count_to_voting_contestants.rb +5 -0
  200. data/db/migrate/20260518094322_create_cm_voting_session_stats.rb +17 -0
  201. data/db/migrate/20260520000001_add_scoring_model_to_cm_voting_sessions.rb +5 -0
  202. data/db/migrate/20260520000001_optimize_cm_votes_indexes.rb +22 -0
  203. data/db/migrate/20260525042257_add_vote_number_to_cm_voting_contestants.rb +18 -0
  204. data/db/migrate/20260527035430_add_confirmed_rank_to_cm_voting_contestants.rb +5 -0
  205. data/db/migrate/20260527062005_add_eliminated_at_to_cm_show_contestants.rb +5 -0
  206. data/db/migrate/20260603063652_add_parent_to_voting_sessions.rb +9 -0
  207. data/db/migrate/20260603090000_add_session_type_to_cm_voting_sessions.rb +6 -0
  208. data/db/migrate/20260608000000_add_display_to_cm_voting_sessions.rb +5 -0
  209. data/db/migrate/20260623082233_create_cm_claims.rb +14 -0
  210. data/db/migrate/20260624175658_add_unique_index_to_cm_claims.rb +11 -0
  211. data/docs/sql/jsonb_query_guide.md +57 -0
  212. data/lib/spree_cm_commissioner/test_helper/factories/claim_factory.rb +14 -0
  213. data/lib/spree_cm_commissioner/test_helper/factories/show_episode_factory.rb +12 -0
  214. data/lib/spree_cm_commissioner/test_helper/factories/show_factory.rb +120 -0
  215. data/lib/spree_cm_commissioner/test_helper/factories/vote_credit_factory.rb +37 -0
  216. data/lib/spree_cm_commissioner/test_helper/factories/vote_factory.rb +28 -0
  217. data/lib/spree_cm_commissioner/test_helper/factories/voting_credit_transaction_factory.rb +11 -0
  218. data/lib/spree_cm_commissioner/test_helper/factories/voting_session_factory.rb +11 -0
  219. data/lib/spree_cm_commissioner/version.rb +1 -1
  220. data/lib/spree_cm_commissioner.rb +7 -1
  221. data/spree_cm_commissioner.gemspec +1 -1
  222. metadata +134 -36
  223. data/app/finders/spree_cm_commissioner/orders/find_by_all_state.rb +0 -11
  224. data/app/interactors/spree_cm_commissioner/payment_incomplete_notification_sender.rb +0 -25
  225. data/app/interactors/spree_cm_commissioner/release_inventory_item_notification_sender.rb +0 -24
  226. data/app/jobs/concerns/spree_cm_commissioner/idempotent_job.rb +0 -12
  227. data/app/jobs/spree_cm_commissioner/cancel_import_order_job.rb +0 -11
  228. data/app/jobs/spree_cm_commissioner/idempotency_keys/prune_job.rb +0 -20
  229. data/app/jobs/spree_cm_commissioner/payment_incomplete_notification_job.rb +0 -12
  230. data/app/jobs/spree_cm_commissioner/telegram_alerts/order_integrity_check_job.rb +0 -17
  231. data/app/jobs/spree_cm_commissioner/waiting_room/publish_lobby_path_job.rb +0 -17
  232. data/app/jobs/spree_cm_commissioner/waiting_room/stamp_queue_positions_job.rb +0 -14
  233. data/app/models/spree_cm_commissioner/idempotency_key.rb +0 -21
  234. data/app/models/spree_cm_commissioner/imported_order.rb +0 -6
  235. data/app/models/spree_cm_commissioner/taxon_ad_banner.rb +0 -12
  236. data/app/notifications/spree_cm_commissioner/order_general_notification.rb +0 -15
  237. data/app/overrides/spree/admin/orders/_search/payment_number_search_field.html.erb.deface +0 -8
  238. data/app/overrides/spree/admin/products/_form/enable_telegram_alert.html.erb.deface +0 -14
  239. data/app/services/spree_cm_commissioner/cart/add_item_decorator.rb +0 -22
  240. data/app/services/spree_cm_commissioner/cart/create_guest.rb +0 -80
  241. data/app/services/spree_cm_commissioner/cart/empty_decorator.rb +0 -23
  242. data/app/services/spree_cm_commissioner/cart/remove_item_decorator.rb +0 -17
  243. data/app/services/spree_cm_commissioner/cart/remove_line_item_decorator.rb +0 -17
  244. data/app/services/spree_cm_commissioner/cart/set_quantity_decorator.rb +0 -17
  245. data/app/services/spree_cm_commissioner/imports/orders/cancel.rb +0 -63
  246. data/app/services/spree_cm_commissioner/telegram_alerts/checks/order_complete_payment_not_paid.rb +0 -21
  247. data/app/services/spree_cm_commissioner/telegram_alerts/checks/payment_paid_order_not_complete.rb +0 -21
  248. data/app/services/spree_cm_commissioner/telegram_alerts/order_integrity_alert.rb +0 -112
  249. data/app/services/spree_cm_commissioner/telegram_alerts/order_integrity_checks_runner.rb +0 -32
  250. data/app/services/spree_cm_commissioner/waiting_room/publish_lobby_path.rb +0 -57
  251. data/app/services/spree_cm_commissioner/waiting_room/stamp_queue_positions.rb +0 -185
  252. data/app/services/spree_cm_commissioner/waiting_room_lobby_metadata_fetcher.rb +0 -47
  253. data/app/views/spree/admin/import_new_orders/_cancel_modal.html.erb +0 -35
  254. data/db/migrate/20260616000001_add_canceled_by_id_to_cm_imports.rb +0 -5
  255. data/db/migrate/20260617000002_create_cm_imported_orders.rb +0 -12
  256. data/db/migrate/20260619000001_create_cm_idempotency_keys.rb +0 -11
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 400b9f97bf94e87bc297ba238423a80ef7f5b94774a39170c48e809350f24bb3
4
- data.tar.gz: dade198707fa92ad3c258205d7616187007e865890906419fcbfaaa36b168dbe
3
+ metadata.gz: 3ac1f1c30c374b8f4ad3dd06b486bbe1e25cbfa1f1c26bbd7a3d53895aaefaa2
4
+ data.tar.gz: affc12ef47495c5c51530c1fae89c771380b3759825932ea263ec5474abbb289
5
5
  SHA512:
6
- metadata.gz: bf0faa81d8026d4283c6292d40c3c0c60be8d5d5e1e755c9eb03c950fd94de02f262e3946473a45a2b8500355e37781b175c7d425ce5c292d5c59e1fbc316533
7
- data.tar.gz: dfd7f46061291a7ddce0780aacb82d9e9129c52ec1b01244b6a48cb1a008a1ee339b5c878656dc4e4091d0492c89de627758cd42ff8c8706059d135a1bb604fc
6
+ metadata.gz: f14fbec2aadcfdf2ecb3e463fe26321d581f40ade6010ace88f1e2eb4dc9f8c3f80d329f8d3a8664133dd223376f17feca17a7413c3f9d515bc1c1f8eaa884d4
7
+ data.tar.gz: 53239fc959124a3cc3d6fe74804bbee08b576161b3914f45dab14b1e9be03c84000cfe716ce012c1c86f7903a3aa72d5127bc2979ff5f3a5a67c8a8a1a643407
data/.env.example CHANGED
@@ -20,13 +20,6 @@ WAITING_ROOM_SESSION_SIGNATURE="e6b2********************26e3"
20
20
  WAITING_ROOM_SESSION_EXPIRE_DURATION_IN_SECOND=
21
21
  WAITING_ROOM_MIN_SESSIONS_COUNT=5
22
22
  WAITING_ROOM_DISABLED=no
23
- WAITING_ROOM_POSITION_STAMP_LIMIT=1000
24
- WAITING_ROOM_FIRESTORE_BATCH_SIZE=500
25
- WAITING_ROOM_MIN_WAIT_TO_ENTER_SECONDS=600 # Waiting Room step floor (10 min)
26
- WAITING_ROOM_MIN_QUEUE_TO_ENTER_SECONDS=300 # Queue step floor (5 min)
27
- WAITING_ROOM_MAX_WAIT_TO_DISPLAY_SECONDS=3600 # above this the ETA is hidden (1 h)
28
- WAITING_ROOM_CALLER_INTERVAL_SECONDS=60 # how often the caller runs; match the waiting_guests_caller cron
29
- WAITING_ROOM_MAX_ETA_BATCH_SIZE=50 # cap on batch size assumed when estimating the ETA
30
23
 
31
24
  # Vattanac Bank
32
25
  VATTANAC_AES_SECRET_KEY= ""
@@ -70,9 +63,3 @@ PAYMENT_LOCK_MIN_DURATION_IN_MINUTES=5 # Minimum remaining hold time requir
70
63
  MAX_ACTIVE_HOLDS_PER_USER=3 # Max concurrent active holds allowed per user across all orders
71
64
  MAX_HOLDS_PER_IP_PER_HOUR=5 # Max hold attempts per IP address within a rolling 1-hour window (abuse prevention)
72
65
  HOLD_COOLDOWN_AFTER_EXPIRY_IN_MINUTES=2 # Cooldown period before a user can re-acquire a hold after one expired on them
73
-
74
- # See: app/models/spree_cm_commissioner/payment_decorator.rb
75
- PAYMENT_INCOMPLETE_NOTIFICATION_DELAY_IN_MINUTES=5 # Delay before notifying a customer that their order is still unpaid after payment is created
76
-
77
- # See: app/jobs/spree_cm_commissioner/telegram_alerts/order_integrity_check_job.rb
78
- ORDER_INTEGRITY_TELEGRAM_ALERT_ENABLED="no" # Set to yes to enable the order integrity Telegram alert job (off by default to avoid flooding Telegram rate limits during traffic spikes)
@@ -163,9 +163,8 @@ jobs:
163
163
  - name: Security
164
164
  env:
165
165
  DATABASE_URL: postgres://myuser:mypassword@localhost:5432/test_db
166
-
167
- # Using --no-threads to bypass a known Ruby 3.2.0 GC segmentation fault in CI.
168
166
  run: |
167
+ # Using --no-threads to bypass a known Ruby 3.2.0 GC segmentation fault in CI.
169
168
  bundle exec brakeman --no-threads --no-exit-on-warn
170
169
 
171
170
  test:
data/.gitignore CHANGED
@@ -34,3 +34,9 @@ vendor/bundle/
34
34
  # Cursor
35
35
  .cursor
36
36
  dump.rdb
37
+ # Added by code-review-graph
38
+ .code-review-graph/
39
+ .cursorrules
40
+ .claude
41
+ CLAUDE.md
42
+ AGENTS.md
data/Gemfile.lock CHANGED
@@ -34,7 +34,7 @@ GIT
34
34
  PATH
35
35
  remote: .
36
36
  specs:
37
- spree_cm_commissioner (2.8.15.pre.pre1)
37
+ spree_cm_commissioner (2.8.15.pre.pre3)
38
38
  activerecord-multi-tenant
39
39
  activerecord_json_validator (~> 2.1, >= 2.1.3)
40
40
  aws-sdk-cloudfront
@@ -16,6 +16,7 @@ module SpreeCmCommissioner
16
16
  GuestCardClassesController
17
17
  SeatLayoutController
18
18
  CmsPagesController
19
+ AdvertisementsController
19
20
  ].freeze
20
21
 
21
22
  # Priority 2: Semi-Static (1 hour)
@@ -25,6 +26,7 @@ module SpreeCmCommissioner
25
26
  HomepageDataController
26
27
  PopularRoutesController
27
28
  RoutePlacesController
29
+ ShowsController
28
30
  ].freeze
29
31
 
30
32
  # Priority 3: Moderate Freshness (30 minutes)
@@ -51,7 +51,10 @@ module Spree
51
51
  def permitted_resource_params
52
52
  segment_value = helpers.calculate_segment_value(params[:spree_cm_commissioner_homepage_section])
53
53
 
54
- params.require(:spree_cm_commissioner_homepage_section).permit(:title, :description, :active, :tenant_id).merge(segment: segment_value)
54
+ params
55
+ .require(:spree_cm_commissioner_homepage_section)
56
+ .permit(:title, :description, :active, :tenant_id, :preview)
57
+ .merge(segment: segment_value)
55
58
  end
56
59
 
57
60
  def load_tenants
@@ -23,31 +23,6 @@ module Spree
23
23
  end
24
24
  end
25
25
  end
26
-
27
- # POST: /admin/orders/import_new_orders/:id/cancel
28
- def cancel
29
- import = model_class.find(params[:id])
30
- show_url = spree.admin_import_new_order_url(import.id)
31
-
32
- unless import.cancellable?
33
- flash[:error] = 'These orders cannot be canceled.' # rubocop:disable Rails/I18nLocaleTexts
34
- redirect_to show_url and return
35
- end
36
-
37
- if params[:confirm_name].to_s.strip != import.name.to_s.strip
38
- flash[:error] = 'Import name did not match. Cancellation aborted.' # rubocop:disable Rails/I18nLocaleTexts
39
- redirect_to show_url and return
40
- end
41
-
42
- SpreeCmCommissioner::CancelImportOrderJob.perform_later(
43
- import_order_id: import.id,
44
- canceled_by_user_id: spree_current_user.id,
45
- cancellation_reason: params[:cancellation_reason].to_s.strip.presence
46
- )
47
-
48
- flash[:success] = "Canceling orders from \"#{import.name}\". This runs in the background."
49
- redirect_to show_url
50
- end
51
26
  end
52
27
  end
53
28
  end
@@ -9,7 +9,6 @@ module Spree
9
9
  q['s'] ||= 'created_at desc'
10
10
  q['created_at_gt'] = parse_time_or_nil(q['created_at_gt'], :beginning_of_day) if q['created_at_gt'].present?
11
11
  q['created_at_lt'] = parse_time_or_nil(q['created_at_lt'], :end_of_day) if q['created_at_lt'].present?
12
- normalize_enum_filters!(q)
13
12
 
14
13
  @search = scope.ransack(q)
15
14
  result_scope = @search.result(distinct: true)
@@ -19,11 +18,7 @@ module Spree
19
18
  .page(params[:page])
20
19
  .per(params[:per_page] || Spree::Backend::Config[:admin_orders_per_page])
21
20
 
22
- # Status tab counts must reflect every other filter but NOT the status filter, so
23
- # each tab shows how many holds it would return (otherwise selecting one status
24
- # zeroes out all the other tabs).
25
- @status_counts = scope.ransack(q.except('status_eq')).result(distinct: true)
26
- .reorder(nil).group(:status).count
21
+ @status_counts = result_scope.reorder(nil).group(:status).count
27
22
  end
28
23
 
29
24
  def release
@@ -49,23 +44,8 @@ module Spree
49
44
 
50
45
  private
51
46
 
52
- # `status` and `release_reason` are integer-backed enums. Ransack casts a string
53
- # value for an integer column with String#to_i, so "payment_locked" becomes 0
54
- # (== :pending) and every label silently filters to the wrong status. Translate
55
- # enum labels to their stored integer before searching.
56
- def normalize_enum_filters!(query)
57
- {
58
- 'status_eq' => SpreeCmCommissioner::InventoryHold.statuses,
59
- 'release_reason_eq' => SpreeCmCommissioner::InventoryHold.release_reasons
60
- }.each do |key, mapping|
61
- value = query[key]
62
- query[key] = mapping[value.to_s] if value.present? && mapping.key?(value.to_s)
63
- end
64
- end
65
-
66
47
  def back_params
67
48
  raw_params = params[:back_params].presence || params
68
- raw_params = raw_params.to_unsafe_h if raw_params.respond_to?(:to_unsafe_h)
69
49
  ActionController::Parameters.new(raw_params).permit(:page, :per_page, q: {})
70
50
  end
71
51
 
@@ -4,10 +4,7 @@ module Spree
4
4
  class WaitingRoomController < Spree::Admin::BaseController
5
5
  def show
6
6
  @fetcher = SpreeCmCommissioner::WaitingRoomSystemMetadataFetcher.new
7
- @lobby_fetcher = SpreeCmCommissioner::WaitingRoomLobbyMetadataFetcher.new
8
-
9
7
  @fetcher.load_document_data
10
- @lobby_fetcher.load_document_data
11
8
 
12
9
  @active_sesions_count = SpreeCmCommissioner::WaitingRoomSession.active.count
13
10
  end
@@ -32,18 +29,6 @@ module Spree
32
29
 
33
30
  redirect_back fallback_location: admin_system_waiting_room_path
34
31
  end
35
-
36
- def publish_lobby_path
37
- result = SpreeCmCommissioner::WaitingRoom::PublishLobbyPath.call
38
-
39
- if result.success?
40
- flash[:success] = "Published waiting guests records path: #{result.value[:records_path]}"
41
- else
42
- flash[:error] = result.error.to_s
43
- end
44
-
45
- redirect_back fallback_location: admin_system_waiting_room_path
46
- end
47
32
  end
48
33
  end
49
34
  end
@@ -3,6 +3,7 @@ module Spree
3
3
  module TaxonsControllerDecorator
4
4
  def self.prepended(base)
5
5
  base.before_action :build_assets, only: %i[create update]
6
+ base.before_action :normalize_sti_taxon_params, only: %i[create update]
6
7
  end
7
8
 
8
9
  # override
@@ -27,10 +28,6 @@ module Spree
27
28
  remove_asset(@taxon.home_banner)
28
29
  end
29
30
 
30
- def remove_ad_banner
31
- remove_asset(@taxon.ad_banner)
32
- end
33
-
34
31
  def remove_video_banner
35
32
  remove_asset(@taxon.video_banner)
36
33
  end
@@ -52,9 +49,26 @@ module Spree
52
49
  @taxon.build_app_banner(attachment: permitted_resource_params.delete(:app_banner)) if permitted_resource_params[:app_banner]
53
50
  @taxon.build_web_banner(attachment: permitted_resource_params.delete(:web_banner)) if permitted_resource_params[:web_banner]
54
51
  @taxon.build_home_banner(attachment: permitted_resource_params.delete(:home_banner)) if permitted_resource_params[:home_banner]
55
- @taxon.build_ad_banner(attachment: permitted_resource_params.delete(:ad_banner)) if permitted_resource_params[:ad_banner]
56
52
  @taxon.build_video_banner(attachment: permitted_resource_params.delete(:video_banner)) if permitted_resource_params[:video_banner]
57
53
  end
54
+
55
+ # Some admin forms submit STI records under their class key
56
+ # (e.g. :spree_cm_commissioner_show) while Spree update reads :taxon.
57
+ # Merge both into :taxon so all edited fields are persisted.
58
+ def normalize_sti_taxon_params
59
+ return unless @taxon
60
+
61
+ sti_key = @taxon.class.model_name.param_key.to_sym
62
+ sti_params = params[sti_key]
63
+ return unless sti_params.is_a?(::ActionController::Parameters)
64
+
65
+ base_params = params[:taxon]
66
+ base_hash = base_params.is_a?(::ActionController::Parameters) ? base_params.to_unsafe_h : {}
67
+ sti_hash = sti_params.to_unsafe_h
68
+ merged_params = ::ActionController::Parameters.new(base_hash.merge(sti_hash))
69
+
70
+ params[:taxon] = merged_params
71
+ end
58
72
  end
59
73
  end
60
74
  end
@@ -44,11 +44,6 @@ module Spree
44
44
  def collection_finder
45
45
  SpreeCmCommissioner::Orders::FindByState
46
46
  end
47
-
48
- # this allows fetching a single order regardless of its state.
49
- def resource_finder
50
- SpreeCmCommissioner::Orders::FindByAllState
51
- end
52
47
  end
53
48
  end
54
49
  end
@@ -31,15 +31,13 @@ module Spree
31
31
  def create
32
32
  spree_authorize! :update, spree_current_order, order_token
33
33
 
34
- result = SpreeCmCommissioner::Cart::CreateGuest.call(
35
- line_item: parent,
36
- options: guest_params
37
- )
34
+ resource = scope.new(guest_params)
38
35
 
39
- if result.success?
40
- render_serialized_payload(201) { serialize_resource(result.value) }
36
+ if resource.save
37
+ resource.save_and_move_to_next_stage
38
+ render_serialized_payload(201) { serialize_resource(resource) }
41
39
  else
42
- render_error_payload(result.value.errors, 422)
40
+ render_error_payload(resource.errors, 422)
43
41
  end
44
42
  end
45
43
 
@@ -19,6 +19,7 @@ module Spree
19
19
  @collection ||= model_class.filter_by_segment(params[:homepage_id] || :general)
20
20
  .active
21
21
  .where(tenant_id: nil)
22
+ .where(preview: false)
22
23
  .order(position: :asc)
23
24
  end
24
25
  end
@@ -30,25 +30,13 @@ module Spree
30
30
  private
31
31
 
32
32
  def collection
33
- # WARNING: Do NOT remove .order(id: :desc).
34
- # Mobile relies on this ordering for paginated order_histories.
35
- # Removing it will cause mobile to paginate by oldest records first (last 10 oldest),
36
- # breaking the "most recent orders" display on the order history screen.
37
- # It related to user cancel or complete order flow, which relies on the most recent orders being returned first.
38
33
  if spree_current_user.present?
39
- spree_current_user.orders
40
- .not_archived
41
- .incomplete
42
- .order(id: :desc)
34
+ spree_current_user.orders.payment.not_archived
43
35
  else
44
36
  order_tokens = Array(params[:order_tokens])
45
37
  return Spree::Order.none if order_tokens.empty?
46
38
 
47
- Spree::Order.not_archived
48
- .incomplete
49
- .without_user
50
- .where(token: order_tokens)
51
- .order(id: :desc)
39
+ Spree::Order.payment.not_archived.without_user.where(token: order_tokens)
52
40
  end
53
41
  end
54
42
 
@@ -0,0 +1,48 @@
1
+ module Spree
2
+ module Api
3
+ module V2
4
+ module Storefront
5
+ class PreviewProductsController < ::Spree::Api::V2::Storefront::ProductsController
6
+ before_action :require_spree_current_user
7
+
8
+ private
9
+
10
+ def scope(**args)
11
+ taxon_type = model_class.reflect_on_association(:taxons).klass.polymorphic_name
12
+
13
+ # IDs of products that have been explicitly assigned their own PreviewRole.
14
+ products_with_own_roles = SpreeCmCommissioner::PreviewRole
15
+ .where(previewable_type: model_class.polymorphic_name)
16
+ .select(:previewable_id)
17
+
18
+ # Base: all active preview products from the parent scope.
19
+ base = super(**args).rewhere(preview: true).where(status: :active)
20
+
21
+ # Path A: product has its own PreviewRole → user must hold that role directly.
22
+ # Taxon membership is irrelevant for these products.
23
+ via_product_role = base
24
+ .where(id: products_with_own_roles)
25
+ .where(id: spree_current_user.preview_roles
26
+ .where(previewable_type: model_class.polymorphic_name)
27
+ .select(:previewable_id)
28
+ )
29
+
30
+ # Path B: product has no own PreviewRole → inherit access from its taxons.
31
+ # Accessible if the user holds a preview role for any taxon the product belongs to.
32
+ via_taxon = base
33
+ .where.not(id: products_with_own_roles)
34
+ .joins(:taxons)
35
+ .where(spree_taxons: { id: spree_current_user.preview_roles
36
+ .where(previewable_type: taxon_type)
37
+ .select(:previewable_id)
38
+ }
39
+ )
40
+
41
+ # via_taxon uses joins(:taxons) which is incompatible with or — wrap as subquery.
42
+ via_product_role.or(base.where(id: via_taxon.select(:id)))
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,27 @@
1
+ module Spree
2
+ module Api
3
+ module V2
4
+ module Storefront
5
+ class PreviewSectionsController < HomepageSectionsController
6
+ before_action :require_spree_current_user
7
+
8
+ # override
9
+ def collection
10
+ allowed_ids = spree_current_user.preview_roles.where(previewable_type: Spree::Taxon.polymorphic_name).pluck(:previewable_id)
11
+
12
+ section_ids = SpreeCmCommissioner::HomepageSectionRelatable
13
+ .where(relatable_type: 'Spree::Taxon', relatable_id: allowed_ids.presence || [])
14
+ .select(:homepage_section_id)
15
+
16
+ @collection ||= model_class.filter_by_segment(params[:homepage_id] || :general)
17
+ .active
18
+ .where(tenant_id: nil)
19
+ .where(preview: true)
20
+ .where(id: section_ids)
21
+ .order(position: :asc)
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,18 @@
1
+ module Spree
2
+ module Api
3
+ module V2
4
+ module Storefront
5
+ class PreviewTaxonsController < ::Spree::Api::V2::Storefront::TaxonsController
6
+ before_action :require_spree_current_user
7
+
8
+ private
9
+
10
+ def scope(**args)
11
+ allowed_ids = spree_current_user.preview_roles.where(previewable_type: model_class.polymorphic_name).select(:previewable_id)
12
+ super(**args).rewhere(preview: true).where(id: allowed_ids)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,15 @@
1
+ module Spree
2
+ module Api
3
+ module V2
4
+ module Storefront
5
+ module ProductsControllerDecorator
6
+ def scope
7
+ super.where(preview: false)
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
14
+
15
+ Spree::Api::V2::Storefront::ProductsController.prepend(Spree::Api::V2::Storefront::ProductsControllerDecorator)
@@ -0,0 +1,15 @@
1
+ module Spree
2
+ module Api
3
+ module V2
4
+ module Storefront
5
+ module TaxonsControllerDecorator
6
+ def scope
7
+ super.where(preview: false)
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
14
+
15
+ Spree::Api::V2::Storefront::TaxonsController.prepend(Spree::Api::V2::Storefront::TaxonsControllerDecorator)
@@ -86,10 +86,9 @@ module Spree
86
86
  end
87
87
 
88
88
  def collection
89
- SpreeCmCommissioner::TicketTransfer
90
- .includes(:order, from_guest: [:event, { line_item: :product }])
91
- .where(from_user_id: spree_current_user.id)
92
- .order(id: :desc)
89
+ SpreeCmCommissioner::TicketTransfer.includes(:order,
90
+ from_guest: [:event, { line_item: :product }]
91
+ ).where(from_user_id: spree_current_user.id)
93
92
  end
94
93
 
95
94
  def resource_serializer
@@ -0,0 +1,25 @@
1
+ module Spree
2
+ module Api
3
+ module V2
4
+ module Tenant
5
+ class AdCampaignsController < BaseController
6
+ private
7
+
8
+ def scope
9
+ Spree::Taxon.ad_campaigns
10
+ .joins(:vendor)
11
+ .includes(:advertisements)
12
+ .where(spree_vendors: { tenant_id: @tenant.id })
13
+ .where('spree_taxons.from_date <= ? OR spree_taxons.from_date IS NULL', Time.current)
14
+ .where('spree_taxons.to_date >= ? OR spree_taxons.to_date IS NULL', Time.current)
15
+ .order(created_at: :asc)
16
+ end
17
+
18
+ def collection_serializer
19
+ Spree::V2::Tenant::AdCampaignSerializer
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -16,6 +16,10 @@ module Spree
16
16
  set_current_tenant(@tenant)
17
17
  end
18
18
 
19
+ def current_vendor
20
+ @current_vendor ||= @tenant.active_vendor
21
+ end
22
+
19
23
  def render_serialized_payload(status = 200)
20
24
  render json: yield, status: status, content_type: content_type
21
25
  end
@@ -0,0 +1,38 @@
1
+ module Spree
2
+ module Api
3
+ module V2
4
+ module Tenant
5
+ class EpisodesController < BaseController
6
+ private
7
+
8
+ def model_class
9
+ SpreeCmCommissioner::ShowEpisode
10
+ end
11
+
12
+ def resource
13
+ @resource ||= scope.find(params[:id])
14
+ end
15
+
16
+ def scope
17
+ current_season.episodes.where(product_type: :show_episode).includes(:voting_sessions, :vote_packages)
18
+ end
19
+
20
+ def current_season
21
+ @current_season ||= SpreeCmCommissioner::Show
22
+ .where(vendor_id: @tenant.vendors.select(:id))
23
+ .where.not(parent_id: nil)
24
+ .find_by!(slug: params[:show_id])
25
+ end
26
+
27
+ def resource_serializer
28
+ Spree::V2::Tenant::ShowEpisodeSerializer
29
+ end
30
+
31
+ def collection_serializer
32
+ Spree::V2::Tenant::ShowEpisodeSerializer
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -5,6 +5,7 @@ module Spree
5
5
  class HomepageSectionsController < BaseController
6
6
  def collection
7
7
  @collection ||= scope.filter_by_segment(params[:homepage_id] || :general)
8
+ .where(preview: false)
8
9
  .active
9
10
  .order(position: :asc)
10
11
  end
@@ -37,25 +37,13 @@ module Spree
37
37
  end
38
38
 
39
39
  def collection
40
- # WARNING: Do NOT remove .order(id: :desc).
41
- # Mobile relies on this ordering for paginated order_histories.
42
- # Removing it will cause mobile to paginate by oldest records first (last 10 oldest),
43
- # breaking the "most recent orders" display on the order history screen.
44
- # It related to user cancel or complete order flow, which relies on the most recent orders being returned first.
45
40
  if spree_current_user.present?
46
- spree_current_user.orders
47
- .not_archived
48
- .incomplete
49
- .order(id: :desc)
41
+ spree_current_user.orders.payment.not_archived
50
42
  else
51
43
  order_tokens = Array(params[:order_tokens])
52
44
  return Spree::Order.none if order_tokens.empty?
53
45
 
54
- Spree::Order.not_archived
55
- .incomplete
56
- .without_user
57
- .where(token: order_tokens)
58
- .order(id: :desc)
46
+ Spree::Order.payment.not_archived.without_user.where(token: order_tokens)
59
47
  end
60
48
  end
61
49
 
@@ -0,0 +1,47 @@
1
+ module Spree
2
+ module Api
3
+ module V2
4
+ module Tenant
5
+ class PreviewProductsController < ProductsController
6
+ before_action :require_spree_current_user
7
+
8
+ # override
9
+ def scope
10
+ taxon_type = model_class.reflect_on_association(:taxons).klass.polymorphic_name
11
+
12
+ # IDs of products that have been explicitly assigned their own PreviewRole.
13
+ products_with_own_roles = SpreeCmCommissioner::PreviewRole
14
+ .where(previewable_type: model_class.polymorphic_name)
15
+ .select(:previewable_id)
16
+
17
+ # Base: all active preview products from the parent scope.
18
+ base = super.where(status: :active, preview: true)
19
+
20
+ # Path A: product has its own PreviewRole → user must hold that role directly.
21
+ # Taxon membership is irrelevant for these products.
22
+ via_product_role = base
23
+ .where(id: products_with_own_roles)
24
+ .where(id: spree_current_user.preview_roles
25
+ .where(previewable_type: model_class.polymorphic_name)
26
+ .select(:previewable_id)
27
+ )
28
+
29
+ # Path B: product has no own PreviewRole → inherit access from its taxons.
30
+ # Accessible if the user holds a preview role for any taxon the product belongs to.
31
+ via_taxon = base
32
+ .where.not(id: products_with_own_roles)
33
+ .joins(:taxons)
34
+ .where(spree_taxons: { id: spree_current_user.preview_roles
35
+ .where(previewable_type: taxon_type)
36
+ .select(:previewable_id)
37
+ }
38
+ )
39
+
40
+ # via_taxon uses joins(:taxons) which is incompatible with or — wrap as subquery.
41
+ via_product_role.or(base.where(id: via_taxon.select(:id)))
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,26 @@
1
+ module Spree
2
+ module Api
3
+ module V2
4
+ module Tenant
5
+ class PreviewSectionsController < HomepageSectionsController
6
+ before_action :require_spree_current_user
7
+
8
+ # override
9
+ def collection
10
+ allowed_ids = spree_current_user.preview_roles.where(previewable_type: Spree::Taxon.polymorphic_name).pluck(:previewable_id)
11
+
12
+ section_ids = SpreeCmCommissioner::HomepageSectionRelatable
13
+ .where(relatable_type: 'Spree::Taxon', relatable_id: allowed_ids.presence || [])
14
+ .select(:homepage_section_id)
15
+
16
+ @collection ||= scope.filter_by_segment(params[:homepage_id] || :general)
17
+ .active
18
+ .where(preview: true)
19
+ .where(id: section_ids)
20
+ .order(position: :asc)
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end