disco_app 0.9.6

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 (332) hide show
  1. checksums.yaml +7 -0
  2. data/Rakefile +37 -0
  3. data/app/assets/images/disco_app/icon.svg +1 -0
  4. data/app/assets/images/disco_app/icons.svg +0 -0
  5. data/app/assets/javascripts/disco_app/components/custom/filterable_shop_list.js.jsx +61 -0
  6. data/app/assets/javascripts/disco_app/components/custom/inline-radio-options.es6.jsx +59 -0
  7. data/app/assets/javascripts/disco_app/components/custom/rules-editor.es6.jsx +417 -0
  8. data/app/assets/javascripts/disco_app/components/custom/shop_filter_query.js.jsx +13 -0
  9. data/app/assets/javascripts/disco_app/components/custom/shop_filter_tab.js.jsx +34 -0
  10. data/app/assets/javascripts/disco_app/components/custom/shop_filter_tabs.js.jsx +21 -0
  11. data/app/assets/javascripts/disco_app/components/custom/shop_list.js.jsx +142 -0
  12. data/app/assets/javascripts/disco_app/components/custom/shop_row.js.jsx +43 -0
  13. data/app/assets/javascripts/disco_app/components/custom/shopify_admin_link.js.jsx +29 -0
  14. data/app/assets/javascripts/disco_app/components/ui-kit/cards/card-footer.es6.jsx +11 -0
  15. data/app/assets/javascripts/disco_app/components/ui-kit/cards/card-header.es6.jsx +11 -0
  16. data/app/assets/javascripts/disco_app/components/ui-kit/cards/card-section.es6.jsx +30 -0
  17. data/app/assets/javascripts/disco_app/components/ui-kit/cards/card.es6.jsx +16 -0
  18. data/app/assets/javascripts/disco_app/components/ui-kit/cards/cart-section-title.es6.jsx +9 -0
  19. data/app/assets/javascripts/disco_app/components/ui-kit/forms/base_form.es6.jsx +72 -0
  20. data/app/assets/javascripts/disco_app/components/ui-kit/forms/base_input.es6.jsx +20 -0
  21. data/app/assets/javascripts/disco_app/components/ui-kit/forms/button.es6.jsx +13 -0
  22. data/app/assets/javascripts/disco_app/components/ui-kit/forms/input-radio.es6.jsx +30 -0
  23. data/app/assets/javascripts/disco_app/components/ui-kit/forms/input-select.es6.jsx +39 -0
  24. data/app/assets/javascripts/disco_app/components/ui-kit/forms/input-text.es6.jsx +59 -0
  25. data/app/assets/javascripts/disco_app/components/ui-kit/forms/input-textarea.es6.jsx +48 -0
  26. data/app/assets/javascripts/disco_app/components/ui-kit/icons/icon-chevron.es6.jsx +33 -0
  27. data/app/assets/javascripts/disco_app/components/ui-kit/icons/next-icon.es6.jsx +18 -0
  28. data/app/assets/javascripts/disco_app/components/ui-kit/input_select.es6.jsx +21 -0
  29. data/app/assets/javascripts/disco_app/components/ui-kit/tables/table.es6.jsx +22 -0
  30. data/app/assets/javascripts/disco_app/components/ui-kit/ui-layout/ui-annotated-section.es6.jsx +29 -0
  31. data/app/assets/javascripts/disco_app/components/ui-kit/ui-layout/ui-empty-state.es6.jsx +35 -0
  32. data/app/assets/javascripts/disco_app/components/ui-kit/ui-layout/ui-footer-help.es6.jsx +13 -0
  33. data/app/assets/javascripts/disco_app/components/ui-kit/ui-layout/ui-layout-item.es6.jsx +11 -0
  34. data/app/assets/javascripts/disco_app/components/ui-kit/ui-layout/ui-layout-section.es6.jsx +19 -0
  35. data/app/assets/javascripts/disco_app/components/ui-kit/ui-layout/ui-layout-sections.es6.jsx +11 -0
  36. data/app/assets/javascripts/disco_app/components/ui-kit/ui-layout/ui-layout.es6.jsx +11 -0
  37. data/app/assets/javascripts/disco_app/components/ui-kit/ui-layout/ui-page-actions.es6.jsx +48 -0
  38. data/app/assets/javascripts/disco_app/components.js +2 -0
  39. data/app/assets/javascripts/disco_app/disco_app.js +9 -0
  40. data/app/assets/javascripts/disco_app/frame.js +152 -0
  41. data/app/assets/javascripts/disco_app/shopify-turbolinks.js +7 -0
  42. data/app/assets/javascripts/disco_app/ui-kit.js +1 -0
  43. data/app/assets/stylesheets/disco_app/admin/_header.scss +75 -0
  44. data/app/assets/stylesheets/disco_app/admin/_layout.scss +32 -0
  45. data/app/assets/stylesheets/disco_app/admin/_nav.scss +184 -0
  46. data/app/assets/stylesheets/disco_app/admin.scss +11 -0
  47. data/app/assets/stylesheets/disco_app/disco_app.scss +19 -0
  48. data/app/assets/stylesheets/disco_app/frame/_buttons.scss +54 -0
  49. data/app/assets/stylesheets/disco_app/frame/_forms.scss +26 -0
  50. data/app/assets/stylesheets/disco_app/frame/_layout.scss +77 -0
  51. data/app/assets/stylesheets/disco_app/frame/_type.scss +25 -0
  52. data/app/assets/stylesheets/disco_app/frame.scss +10 -0
  53. data/app/assets/stylesheets/disco_app/mixins/_flexbox.scss +400 -0
  54. data/app/assets/stylesheets/disco_app/ui-kit/_ui-empty-state.scss +121 -0
  55. data/app/assets/stylesheets/disco_app/ui-kit/_ui-footer-help.scss +28 -0
  56. data/app/assets/stylesheets/disco_app/ui-kit/_ui-icons.scss +28 -0
  57. data/app/assets/stylesheets/disco_app/ui-kit/_ui-kit.scss +5113 -0
  58. data/app/assets/stylesheets/disco_app/ui-kit/_ui-layout.scss +15 -0
  59. data/app/assets/stylesheets/disco_app/ui-kit/_ui-page-actions.scss +21 -0
  60. data/app/assets/stylesheets/disco_app/ui-kit/_ui-tabs.scss +75 -0
  61. data/app/assets/stylesheets/disco_app/ui-kit/_ui-type.scss +13 -0
  62. data/app/controllers/disco_app/admin/app_settings_controller.rb +3 -0
  63. data/app/controllers/disco_app/admin/application_controller.rb +3 -0
  64. data/app/controllers/disco_app/admin/concerns/app_settings_controller.rb +24 -0
  65. data/app/controllers/disco_app/admin/concerns/authenticated_controller.rb +20 -0
  66. data/app/controllers/disco_app/admin/concerns/plans_controller.rb +54 -0
  67. data/app/controllers/disco_app/admin/concerns/shops_controller.rb +7 -0
  68. data/app/controllers/disco_app/admin/concerns/subscriptions_controller.rb +29 -0
  69. data/app/controllers/disco_app/admin/plans_controller.rb +3 -0
  70. data/app/controllers/disco_app/admin/resources/shops_controller.rb +3 -0
  71. data/app/controllers/disco_app/admin/shops_controller.rb +3 -0
  72. data/app/controllers/disco_app/admin/subscriptions_controller.rb +3 -0
  73. data/app/controllers/disco_app/charges_controller.rb +47 -0
  74. data/app/controllers/disco_app/concerns/app_proxy_controller.rb +40 -0
  75. data/app/controllers/disco_app/concerns/authenticated_controller.rb +56 -0
  76. data/app/controllers/disco_app/concerns/carrier_request_controller.rb +21 -0
  77. data/app/controllers/disco_app/frame_controller.rb +9 -0
  78. data/app/controllers/disco_app/install_controller.rb +27 -0
  79. data/app/controllers/disco_app/subscriptions_controller.rb +32 -0
  80. data/app/controllers/disco_app/webhooks_controller.rb +46 -0
  81. data/app/controllers/sessions_controller.rb +28 -0
  82. data/app/helpers/disco_app/application_helper.rb +50 -0
  83. data/app/jobs/disco_app/app_installed_job.rb +3 -0
  84. data/app/jobs/disco_app/app_uninstalled_job.rb +3 -0
  85. data/app/jobs/disco_app/concerns/app_installed_job.rb +39 -0
  86. data/app/jobs/disco_app/concerns/app_uninstalled_job.rb +20 -0
  87. data/app/jobs/disco_app/concerns/render_asset_group_job.rb +8 -0
  88. data/app/jobs/disco_app/concerns/shop_update_job.rb +13 -0
  89. data/app/jobs/disco_app/concerns/subscription_changed_job.rb +7 -0
  90. data/app/jobs/disco_app/concerns/synchronise_carrier_service_job.rb +52 -0
  91. data/app/jobs/disco_app/concerns/synchronise_webhooks_job.rb +61 -0
  92. data/app/jobs/disco_app/render_asset_group_job.rb +3 -0
  93. data/app/jobs/disco_app/shop_job.rb +27 -0
  94. data/app/jobs/disco_app/shop_update_job.rb +3 -0
  95. data/app/jobs/disco_app/subscription_changed_job.rb +3 -0
  96. data/app/jobs/disco_app/synchronise_carrier_service_job.rb +3 -0
  97. data/app/jobs/disco_app/synchronise_webhooks_job.rb +3 -0
  98. data/app/models/disco_app/app_settings.rb +3 -0
  99. data/app/models/disco_app/application_charge.rb +18 -0
  100. data/app/models/disco_app/concerns/app_settings.rb +7 -0
  101. data/app/models/disco_app/concerns/plan.rb +26 -0
  102. data/app/models/disco_app/concerns/plan_code.rb +15 -0
  103. data/app/models/disco_app/concerns/renders_assets.rb +166 -0
  104. data/app/models/disco_app/concerns/shop.rb +63 -0
  105. data/app/models/disco_app/concerns/subscription.rb +60 -0
  106. data/app/models/disco_app/concerns/synchronises.rb +39 -0
  107. data/app/models/disco_app/plan.rb +3 -0
  108. data/app/models/disco_app/plan_code.rb +3 -0
  109. data/app/models/disco_app/recurring_application_charge.rb +18 -0
  110. data/app/models/disco_app/session_storage.rb +18 -0
  111. data/app/models/disco_app/shop.rb +3 -0
  112. data/app/models/disco_app/subscription.rb +3 -0
  113. data/app/resources/disco_app/admin/resources/concerns/shop_resource.rb +100 -0
  114. data/app/resources/disco_app/admin/resources/shop_resource.rb +4 -0
  115. data/app/services/disco_app/carrier_request_service.rb +15 -0
  116. data/app/services/disco_app/charges_service.rb +81 -0
  117. data/app/services/disco_app/proxy_service.rb +17 -0
  118. data/app/services/disco_app/subscription_service.rb +46 -0
  119. data/app/services/disco_app/webhook_service.rb +30 -0
  120. data/app/views/disco_app/admin/app_settings/edit.html.erb +5 -0
  121. data/app/views/disco_app/admin/plans/_form.html.erb +72 -0
  122. data/app/views/disco_app/admin/plans/_plan_code_fields.html.erb +15 -0
  123. data/app/views/disco_app/admin/plans/edit.html.erb +7 -0
  124. data/app/views/disco_app/admin/plans/index.html.erb +43 -0
  125. data/app/views/disco_app/admin/plans/new.html.erb +7 -0
  126. data/app/views/disco_app/admin/shops/index.html.erb +13 -0
  127. data/app/views/disco_app/admin/subscriptions/edit.html.erb +33 -0
  128. data/app/views/disco_app/charges/activate.html.erb +1 -0
  129. data/app/views/disco_app/charges/create.html.erb +1 -0
  130. data/app/views/disco_app/charges/new.html.erb +12 -0
  131. data/app/views/disco_app/frame/frame.html.erb +36 -0
  132. data/app/views/disco_app/install/installing.html.erb +7 -0
  133. data/app/views/disco_app/install/uninstalling.html.erb +1 -0
  134. data/app/views/disco_app/proxy_errors/404.html.erb +1 -0
  135. data/app/views/disco_app/shared/_card.html.erb +14 -0
  136. data/app/views/disco_app/shared/_icons.html.erb +3 -0
  137. data/app/views/disco_app/shared/_section.html.erb +17 -0
  138. data/app/views/disco_app/subscriptions/new.html.erb +25 -0
  139. data/app/views/layouts/admin/_nav_items.erb +20 -0
  140. data/app/views/layouts/admin.html.erb +67 -0
  141. data/app/views/layouts/application.html.erb +18 -0
  142. data/app/views/layouts/embedded_app.html.erb +44 -0
  143. data/app/views/layouts/embedded_app_modal.html.erb +28 -0
  144. data/app/views/sessions/new.html.erb +26 -0
  145. data/config/routes.rb +48 -0
  146. data/db/migrate/20150525000000_create_shops_if_not_existent.rb +15 -0
  147. data/db/migrate/20150525162112_add_status_to_shops.rb +5 -0
  148. data/db/migrate/20150525171422_add_meta_to_shops.rb +11 -0
  149. data/db/migrate/20150629210346_add_charge_status_to_shop.rb +5 -0
  150. data/db/migrate/20150814214025_add_more_meta_to_shops.rb +15 -0
  151. data/db/migrate/20151017231302_create_disco_app_plans.rb +13 -0
  152. data/db/migrate/20151017232027_create_disco_app_subscriptions.rb +15 -0
  153. data/db/migrate/20151017234409_move_shop_to_disco_app_engine.rb +5 -0
  154. data/db/migrate/20160112233706_create_disco_app_sessions.rb +12 -0
  155. data/db/migrate/20160113194418_add_shop_id_to_disco_app_sessions.rb +6 -0
  156. data/db/migrate/20160223111044_create_disco_app_settings.rb +8 -0
  157. data/db/migrate/20160301223215_update_plans.rb +22 -0
  158. data/db/migrate/20160301224558_update_subscriptions.rb +13 -0
  159. data/db/migrate/20160302104816_create_disco_app_recurring_application_charges.rb +14 -0
  160. data/db/migrate/20160302105259_create_disco_app_application_charges.rb +14 -0
  161. data/db/migrate/20160302134728_drop_charge_status_from_shops.rb +5 -0
  162. data/db/migrate/20160302142941_add_shopify_attributes_to_charges.rb +8 -0
  163. data/db/migrate/20160331093148_create_disco_app_plan_codes.rb +14 -0
  164. data/db/migrate/20160401044420_add_status_to_plan_codes.rb +5 -0
  165. data/db/migrate/20160401045551_add_amount_and_plan_code_to_disco_app_subscriptions.rb +7 -0
  166. data/db/migrate/20160425205211_add_source_to_disco_app_subscriptions.rb +5 -0
  167. data/db/migrate/20160426033520_add_trial_period_days_to_disco_app_subscriptions.rb +5 -0
  168. data/db/migrate/20160513140727_add_name_to_disco_app_shops.rb +5 -0
  169. data/db/migrate/20160521135510_move_shop_to_synchronises.rb +61 -0
  170. data/lib/disco_app/configuration.rb +39 -0
  171. data/lib/disco_app/constants.rb +4 -0
  172. data/lib/disco_app/engine.rb +26 -0
  173. data/lib/disco_app/session.rb +14 -0
  174. data/lib/disco_app/support/file_fixtures.rb +23 -0
  175. data/lib/disco_app/test_help.rb +11 -0
  176. data/lib/disco_app/version.rb +3 -0
  177. data/lib/disco_app.rb +7 -0
  178. data/lib/generators/disco_app/USAGE +5 -0
  179. data/lib/generators/disco_app/disco_app_generator.rb +235 -0
  180. data/lib/generators/disco_app/templates/assets/javascripts/application.js +17 -0
  181. data/lib/generators/disco_app/templates/assets/javascripts/components.js +3 -0
  182. data/lib/generators/disco_app/templates/assets/stylesheets/application.scss +5 -0
  183. data/lib/generators/disco_app/templates/config/database.yml.tt +20 -0
  184. data/lib/generators/disco_app/templates/config/newrelic.yml +26 -0
  185. data/lib/generators/disco_app/templates/config/puma.rb +15 -0
  186. data/lib/generators/disco_app/templates/controllers/home_controller.rb +7 -0
  187. data/lib/generators/disco_app/templates/initializers/disco_app.rb +19 -0
  188. data/lib/generators/disco_app/templates/initializers/rollbar.rb +12 -0
  189. data/lib/generators/disco_app/templates/initializers/session_store.rb +2 -0
  190. data/lib/generators/disco_app/templates/initializers/shopify_app.rb +7 -0
  191. data/lib/generators/disco_app/templates/initializers/shopify_session_repository.rb +7 -0
  192. data/lib/generators/disco_app/templates/root/CHECKS +4 -0
  193. data/lib/generators/disco_app/templates/root/Procfile +2 -0
  194. data/lib/generators/disco_app/templates/views/home/index.html.erb +2 -0
  195. data/lib/tasks/carrier_service.rake +10 -0
  196. data/lib/tasks/database.rake +8 -0
  197. data/lib/tasks/sessions.rake +9 -0
  198. data/lib/tasks/shops.rake +10 -0
  199. data/lib/tasks/start.rake +3 -0
  200. data/lib/tasks/webhooks.rake +10 -0
  201. data/test/controllers/disco_app/admin/shops_controller_test.rb +54 -0
  202. data/test/controllers/disco_app/charges_controller_test.rb +99 -0
  203. data/test/controllers/disco_app/install_controller_test.rb +50 -0
  204. data/test/controllers/disco_app/subscriptions_controller_test.rb +68 -0
  205. data/test/controllers/disco_app/webhooks_controller_test.rb +58 -0
  206. data/test/controllers/home_controller_test.rb +92 -0
  207. data/test/controllers/proxy_controller_test.rb +42 -0
  208. data/test/disco_app_test.rb +7 -0
  209. data/test/dummy/Rakefile +6 -0
  210. data/test/dummy/app/assets/javascripts/application.js +17 -0
  211. data/test/dummy/app/assets/stylesheets/application.scss +5 -0
  212. data/test/dummy/app/controllers/application_controller.rb +6 -0
  213. data/test/dummy/app/controllers/disco_app/admin/shops_controller.rb +8 -0
  214. data/test/dummy/app/controllers/home_controller.rb +7 -0
  215. data/test/dummy/app/controllers/proxy_controller.rb +8 -0
  216. data/test/dummy/app/helpers/application_helper.rb +2 -0
  217. data/test/dummy/app/jobs/disco_app/app_installed_job.rb +16 -0
  218. data/test/dummy/app/jobs/disco_app/app_uninstalled_job.rb +11 -0
  219. data/test/dummy/app/jobs/products_create_job.rb +7 -0
  220. data/test/dummy/app/jobs/products_delete_job.rb +7 -0
  221. data/test/dummy/app/jobs/products_update_job.rb +7 -0
  222. data/test/dummy/app/models/disco_app/shop.rb +15 -0
  223. data/test/dummy/app/models/js_configuration.rb +8 -0
  224. data/test/dummy/app/models/product.rb +6 -0
  225. data/test/dummy/app/models/widget_configuration.rb +10 -0
  226. data/test/dummy/app/views/assets/script_tag.js.erb +1 -0
  227. data/test/dummy/app/views/assets/test.js.erb +1 -0
  228. data/test/dummy/app/views/assets/widget.js.erb +2 -0
  229. data/test/dummy/app/views/assets/widget.scss.erb +3 -0
  230. data/test/dummy/app/views/home/index.html.erb +2 -0
  231. data/test/dummy/app/views/snippets/widget.liquid.erb +1 -0
  232. data/test/dummy/bin/bundle +3 -0
  233. data/test/dummy/bin/rails +4 -0
  234. data/test/dummy/bin/rake +4 -0
  235. data/test/dummy/bin/setup +29 -0
  236. data/test/dummy/config/application.rb +38 -0
  237. data/test/dummy/config/boot.rb +5 -0
  238. data/test/dummy/config/database.codeship.yml +23 -0
  239. data/test/dummy/config/database.gitlab-ci.yml +24 -0
  240. data/test/dummy/config/database.yml +20 -0
  241. data/test/dummy/config/environment.rb +5 -0
  242. data/test/dummy/config/environments/development.rb +41 -0
  243. data/test/dummy/config/environments/production.rb +85 -0
  244. data/test/dummy/config/environments/test.rb +42 -0
  245. data/test/dummy/config/initializers/assets.rb +11 -0
  246. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  247. data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
  248. data/test/dummy/config/initializers/disco_app.rb +19 -0
  249. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  250. data/test/dummy/config/initializers/inflections.rb +16 -0
  251. data/test/dummy/config/initializers/mime_types.rb +4 -0
  252. data/test/dummy/config/initializers/omniauth.rb +9 -0
  253. data/test/dummy/config/initializers/session_store.rb +2 -0
  254. data/test/dummy/config/initializers/shopify_app.rb +7 -0
  255. data/test/dummy/config/initializers/shopify_session_repository.rb +7 -0
  256. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  257. data/test/dummy/config/locales/en.yml +23 -0
  258. data/test/dummy/config/routes.rb +10 -0
  259. data/test/dummy/config/secrets.yml +22 -0
  260. data/test/dummy/config.ru +4 -0
  261. data/test/dummy/db/migrate/20160307182229_create_products.rb +11 -0
  262. data/test/dummy/db/migrate/20160530160739_create_asset_models.rb +19 -0
  263. data/test/dummy/db/schema.rb +141 -0
  264. data/test/dummy/public/404.html +67 -0
  265. data/test/dummy/public/422.html +67 -0
  266. data/test/dummy/public/500.html +66 -0
  267. data/test/dummy/public/favicon.ico +0 -0
  268. data/test/fixtures/api/widget_store/assets/create_script_tag_js_request.json +6 -0
  269. data/test/fixtures/api/widget_store/assets/create_script_tag_js_response.json +12 -0
  270. data/test/fixtures/api/widget_store/assets/create_script_tag_request.json +6 -0
  271. data/test/fixtures/api/widget_store/assets/create_script_tag_response.json +10 -0
  272. data/test/fixtures/api/widget_store/assets/create_test_js_request.json +6 -0
  273. data/test/fixtures/api/widget_store/assets/create_test_js_response.json +12 -0
  274. data/test/fixtures/api/widget_store/assets/create_widget_js_request.json +6 -0
  275. data/test/fixtures/api/widget_store/assets/create_widget_js_response.json +12 -0
  276. data/test/fixtures/api/widget_store/assets/create_widget_liquid_request.json +6 -0
  277. data/test/fixtures/api/widget_store/assets/create_widget_liquid_response.json +12 -0
  278. data/test/fixtures/api/widget_store/assets/create_widget_scss_request.json +6 -0
  279. data/test/fixtures/api/widget_store/assets/create_widget_scss_response.json +12 -0
  280. data/test/fixtures/api/widget_store/assets/get_script_tags_empty_request.json +1 -0
  281. data/test/fixtures/api/widget_store/assets/get_script_tags_empty_response.json +1 -0
  282. data/test/fixtures/api/widget_store/assets/get_script_tags_preexisting_request.json +1 -0
  283. data/test/fixtures/api/widget_store/assets/get_script_tags_preexisting_response.json +12 -0
  284. data/test/fixtures/api/widget_store/assets/update_script_tag_request.json +10 -0
  285. data/test/fixtures/api/widget_store/assets/update_script_tag_response.json +10 -0
  286. data/test/fixtures/api/widget_store/charges/activate_application_charge_request.json +16 -0
  287. data/test/fixtures/api/widget_store/charges/activate_application_charge_response.json +1 -0
  288. data/test/fixtures/api/widget_store/charges/activate_recurring_application_charge_request.json +20 -0
  289. data/test/fixtures/api/widget_store/charges/activate_recurring_application_charge_response.json +1 -0
  290. data/test/fixtures/api/widget_store/charges/create_application_charge_request.json +9 -0
  291. data/test/fixtures/api/widget_store/charges/create_application_charge_response.json +16 -0
  292. data/test/fixtures/api/widget_store/charges/create_recurring_application_charge_request.json +9 -0
  293. data/test/fixtures/api/widget_store/charges/create_recurring_application_charge_response.json +20 -0
  294. data/test/fixtures/api/widget_store/charges/create_second_recurring_application_charge_request.json +9 -0
  295. data/test/fixtures/api/widget_store/charges/create_second_recurring_application_charge_response.json +20 -0
  296. data/test/fixtures/api/widget_store/charges/get_accepted_application_charge_response.json +16 -0
  297. data/test/fixtures/api/widget_store/charges/get_accepted_recurring_application_charge_response.json +20 -0
  298. data/test/fixtures/api/widget_store/charges/get_declined_application_charge_response.json +16 -0
  299. data/test/fixtures/api/widget_store/charges/get_declined_recurring_application_charge_response.json +20 -0
  300. data/test/fixtures/api/widget_store/charges/get_pending_application_charge_response.json +16 -0
  301. data/test/fixtures/api/widget_store/charges/get_pending_recurring_application_charge_response.json +20 -0
  302. data/test/fixtures/api/widget_store/shop.json +46 -0
  303. data/test/fixtures/api/widget_store/webhooks.json +1 -0
  304. data/test/fixtures/assets/test.js +1 -0
  305. data/test/fixtures/assets/test.min.js +1 -0
  306. data/test/fixtures/disco_app/application_charges.yml +11 -0
  307. data/test/fixtures/disco_app/plan_codes.yml +13 -0
  308. data/test/fixtures/disco_app/plans.yml +37 -0
  309. data/test/fixtures/disco_app/recurring_application_charges.yml +11 -0
  310. data/test/fixtures/disco_app/shops.yml +10 -0
  311. data/test/fixtures/disco_app/subscriptions.yml +22 -0
  312. data/test/fixtures/js_configurations.yml +3 -0
  313. data/test/fixtures/products.yml +4 -0
  314. data/test/fixtures/webhooks/app_uninstalled.json +46 -0
  315. data/test/fixtures/webhooks/product_created.json +167 -0
  316. data/test/fixtures/webhooks/product_deleted.json +3 -0
  317. data/test/fixtures/webhooks/product_updated.json +167 -0
  318. data/test/fixtures/widget_configurations.yml +4 -0
  319. data/test/integration/synchronises_test.rb +55 -0
  320. data/test/jobs/disco_app/app_installed_job_test.rb +55 -0
  321. data/test/jobs/disco_app/app_uninstalled_job_test.rb +30 -0
  322. data/test/models/disco_app/plan_test.rb +5 -0
  323. data/test/models/disco_app/renders_assets_test.rb +109 -0
  324. data/test/models/disco_app/session_test.rb +31 -0
  325. data/test/models/disco_app/shop_test.rb +27 -0
  326. data/test/models/disco_app/subscription_test.rb +19 -0
  327. data/test/services/disco_app/charges_service_test.rb +112 -0
  328. data/test/services/disco_app/subscription_service_test.rb +60 -0
  329. data/test/support/test_file_fixtures.rb +29 -0
  330. data/test/support/test_shopify_api.rb +16 -0
  331. data/test/test_helper.rb +55 -0
  332. metadata +827 -0
@@ -0,0 +1,15 @@
1
+ //
2
+ // ui-layout.scss
3
+ // Styles for UI layout not provided by the Channel
4
+ // SDK UI Kit.
5
+ // --------------------------------------------------
6
+
7
+ body {
8
+ margin: 0;
9
+ padding: 0;
10
+ }
11
+
12
+ ul, ol, dl {
13
+ margin: 0;
14
+ padding: 0;
15
+ }
@@ -0,0 +1,21 @@
1
+ //
2
+ // ui-page-actions.scss
3
+ // Styles for UI page actions not provided by the
4
+ // Channel SDK UI Kit.
5
+ // --------------------------------------------------
6
+
7
+ .ui-page-actions {
8
+ @include flexbox();
9
+ margin: 0 auto 20px auto;
10
+ max-width: 1036px;
11
+ }
12
+
13
+ .ui-page-actions__primary {
14
+ @include flex(1 1 auto);
15
+ padding-right: 20px;
16
+ }
17
+
18
+ .ui-page-actions__secondary {
19
+ @include flex(1 1 auto);
20
+ padding: 0 20px;
21
+ }
@@ -0,0 +1,75 @@
1
+ //
2
+ // ui-tabs.scss
3
+ // Styles for tabs not provided by the Channel SDK UI
4
+ // Kit.
5
+ // --------------------------------------------------
6
+
7
+ .next-tab__list {
8
+ padding: 0;
9
+ margin: 0;
10
+ list-style: none;
11
+ background-color: #f5f6f7;
12
+ overflow: visible;
13
+ border-radius: 3px 3px 0 0;
14
+
15
+ flex-wrap: nowrap;
16
+ display: flex;
17
+ align-items: stretch;
18
+
19
+ > li {
20
+ position: relative;
21
+ flex-grow: 0;
22
+ flex-shrink: 0;
23
+ display: flex;
24
+ align-items: stretch;
25
+
26
+ &:first-child .next-tab {
27
+ border-top-left-radius: 3px;
28
+ }
29
+ }
30
+ }
31
+
32
+ .next-tab {
33
+ color: rgba(0,0,0,0.56);
34
+ padding: 15px 20px;
35
+ text-decoration: none;
36
+ border-right: 1px solid #ebeef0;
37
+ border-bottom: 1px solid #ebeef0;
38
+ text-align: center;
39
+ line-height: 1;
40
+ cursor: pointer;
41
+ position: relative;
42
+ -webkit-box-flex: 1;
43
+ -webkit-flex-grow: 1;
44
+ -ms-flex-positive: 1;
45
+ flex-grow: 1;
46
+ display: -webkit-box;
47
+ display: -webkit-flex;
48
+ display: -ms-flexbox;
49
+ display: flex;
50
+ -webkit-box-align: center;
51
+ -webkit-align-items: center;
52
+ -ms-flex-align: center;
53
+ align-items: center;
54
+ -webkit-box-pack: center;
55
+ -webkit-justify-content: center;
56
+ -ms-flex-pack: center;
57
+ justify-content: center;
58
+
59
+ &:focus,
60
+ &:hover {
61
+ outline: none;
62
+ background-color: #fafbfc;
63
+ color: #0078bd;
64
+ text-decoration: none;
65
+ }
66
+
67
+ &.next-tab--is-active {
68
+ font-weight: normal;
69
+ color: rgba(0,0,0,0.9);
70
+ background-color: #ffffff;
71
+ border-bottom-color: #ffffff;
72
+ cursor: default;
73
+ text-decoration: none;
74
+ }
75
+ }
@@ -0,0 +1,13 @@
1
+ //
2
+ // ui-type.scss
3
+ // Styles for type not provided by the Channel SDK UI
4
+ // Kit.
5
+ // --------------------------------------------------
6
+
7
+ hr {
8
+ color: #e6e6e6;
9
+ background-color: #e6e6e6;
10
+ height: 1px;
11
+ padding: 0;
12
+ border: 0;
13
+ }
@@ -0,0 +1,3 @@
1
+ class DiscoApp::Admin::AppSettingsController < DiscoApp::Admin::ApplicationController
2
+ include DiscoApp::Admin::Concerns::AppSettingsController
3
+ end
@@ -0,0 +1,3 @@
1
+ class DiscoApp::Admin::ApplicationController < ActionController::Base
2
+ include DiscoApp::Admin::Concerns::AuthenticatedController
3
+ end
@@ -0,0 +1,24 @@
1
+ module DiscoApp::Admin::Concerns::AppSettingsController
2
+ extend ActiveSupport::Concern
3
+
4
+ def edit
5
+ @app_settings = DiscoApp::AppSettings.instance
6
+ end
7
+
8
+ def update
9
+ @app_settings = DiscoApp::AppSettings.instance
10
+ if @app_settings.update_attributes(app_settings_params)
11
+ flash[:success] = 'Settings updated.'
12
+ redirect_to edit_admin_app_settings_path
13
+ else
14
+ render 'edit'
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ def app_settings_params
21
+ params.require(:app_settings)
22
+ end
23
+
24
+ end
@@ -0,0 +1,20 @@
1
+ module DiscoApp::Admin::Concerns::AuthenticatedController
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+
6
+ protect_from_forgery with: :exception
7
+ before_action :authenticate_administrator
8
+ layout 'admin'
9
+
10
+ end
11
+
12
+ private
13
+
14
+ def authenticate_administrator
15
+ authenticate_or_request_with_http_basic do |username, password|
16
+ (not username.blank?) && (not password.blank?) && username == ENV['ADMIN_APP_USERNAME'] && password == ENV['ADMIN_APP_PASSWORD']
17
+ end
18
+ end
19
+
20
+ end
@@ -0,0 +1,54 @@
1
+ module DiscoApp::Admin::Concerns::PlansController
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ before_action :find_plan, only: [:edit, :update, :destroy]
6
+ end
7
+
8
+ def index
9
+ @plans = DiscoApp::Plan.all
10
+ end
11
+
12
+ def new
13
+ @plan = DiscoApp::Plan.new
14
+ end
15
+
16
+ def create
17
+ @plan = DiscoApp::Plan.new(plan_params)
18
+ if @plan.save
19
+ redirect_to admin_plans_path
20
+ else
21
+ render 'new'
22
+ end
23
+ end
24
+
25
+ def edit
26
+ end
27
+
28
+ def update
29
+ if @plan.update_attributes(plan_params)
30
+ redirect_to edit_admin_plan_path(@plan)
31
+ else
32
+ render 'edit'
33
+ end
34
+ end
35
+
36
+ def destroy
37
+ @plan.destroy
38
+ redirect_to admin_plans_path
39
+ end
40
+
41
+ private
42
+
43
+ def find_plan
44
+ @plan = DiscoApp::Plan.find(params[:id])
45
+ end
46
+
47
+ def plan_params
48
+ params.require(:plan).permit(
49
+ :name, :status, :plan_type, :trial_period_days, :amount,
50
+ :plan_codes_attributes => [:id, :_destroy, :code, :trial_period_days, :amount]
51
+ )
52
+ end
53
+
54
+ end
@@ -0,0 +1,7 @@
1
+ module DiscoApp::Admin::Concerns::ShopsController
2
+ extend ActiveSupport::Concern
3
+
4
+ def index
5
+ end
6
+
7
+ end
@@ -0,0 +1,29 @@
1
+ module DiscoApp::Admin::Concerns::SubscriptionsController
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ before_action :find_subscription
6
+ end
7
+
8
+ def edit
9
+ end
10
+
11
+ def update
12
+ if @subscription.update_attributes(subscription_params)
13
+ redirect_to edit_admin_shop_subscription_path(@subscription.shop, @subscription)
14
+ else
15
+ render 'edit'
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def find_subscription
22
+ @subscription = DiscoApp::Subscription.find_by_id(params[:id])
23
+ end
24
+
25
+ def subscription_params
26
+ params.require(:subscription).permit(:amount, :trial_period_days)
27
+ end
28
+
29
+ end
@@ -0,0 +1,3 @@
1
+ class DiscoApp::Admin::PlansController < DiscoApp::Admin::ApplicationController
2
+ include DiscoApp::Admin::Concerns::PlansController
3
+ end
@@ -0,0 +1,3 @@
1
+ class DiscoApp::Admin::Resources::ShopsController < JSONAPI::ResourceController
2
+ include DiscoApp::Admin::Concerns::AuthenticatedController
3
+ end
@@ -0,0 +1,3 @@
1
+ class DiscoApp::Admin::ShopsController < DiscoApp::Admin::ApplicationController
2
+ include DiscoApp::Admin::Concerns::ShopsController
3
+ end
@@ -0,0 +1,3 @@
1
+ class DiscoApp::Admin::SubscriptionsController < DiscoApp::Admin::ApplicationController
2
+ include DiscoApp::Admin::Concerns::SubscriptionsController
3
+ end
@@ -0,0 +1,47 @@
1
+ class DiscoApp::ChargesController < ApplicationController
2
+ include DiscoApp::Concerns::AuthenticatedController
3
+
4
+ skip_before_action :check_active_charge
5
+ before_action :find_subscription
6
+
7
+ # Display a "pre-charge" page, giving the opportunity to explain why a charge
8
+ # needs to be made.
9
+ def new
10
+ end
11
+
12
+ # Attempt to create a new charge for the logged in shop and selected
13
+ # subscription. If successful, redirect to the (external) charge confirmation
14
+ # URL. If it fails, redirect back to the new charge page.
15
+ def create
16
+ if(charge = DiscoApp::ChargesService.create(@shop, @subscription)).nil?
17
+ redirect_to action: :new
18
+ else
19
+ redirect_to charge.confirmation_url
20
+ end
21
+ end
22
+
23
+ # Attempt to activate a charge after a user has accepted or declined it.
24
+ # Redirect to the main application's root URL immediately afterwards - if the
25
+ # charge wasn't accepted, the flow will start again.
26
+ def activate
27
+ # First attempt to find a matching charge.
28
+ if(charge = @subscription.charges.find_by(id: params[:id], shopify_id: params[:charge_id])).nil?
29
+ redirect_to action: :new and return
30
+ end
31
+ if DiscoApp::ChargesService.activate(@shop, charge)
32
+ redirect_to main_app.root_url
33
+ else
34
+ redirect_to action: :new
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ def find_subscription
41
+ @subscription = @shop.subscriptions.find_by_id!(params[:subscription_id])
42
+ unless @subscription.requires_active_charge? and not @subscription.active_charge?
43
+ redirect_to main_app.root_url
44
+ end
45
+ end
46
+
47
+ end
@@ -0,0 +1,40 @@
1
+ module DiscoApp::Concerns::AppProxyController
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ before_action :verify_proxy_signature
6
+ before_action :shopify_shop
7
+ after_action :add_liquid_header
8
+
9
+ rescue_from ActiveRecord::RecordNotFound do |exception|
10
+ render_error 404
11
+ end
12
+ end
13
+
14
+ private
15
+
16
+ def verify_proxy_signature
17
+ unless proxy_signature_is_valid?
18
+ head :unauthorized
19
+ end
20
+ end
21
+
22
+ def proxy_signature_is_valid?
23
+ return true if Rails.env.development? and DiscoApp.configuration.skip_proxy_verification?
24
+ DiscoApp::ProxyService.proxy_signature_is_valid?(request.query_string, ShopifyApp.configuration.secret)
25
+ end
26
+
27
+ def shopify_shop
28
+ @shop = DiscoApp::Shop.find_by_shopify_domain!(params[:shop])
29
+ end
30
+
31
+ def add_liquid_header
32
+ response.headers['Content-Type'] = 'application/liquid'
33
+ end
34
+
35
+ def render_error(status)
36
+ add_liquid_header
37
+ render "disco_app/proxy_errors/#{status}", status: status
38
+ end
39
+
40
+ end
@@ -0,0 +1,56 @@
1
+ module DiscoApp::Concerns::AuthenticatedController
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ before_action :login_again_if_different_shop
6
+ before_action :shopify_shop
7
+ before_action :check_installed
8
+ before_action :check_current_subscription
9
+ before_action :check_active_charge
10
+ around_filter :shopify_session
11
+ layout 'embedded_app'
12
+ end
13
+
14
+ private
15
+
16
+ def shopify_shop
17
+ if shop_session
18
+ @shop = DiscoApp::Shop.find_by!(shopify_domain: @shop_session.url)
19
+ else
20
+ redirect_to_login
21
+ end
22
+ end
23
+
24
+ def check_installed
25
+ if @shop.awaiting_install? or @shop.installing?
26
+ redirect_if_not_current_path disco_app.installing_path
27
+ return
28
+ end
29
+ if @shop.awaiting_uninstall? or @shop.uninstalling?
30
+ redirect_if_not_current_path disco_app.uninstalling_path
31
+ return
32
+ end
33
+ unless @shop.installed?
34
+ redirect_if_not_current_path disco_app.install_path
35
+ end
36
+ end
37
+
38
+ def check_current_subscription
39
+ unless @shop.current_subscription?
40
+ redirect_if_not_current_path disco_app.new_subscription_path
41
+ end
42
+ end
43
+
44
+ def check_active_charge
45
+ if @shop.current_subscription? and @shop.current_subscription.requires_active_charge? and not @shop.current_subscription.active_charge?
46
+ redirect_if_not_current_path disco_app.new_subscription_charge_path(@shop.current_subscription)
47
+ end
48
+ end
49
+
50
+ def redirect_if_not_current_path(target)
51
+ if request.path != target
52
+ redirect_to target
53
+ end
54
+ end
55
+
56
+ end
@@ -0,0 +1,21 @@
1
+ module DiscoApp::Concerns::CarrierRequestController
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ before_action :verify_carrier_request
6
+ end
7
+
8
+ private
9
+
10
+ def verify_carrier_request
11
+ unless carrier_request_signature_is_valid?
12
+ head :unauthorized
13
+ end
14
+ end
15
+
16
+ def carrier_request_signature_is_valid?
17
+ return true if Rails.env.development? and DiscoApp.configuration.skip_carrier_request_verification?
18
+ DiscoApp::CarrierRequestService.is_valid_hmac?(request.body.read.to_s, ShopifyApp.configuration.secret, request.headers['HTTP_X_SHOPIFY_HMAC_SHA256'])
19
+ end
20
+
21
+ end
@@ -0,0 +1,9 @@
1
+ class DiscoApp::FrameController < ActionController::Base
2
+
3
+ layout nil
4
+
5
+ def frame
6
+
7
+ end
8
+
9
+ end
@@ -0,0 +1,27 @@
1
+ class DiscoApp::InstallController < ApplicationController
2
+ include DiscoApp::Concerns::AuthenticatedController
3
+
4
+ skip_before_action :check_current_subscription
5
+ skip_before_action :check_active_charge
6
+
7
+ # Start the installation process for the current shop, then redirect to the installing screen.
8
+ def install
9
+ DiscoApp::AppInstalledJob.perform_later(@shop, cookies[DiscoApp::CODE_COOKIE_KEY], cookies[DiscoApp::SOURCE_COOKIE_KEY])
10
+ redirect_to action: :installing
11
+ end
12
+
13
+ # Display an "installing" page.
14
+ def installing
15
+ if @shop.installed?
16
+ redirect_to main_app.root_path
17
+ end
18
+ end
19
+
20
+ # Display an "uninstalling" page. Should be almost never used.
21
+ def uninstalling
22
+ if @shop.uninstalled?
23
+ redirect_to main_app.root_path
24
+ end
25
+ end
26
+
27
+ end
@@ -0,0 +1,32 @@
1
+ class DiscoApp::SubscriptionsController < ApplicationController
2
+ include DiscoApp::Concerns::AuthenticatedController
3
+
4
+ skip_before_action :check_current_subscription
5
+
6
+ def new
7
+ @subscription = DiscoApp::Subscription.new
8
+ end
9
+
10
+ def create
11
+ # Get the selected plan. If it's not available or couldn't be found,
12
+ # redirect back to the plan selection page.
13
+ if(plan = DiscoApp::Plan.available.find_by_id(subscription_params[:plan])).nil?
14
+ redirect_to action: :new and return
15
+ end
16
+
17
+ # Subscribe the current shop to the selected plan. Pass along any cookied
18
+ # plan code and source code.
19
+ if(subscription = DiscoApp::SubscriptionService.subscribe(@shop, plan, cookies[DiscoApp::CODE_COOKIE_KEY], cookies[DiscoApp::SOURCE_COOKIE_KEY])).nil?
20
+ redirect_to action: :new
21
+ else
22
+ redirect_to main_app.root_path
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def subscription_params
29
+ params.require(:subscription).permit(:plan, :plan_code)
30
+ end
31
+
32
+ end
@@ -0,0 +1,46 @@
1
+ module DiscoApp
2
+ class WebhooksController < ActionController::Base
3
+
4
+ before_action :verify_webhook
5
+
6
+ def process_webhook
7
+ # Get the topic and domain for this webhook.
8
+ topic = request.headers['HTTP_X_SHOPIFY_TOPIC']
9
+ domain = request.headers['HTTP_X_SHOPIFY_SHOP_DOMAIN']
10
+
11
+ # Ensure a domain was provided in the headers.
12
+ unless domain
13
+ head :bad_request
14
+ end
15
+
16
+ # Try to find a matching background job task for the given topic using class name.
17
+ job_class = DiscoApp::WebhookService.find_job_class(topic)
18
+
19
+ # Return bad request if we couldn't match a job class.
20
+ unless job_class.present?
21
+ head :bad_request
22
+ end
23
+
24
+ # Decode the body data and enqueue the appropriate job.
25
+ data = ActiveSupport::JSON::decode(request.body.read).with_indifferent_access
26
+ job_class.perform_later(domain, data)
27
+
28
+ render nothing: true
29
+ end
30
+
31
+ private
32
+
33
+ def verify_webhook
34
+ unless webhook_is_valid?
35
+ head :unauthorized
36
+ end
37
+ request.body.rewind
38
+ end
39
+
40
+ def webhook_is_valid?
41
+ return true if Rails.env.development? and DiscoApp.configuration.skip_webhook_verification?
42
+ DiscoApp::WebhookService.is_valid_hmac?(request.body.read.to_s, ShopifyApp.configuration.secret, request.headers['HTTP_X_SHOPIFY_HMAC_SHA256'])
43
+ end
44
+
45
+ end
46
+ end
@@ -0,0 +1,28 @@
1
+ class SessionsController < ApplicationController
2
+ include ShopifyApp::SessionsController
3
+
4
+ def referral
5
+ cookies[DiscoApp::SOURCE_COOKIE_KEY] = params[:source] if params[:source].present?
6
+ cookies[DiscoApp::CODE_COOKIE_KEY] = params[:code] if params[:code].present?
7
+ redirect_to root_path
8
+ end
9
+
10
+ protected
11
+
12
+ # Override the authenticate method to allow skipping OAuth in development
13
+ # mode. Skipping OAuth still requires a shop with Shopify domain specified
14
+ # by the `shop` parameter to be present in the local database.
15
+ def authenticate
16
+ if Rails.env.development? and DiscoApp.configuration.skip_oauth?
17
+ shop = DiscoApp::Shop.find_by_shopify_domain!(sanitized_shop_name)
18
+
19
+ sess = ShopifyAPI::Session.new(shop.shopify_domain, shop.shopify_token)
20
+ session[:shopify] = ShopifyApp::SessionRepository.store(sess)
21
+ session[:shopify_domain] = sanitized_shop_name
22
+
23
+ redirect_to disco_app.frame_path and return
24
+ end
25
+ super
26
+ end
27
+
28
+ end
@@ -0,0 +1,50 @@
1
+ module DiscoApp::ApplicationHelper
2
+
3
+ # Generates a link pointing to an object (such as an order or customer) inside
4
+ # the given shop's Shopify admin. This helper makes it easy to create links
5
+ # to objects within the admin that support both right-clicking and opening in
6
+ # a new tab as well as capturing a left click and redirecting to the relevant
7
+ # object using `ShopifyApp.redirect()`.
8
+ def link_to_shopify_admin(shop, name, admin_path, options = {})
9
+ options[:onclick] = "ShopifyApp.redirect('#{admin_path}'); return false;"
10
+ options[:'data-no-turbolink'] = true
11
+ link_to(name, "https://#{shop.shopify_domain}/admin/#{admin_path}", options)
12
+ end
13
+
14
+ # Generate a link that will open its href in an embedded Shopify modal.
15
+ def link_to_modal(name, path, options = {})
16
+ modal_options = {
17
+ src: path,
18
+ title: options.delete(:modal_title),
19
+ width: options.delete(:modal_width),
20
+ height: options.delete(:modal_height),
21
+ buttons: options.delete(:modal_buttons),
22
+ }
23
+ options[:onclick] = "ShopifyApp.Modal.open(#{modal_options.to_json}); return false;"
24
+ options[:onclick].gsub!(/"function(.*?)"/, 'function\1')
25
+ link_to(name, path, options)
26
+ end
27
+
28
+ # Render a React component with inner HTML content.
29
+ # Thanks to https://github.com/reactjs/react-rails/pull/166#issuecomment-86178980
30
+ def react_component_with_content(name, args = {}, options = {}, &block)
31
+ args[:__html] = capture(&block) if block.present?
32
+ react_component(name, args, options)
33
+ end
34
+
35
+ # Provide link to dynamically add a new nested fields association
36
+ def link_to_add_fields(name, f, association)
37
+ new_object = f.object.send(association).klass.new
38
+ id = new_object.object_id
39
+ fields = f.fields_for(association, new_object, child_index: id) do |builder|
40
+ render(association.to_s.singularize + "_fields", f: builder)
41
+ end
42
+ link_to(name, '#', class: "add_fields", data: {id: id, fields: fields.gsub("\n", "")})
43
+ end
44
+
45
+ # Helper method that provides detailed error information from an active record as JSON
46
+ def errors_to_react(record)
47
+ {type: record.model_name.human.downcase, errors: record.errors.keys, messages: record.errors.full_messages}.as_json
48
+ end
49
+
50
+ end
@@ -0,0 +1,3 @@
1
+ class DiscoApp::AppInstalledJob < DiscoApp::ShopJob
2
+ include DiscoApp::Concerns::AppInstalledJob
3
+ end
@@ -0,0 +1,3 @@
1
+ class DiscoApp::AppUninstalledJob < DiscoApp::ShopJob
2
+ include DiscoApp::Concerns::AppUninstalledJob
3
+ end