shopify_app 13.2.0 → 20.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (167) hide show
  1. checksums.yaml +4 -4
  2. data/.github/CODEOWNERS +1 -0
  3. data/.github/ISSUE_TEMPLATE/bug-report.md +63 -0
  4. data/.github/ISSUE_TEMPLATE/config.yml +1 -0
  5. data/.github/ISSUE_TEMPLATE/feature-request.md +33 -0
  6. data/.github/PULL_REQUEST_TEMPLATE.md +22 -0
  7. data/.github/workflows/build.yml +40 -0
  8. data/.github/workflows/cla.yml +22 -0
  9. data/.github/workflows/close-waiting-for-response-issues.yml +20 -0
  10. data/.github/workflows/release.yml +24 -0
  11. data/.github/workflows/remove-labels-on-activity.yml +16 -0
  12. data/.github/workflows/rubocop.yml +22 -0
  13. data/.github/workflows/stale.yml +31 -0
  14. data/.gitignore +1 -2
  15. data/.nvmrc +1 -1
  16. data/.rubocop.yml +2 -0
  17. data/.ruby-version +1 -1
  18. data/CHANGELOG.md +221 -0
  19. data/CONTRIBUTING.md +81 -0
  20. data/Gemfile +5 -2
  21. data/Gemfile.lock +248 -0
  22. data/README.md +74 -563
  23. data/Rakefile +4 -3
  24. data/SECURITY.md +59 -0
  25. data/app/assets/images/storage_access.svg +1 -2
  26. data/app/assets/javascripts/shopify_app/app_bridge_3.1.1.js +10 -0
  27. data/app/assets/javascripts/shopify_app/app_bridge_redirect.js +22 -0
  28. data/app/assets/javascripts/shopify_app/app_bridge_utils_3.1.1.js +1 -0
  29. data/app/assets/javascripts/shopify_app/post_redirect.js +9 -0
  30. data/app/assets/javascripts/shopify_app/redirect.js +10 -14
  31. data/app/assets/javascripts/shopify_app/storage_access.js +5 -10
  32. data/app/assets/javascripts/shopify_app/top_level_interaction.js +1 -1
  33. data/app/controllers/concerns/shopify_app/authenticated.rb +4 -0
  34. data/app/controllers/concerns/shopify_app/ensure_authenticated_links.rb +39 -0
  35. data/app/controllers/concerns/shopify_app/require_known_shop.rb +48 -0
  36. data/app/controllers/concerns/shopify_app/shop_access_scopes_verification.rb +40 -0
  37. data/app/controllers/shopify_app/authenticated_controller.rb +1 -0
  38. data/app/controllers/shopify_app/callback_controller.rb +56 -77
  39. data/app/controllers/shopify_app/extension_verification_controller.rb +2 -7
  40. data/app/controllers/shopify_app/sessions_controller.rb +33 -117
  41. data/app/controllers/shopify_app/webhooks_controller.rb +5 -26
  42. data/app/views/shopify_app/partials/_button_styles.html.erb +41 -36
  43. data/app/views/shopify_app/partials/_card_styles.html.erb +3 -3
  44. data/app/views/shopify_app/partials/_empty_state_styles.html.erb +28 -59
  45. data/app/views/shopify_app/partials/_form_styles.html.erb +56 -0
  46. data/app/views/shopify_app/partials/_layout_styles.html.erb +16 -1
  47. data/app/views/shopify_app/partials/_typography_styles.html.erb +6 -6
  48. data/app/views/shopify_app/sessions/enable_cookies.html.erb +2 -7
  49. data/app/views/shopify_app/sessions/new.html.erb +38 -110
  50. data/app/views/shopify_app/sessions/request_storage_access.html.erb +12 -12
  51. data/app/views/shopify_app/sessions/top_level_interaction.html.erb +21 -22
  52. data/app/views/shopify_app/shared/post_redirect_to_auth_shopify.html.erb +13 -0
  53. data/app/views/shopify_app/shared/redirect.html.erb +2 -2
  54. data/config/locales/de.yml +11 -11
  55. data/config/locales/ja.yml +4 -4
  56. data/config/locales/nl.yml +2 -2
  57. data/config/locales/th.yml +4 -4
  58. data/config/locales/vi.yml +22 -0
  59. data/config/locales/zh-CN.yml +2 -2
  60. data/config/routes.rb +20 -12
  61. data/docs/Quickstart.md +19 -83
  62. data/docs/Releasing.md +18 -15
  63. data/docs/Troubleshooting.md +140 -5
  64. data/docs/Upgrading.md +247 -0
  65. data/docs/shopify_app/authentication.md +128 -0
  66. data/docs/shopify_app/content-security-policy.md +10 -0
  67. data/docs/shopify_app/engine.md +82 -0
  68. data/docs/shopify_app/generators.md +127 -0
  69. data/docs/shopify_app/handling-access-scopes-changes.md +24 -0
  70. data/docs/shopify_app/script-tags.md +28 -0
  71. data/docs/shopify_app/session-repository.md +88 -0
  72. data/docs/shopify_app/testing.md +38 -0
  73. data/docs/shopify_app/webhooks.md +72 -0
  74. data/karma.conf.js +1 -1
  75. data/lib/generators/shopify_app/add_after_authenticate_job/add_after_authenticate_job_generator.rb +10 -9
  76. data/lib/generators/shopify_app/add_after_authenticate_job/templates/after_authenticate_job.rb +1 -0
  77. data/lib/generators/shopify_app/add_marketing_activity_extension/add_marketing_activity_extension_generator.rb +4 -3
  78. data/lib/generators/shopify_app/add_webhook/add_webhook_generator.rb +15 -14
  79. data/lib/generators/shopify_app/add_webhook/templates/webhook_job.rb.tt +9 -1
  80. data/lib/generators/shopify_app/app_proxy_controller/app_proxy_controller_generator.rb +7 -6
  81. data/lib/generators/shopify_app/app_proxy_controller/templates/app_proxy_controller.rb +2 -1
  82. data/lib/generators/shopify_app/app_proxy_controller/templates/app_proxy_route.rb +1 -1
  83. data/lib/generators/shopify_app/authenticated_controller/authenticated_controller_generator.rb +4 -4
  84. data/lib/generators/shopify_app/controllers/controllers_generator.rb +5 -4
  85. data/lib/generators/shopify_app/home_controller/home_controller_generator.rb +27 -4
  86. data/lib/generators/shopify_app/home_controller/templates/home_controller.rb +12 -2
  87. data/lib/generators/shopify_app/home_controller/templates/index.html.erb +74 -16
  88. data/lib/generators/shopify_app/home_controller/templates/unauthenticated_home_controller.rb +16 -0
  89. data/lib/generators/shopify_app/install/install_generator.rb +52 -40
  90. data/lib/generators/shopify_app/install/templates/embedded_app.html.erb +5 -2
  91. data/lib/generators/shopify_app/install/templates/flash_messages.js +0 -2
  92. data/lib/generators/shopify_app/install/templates/session_store.rb +2 -1
  93. data/lib/generators/shopify_app/install/templates/shopify_app.js +1 -1
  94. data/lib/generators/shopify_app/install/templates/shopify_app.rb.tt +43 -5
  95. data/lib/generators/shopify_app/install/templates/shopify_app_importmap.js +13 -0
  96. data/lib/generators/shopify_app/products_controller/products_controller_generator.rb +19 -0
  97. data/lib/generators/shopify_app/products_controller/templates/products_controller.rb +8 -0
  98. data/lib/generators/shopify_app/rotate_shopify_token_job/rotate_shopify_token_job_generator.rb +4 -4
  99. data/lib/generators/shopify_app/rotate_shopify_token_job/templates/rotate_shopify_token.rake +1 -0
  100. data/lib/generators/shopify_app/rotate_shopify_token_job/templates/rotate_shopify_token_job.rb +1 -1
  101. data/lib/generators/shopify_app/routes/routes_generator.rb +6 -5
  102. data/lib/generators/shopify_app/routes/templates/routes.rb +5 -5
  103. data/lib/generators/shopify_app/shop_model/shop_model_generator.rb +35 -7
  104. data/lib/generators/shopify_app/shop_model/templates/db/migrate/add_shop_access_scopes_column.erb +5 -0
  105. data/lib/generators/shopify_app/shop_model/templates/shop.rb +2 -1
  106. data/lib/generators/shopify_app/shopify_app_generator.rb +4 -3
  107. data/lib/generators/shopify_app/user_model/templates/db/migrate/add_user_access_scopes_column.erb +5 -0
  108. data/lib/generators/shopify_app/user_model/templates/user.rb +2 -1
  109. data/lib/generators/shopify_app/user_model/user_model_generator.rb +35 -7
  110. data/lib/generators/shopify_app/views/views_generator.rb +5 -4
  111. data/lib/shopify_app/access_scopes/noop_strategy.rb +13 -0
  112. data/lib/shopify_app/access_scopes/shop_strategy.rb +24 -0
  113. data/lib/shopify_app/access_scopes/user_strategy.rb +41 -0
  114. data/lib/shopify_app/configuration.rb +58 -11
  115. data/lib/shopify_app/controller_concerns/app_proxy_verification.rb +4 -4
  116. data/lib/shopify_app/controller_concerns/csrf_protection.rb +16 -0
  117. data/lib/shopify_app/controller_concerns/embedded_app.rb +6 -3
  118. data/lib/shopify_app/controller_concerns/ensure_billing.rb +243 -0
  119. data/lib/shopify_app/controller_concerns/frame_ancestors.rb +16 -0
  120. data/lib/shopify_app/controller_concerns/itp.rb +3 -3
  121. data/lib/shopify_app/controller_concerns/localization.rb +1 -0
  122. data/lib/shopify_app/controller_concerns/login_protection.rb +105 -90
  123. data/lib/shopify_app/controller_concerns/payload_verification.rb +25 -0
  124. data/lib/shopify_app/controller_concerns/redirect_for_embedded.rb +36 -0
  125. data/lib/shopify_app/controller_concerns/sanitized_params.rb +36 -0
  126. data/lib/shopify_app/controller_concerns/webhook_verification.rb +3 -18
  127. data/lib/shopify_app/engine.rb +26 -11
  128. data/lib/shopify_app/errors.rb +34 -0
  129. data/lib/shopify_app/jobs/scripttags_manager_job.rb +2 -2
  130. data/lib/shopify_app/jobs/webhooks_manager_job.rb +4 -5
  131. data/lib/shopify_app/managers/scripttags_manager.rb +12 -6
  132. data/lib/shopify_app/managers/webhooks_manager.rb +62 -42
  133. data/lib/shopify_app/middleware/jwt_middleware.rb +6 -3
  134. data/lib/shopify_app/session/in_memory_session_store.rb +2 -3
  135. data/lib/shopify_app/session/in_memory_shop_session_store.rb +10 -7
  136. data/lib/shopify_app/session/in_memory_user_session_store.rb +10 -7
  137. data/lib/shopify_app/session/jwt.rb +19 -16
  138. data/lib/shopify_app/session/null_user_session_store.rb +2 -1
  139. data/lib/shopify_app/session/session_repository.rb +40 -2
  140. data/lib/shopify_app/session/session_storage.rb +4 -6
  141. data/lib/shopify_app/session/shop_session_storage.rb +6 -6
  142. data/lib/shopify_app/session/shop_session_storage_with_scopes.rb +57 -0
  143. data/lib/shopify_app/session/user_session_storage.rb +20 -7
  144. data/lib/shopify_app/session/user_session_storage_with_scopes.rb +71 -0
  145. data/lib/shopify_app/test_helpers/all.rb +2 -1
  146. data/lib/shopify_app/test_helpers/webhook_verification_helper.rb +4 -3
  147. data/lib/shopify_app/utils.rb +14 -7
  148. data/lib/shopify_app/version.rb +2 -1
  149. data/lib/shopify_app.rb +52 -29
  150. data/package.json +7 -8
  151. data/service.yml +1 -5
  152. data/shopify_app.gemspec +22 -20
  153. data/translation.yml +1 -1
  154. data/yarn.lock +2173 -2206
  155. metadata +110 -56
  156. data/.github/ISSUE_TEMPLATE.md +0 -14
  157. data/.github/probots.yml +0 -2
  158. data/.travis.yml +0 -28
  159. data/config/locales/hi.yml +0 -23
  160. data/config/locales/ms.yml +0 -22
  161. data/docs/install-on-dev-shop.png +0 -0
  162. data/docs/test-your-app.png +0 -0
  163. data/lib/generators/shopify_app/install/templates/omniauth.rb +0 -3
  164. data/lib/generators/shopify_app/install/templates/shopify_provider.rb +0 -20
  165. data/lib/generators/shopify_app/install/templates/user_agent.rb +0 -6
  166. data/lib/shopify_app/middleware/same_site_cookie_middleware.rb +0 -34
  167. data/package-lock.json +0 -7245
@@ -0,0 +1,88 @@
1
+ # Session repository
2
+
3
+ #### Table of contents
4
+
5
+ [`ShopifyApp::SessionRepository`](#shopifyappsessionrepository)
6
+ * [Shop-based token storage](#shop-based-token-storage)
7
+ * [User-based token storage](#user-based-token-storage)
8
+
9
+ [Access scopes](#access-scopes)
10
+ * [`ShopifyApp::ShopSessionStorageWithScopes`](#shopifyappshopsessionstoragewithscopes)
11
+ * [``ShopifyApp::UserSessionStorageWithScopes``](#shopifyappusersessionstoragewithscopes)
12
+
13
+ [Migrating from shop-based to user-based token strategy](#migrating-from-shop-based-to-user-based-token-strategy)
14
+
15
+ ## ShopifyApp::SessionRepository
16
+
17
+ `ShopifyApp::SessionRepository` allows you as a developer to define how your sessions are stored and retrieved for shops. The `SessionRepository` is configured in the `config/initializers/shopify_app.rb` file and can be set to any object that implements `self.store(auth_session, *args)` which stores the session and returns a unique identifier and `self.retrieve(id)` which returns a `ShopifyAPI::Session` for the passed id. These methods are already implemented as part of the `ShopifyApp::SessionStorage` concern but can be overridden for custom implementation.
18
+
19
+ ### Shop-based token storage
20
+
21
+ Storing tokens on the store model means that any user login associated with the store will have equal access levels to whatever the original user granted the app.
22
+ ```sh
23
+ rails generate shopify_app:shop_model
24
+ ```
25
+ This will generate a shop model which will be the storage for the tokens necessary for authentication.
26
+
27
+ ### User-based token storage
28
+
29
+ A more granular control over the level of access per user on an app might be necessary, to which the shop-based token strategy is not sufficient. Shopify supports a user-based token storage strategy where a unique token to each user can be managed. Shop tokens must still be maintained if you are running background jobs so that you can make use of them when necessary.
30
+ ```sh
31
+ rails generate shopify_app:shop_model
32
+ rails generate shopify_app:user_model
33
+ ```
34
+ This will generate a shop model and user model, which will be the storage for the tokens necessary for authentication.
35
+
36
+ The current Shopify user will be stored in the rails session at `session[:shopify_user]`
37
+
38
+ Read more about Online vs. Offline access [here](https://help.shopify.com/api/getting-started/authentication/oauth).
39
+
40
+ ## Access scopes
41
+
42
+ If you want to customize how access scopes are stored for shops and users, you can implement the `access_scopes` getters and setters in the models that include `ShopifyApp::ShopSessionStorageWithScopes` and `ShopifyApp::UserSessionStorageWithScopes` as shown:
43
+
44
+ ### `ShopifyApp::ShopSessionStorageWithScopes`
45
+ ```ruby
46
+ class Shop < ActiveRecord::Base
47
+ include ShopifyApp::ShopSessionStorageWithScopes
48
+
49
+ def access_scopes=(scopes)
50
+ # Store access scopes
51
+ end
52
+ def access_scopes
53
+ # Find access scopes
54
+ end
55
+ end
56
+ ```
57
+
58
+ ### `ShopifyApp::UserSessionStorageWithScopes`
59
+ ```ruby
60
+ class User < ActiveRecord::Base
61
+ include ShopifyApp::UserSessionStorageWithScopes
62
+
63
+ def access_scopes=(scopes)
64
+ # Store access scopes
65
+ end
66
+ def access_scopes
67
+ # Find access scopes
68
+ end
69
+ end
70
+ ```
71
+ ## Migrating from shop-based to user-based token strategy
72
+
73
+ 1. Run the `user_model` generator as mentioned above.
74
+ 2. Ensure that both your `Shop` model and `User` model includes the necessary concerns `ShopifyApp::ShopSessionStorage` and `ShopifyApp::UserSessionStorage`.
75
+ 3. Make changes to 2 initializer files as shown below:
76
+ ```ruby
77
+ # In the `omniauth.rb` initializer:
78
+ provider :shopify,
79
+ ...
80
+ setup: lambda { |env|
81
+ configuration = ShopifyApp::OmniAuthConfiguration.new(env['omniauth.strategy'], Rack::Request.new(env))
82
+ configuration.build_options
83
+ }
84
+
85
+ # In the `shopify_app.rb` initializer:
86
+ config.shop_session_repository = {YOUR_SHOP_MODEL_CLASS}
87
+ config.user_session_repository = {YOUR_USER_MODEL_CLASS}
88
+ ```
@@ -0,0 +1,38 @@
1
+ # Testing
2
+
3
+ #### Table of contents
4
+
5
+ [Using test helpers inside your application](#using-test-helpers-inside-your-application)
6
+
7
+ [Testing an embedded app outside the Shopify admin](#testing-an-embedded-app-outside-the-shopify-admin)
8
+
9
+ ## Using test helpers inside your application
10
+
11
+ A test helper that will allow you to test `ShopifyApp::WebhookVerification` in the controller from your app, to use this test, you need to `require` it directly inside your app `test/controllers/webhook_verification_test.rb`.
12
+
13
+ ```ruby
14
+ require 'test_helper'
15
+ require 'action_controller'
16
+ require 'action_controller/base'
17
+ require 'shopify_app/test_helpers/webhook_verification_helper'
18
+ ```
19
+
20
+ Or you can require in your `test/test_helper.rb`.
21
+
22
+ ```ruby
23
+ ENV['RAILS_ENV'] ||= 'test'
24
+ require_relative '../config/environment'
25
+ require 'rails/test_help'
26
+ require 'byebug'
27
+ require 'shopify_app/test_helpers/all'
28
+ ```
29
+
30
+ With `lib/shopify_app/test_helpers/all'` more tests can be added and will only need to be required in once in your library.
31
+
32
+ ## Testing an embedded app outside the Shopify admin
33
+
34
+ By default, loading your embedded app will redirect to the Shopify admin, with the app view loaded in an `iframe`. If you need to load your app outside of the Shopify admin (e.g., for performance testing), you can change `forceRedirect: true` to `false` in `ShopifyApp.init` block in the `embedded_app` view. To keep the redirect on in production but off in your `development` and `test` environments, you can use:
35
+
36
+ ```javascript
37
+ forceRedirect: <%= Rails.env.development? || Rails.env.test? ? 'false' : 'true' %>
38
+ ```
@@ -0,0 +1,72 @@
1
+ # Webhooks
2
+
3
+ #### Table of contents
4
+
5
+ [Manage webhooks using `ShopifyApp::WebhooksManager`](#manage-webhooks-using-shopifyappwebhooksmanager)
6
+
7
+ ## Manage webhooks using `ShopifyApp::WebhooksManager`
8
+
9
+ See [`ShopifyApp::WebhooksManager`](/lib/shopify_app/managers/webhooks_manager.rb)
10
+ ShopifyApp can manage your app's webhooks for you if you set which webhooks you require in the initializer:
11
+
12
+ ```ruby
13
+ ShopifyApp.configure do |config|
14
+ config.webhooks = [
15
+ {topic: 'carts/update', path: 'webhooks/carts_update'}
16
+ ]
17
+ end
18
+ ```
19
+
20
+ When the [OAuth callback](/docs/shopify_app/authentication.md#oauth-callback) is completed successfully, ShopifyApp will queue a background job which will ensure all the specified webhooks exist for that shop. Because this runs on every OAuth callback, it means your app will always have the webhooks it needs even if the user uninstalls and re-installs the app.
21
+
22
+ ShopifyApp also provides a [WebhooksController](/app/controllers/shopify_app/webhooks_controller.rb) that receives webhooks and queues a job based on the received topic. For example, if you register the webhook from above, then all you need to do is create a job called `CartsUpdateJob`. The job will be queued with 2 params: `shop_domain` and `webhook` (which is the webhook body).
23
+
24
+ If you would like to namespace your jobs, you may set `webhook_jobs_namespace` in the config. For example, if your app handles webhooks from other ecommerce applications as well, and you want Shopify cart update webhooks to be processed by a job living in `jobs/shopify/webhooks/carts_update_job.rb` rather than `jobs/carts_update_job.rb`):
25
+
26
+ ```ruby
27
+ ShopifyApp.configure do |config|
28
+ config.webhook_jobs_namespace = 'shopify/webhooks'
29
+ end
30
+ ```
31
+
32
+ If you are only interested in particular fields, you can optionally filter the data sent by Shopify by specifying the `fields` parameter in `config/webhooks`. Note that you will still receive a webhook request from Shopify every time the resource is updated, but only the specified fields will be sent.
33
+
34
+ ```ruby
35
+ ShopifyApp.configure do |config|
36
+ config.webhooks = [
37
+ {topic: 'products/update', path: 'webhooks/products_update', fields: ['title', 'vendor']}
38
+ ]
39
+ end
40
+ ```
41
+
42
+ If you'd rather implement your own controller then you'll want to use the [`ShopifyApp::WebhookVerification`](/lib/shopify_app/controller_concerns/webhook_verification.rb) module to verify your webhooks, example:
43
+
44
+ ```ruby
45
+ class CustomWebhooksController < ApplicationController
46
+ include ShopifyApp::WebhookVerification
47
+
48
+ def carts_update
49
+ params.permit!
50
+ SomeJob.perform_later(shop_domain: shop_domain, webhook: webhook_params.to_h)
51
+ head :no_content
52
+ end
53
+
54
+ private
55
+
56
+ def webhook_params
57
+ params.except(:controller, :action, :type)
58
+ end
59
+ end
60
+ ```
61
+
62
+ The module skips the `verify_authenticity_token` before_action and adds an action to verify that the webhook came from Shopify. You can now add a post route to your application, pointing to the controller and action to accept the webhook data from Shopify.
63
+
64
+ The WebhooksManager uses ActiveJob. If ActiveJob is not configured then by default Rails will run the jobs inline. However, it is highly recommended to configure a proper background processing queue like Sidekiq or Resque in production.
65
+
66
+ ShopifyApp can create webhooks for you using the `add_webhook` generator. This will add the new webhook to your config and create the required job class for you.
67
+
68
+ ```
69
+ rails g shopify_app:add_webhook --topic carts/update --path webhooks/carts_update
70
+ ```
71
+
72
+ Where `--topic` is the topic and `--path` is the path the webhook should be sent to.
data/karma.conf.js CHANGED
@@ -8,7 +8,7 @@ module.exports = function(config) {
8
8
  config.set({
9
9
  mode: 'development',
10
10
  basePath: '',
11
- frameworks: ['mocha-debug', 'mocha', 'chai-sinon'],
11
+ frameworks: ['mocha', 'chai-sinon'],
12
12
  files: [
13
13
  'app/assets/javascripts/**/*.js',
14
14
  'test/javascripts/**/*test.js',
@@ -1,10 +1,11 @@
1
1
  # frozen_string_literal: true
2
- require 'rails/generators/base'
2
+
3
+ require "rails/generators/base"
3
4
 
4
5
  module ShopifyApp
5
6
  module Generators
6
7
  class AddAfterAuthenticateJobGenerator < Rails::Generators::Base
7
- source_root File.expand_path('../templates', __FILE__)
8
+ source_root File.expand_path("../templates", __FILE__)
8
9
 
9
10
  hook_for :test_framework, as: :job, in: :rails do |instance, generator|
10
11
  instance.invoke(generator, [instance.send(:job_file_name)])
@@ -15,32 +16,32 @@ module ShopifyApp
15
16
 
16
17
  after_authenticate_job_config =
17
18
  " config.after_authenticate_job = "\
18
- "{ job: \"Shopify::AfterAuthenticateJob\", inline: false }\n"
19
+ "{ job: \"Shopify::AfterAuthenticateJob\", inline: false }\n"
19
20
 
20
21
  inject_into_file(
21
- 'config/initializers/shopify_app.rb',
22
+ "config/initializers/shopify_app.rb",
22
23
  after_authenticate_job_config,
23
- before: 'end'
24
+ before: "end"
24
25
  )
25
26
 
26
27
  unless initializer.include?(after_authenticate_job_config)
27
28
  shell.say("Error adding after_authenticate_job to config. Add this line manually: "\
28
- "#{after_authenticate_job_config}", :red)
29
+ "#{after_authenticate_job_config}", :red)
29
30
  end
30
31
  end
31
32
 
32
33
  def add_after_authenticate_job
33
- template('after_authenticate_job.rb', "app/jobs/#{job_file_name}_job.rb")
34
+ template("after_authenticate_job.rb", "app/jobs/#{job_file_name}_job.rb")
34
35
  end
35
36
 
36
37
  private
37
38
 
38
39
  def load_initializer
39
- File.read(File.join(destination_root, 'config/initializers/shopify_app.rb'))
40
+ File.read(File.join(destination_root, "config/initializers/shopify_app.rb"))
40
41
  end
41
42
 
42
43
  def job_file_name
43
- 'shopify/after_authenticate'
44
+ "shopify/after_authenticate"
44
45
  end
45
46
  end
46
47
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Shopify
3
4
  class AfterAuthenticateJob < ActiveJob::Base
4
5
  def perform(shop_domain:)
@@ -1,10 +1,11 @@
1
1
  # frozen_string_literal: true
2
- require 'rails/generators/base'
2
+
3
+ require "rails/generators/base"
3
4
 
4
5
  module ShopifyApp
5
6
  module Generators
6
7
  class AddMarketingActivityExtensionGenerator < Rails::Generators::Base
7
- source_root File.expand_path('../templates', __FILE__)
8
+ source_root File.expand_path("../templates", __FILE__)
8
9
 
9
10
  def generate_app_extension
10
11
  template("marketing_activities_controller.rb", "app/controllers/marketing_activities_controller.rb")
@@ -15,7 +16,7 @@ module ShopifyApp
15
16
 
16
17
  def generate_routes
17
18
  inject_into_file(
18
- 'config/routes.rb',
19
+ "config/routes.rb",
19
20
  optimize_indentation(routes, 2),
20
21
  after: "root :to => 'home#index'\n"
21
22
  )
@@ -1,12 +1,13 @@
1
1
  # frozen_string_literal: true
2
- require 'rails/generators/base'
2
+
3
+ require "rails/generators/base"
3
4
 
4
5
  module ShopifyApp
5
6
  module Generators
6
7
  class AddWebhookGenerator < Rails::Generators::Base
7
- source_root File.expand_path('../templates', __FILE__)
8
+ source_root File.expand_path("../templates", __FILE__)
8
9
  class_option :topic, type: :string, aliases: "-t", required: true
9
- class_option :address, type: :string, aliases: "-a", required: true
10
+ class_option :path, type: :string, aliases: "-p", required: true
10
11
 
11
12
  hook_for :test_framework, as: :job, in: :rails do |instance, generator|
12
13
  instance.invoke(generator, [instance.send(:job_file_name)])
@@ -17,15 +18,15 @@ module ShopifyApp
17
18
  return if initializer.include?("config.webhooks")
18
19
 
19
20
  inject_into_file(
20
- 'config/initializers/shopify_app.rb',
21
+ "config/initializers/shopify_app.rb",
21
22
  " config.webhooks = [\n ]\n",
22
- before: 'end'
23
+ after: /ShopifyApp\.configure.*\n/
23
24
  )
24
25
  end
25
26
 
26
27
  def inject_webhook_to_shopify_app_initializer
27
28
  inject_into_file(
28
- 'config/initializers/shopify_app.rb',
29
+ "config/initializers/shopify_app.rb",
29
30
  webhook_config,
30
31
  after: "config.webhooks = ["
31
32
  )
@@ -38,31 +39,31 @@ module ShopifyApp
38
39
  end
39
40
 
40
41
  def add_webhook_job
41
- @job_file_name = job_file_name + '_job'
42
+ @job_file_name = job_file_name + "_job"
42
43
  @job_class_name = @job_file_name.classify
43
- template('webhook_job.rb', "app/jobs/#{@job_file_name}.rb")
44
+ template("webhook_job.rb", "app/jobs/#{@job_file_name}.rb")
44
45
  end
45
46
 
46
47
  private
47
48
 
48
49
  def job_file_name
49
- address.split('/').last
50
+ path.split("/").last
50
51
  end
51
52
 
52
53
  def load_initializer
53
- File.read(File.join(destination_root, 'config/initializers/shopify_app.rb'))
54
+ File.read(File.join(destination_root, "config/initializers/shopify_app.rb"))
54
55
  end
55
56
 
56
57
  def webhook_config
57
- "\n {topic: '#{topic}', address: '#{address}', format: 'json'},"
58
+ "\n { topic: \"#{topic}\", path: \"#{path}\" },"
58
59
  end
59
60
 
60
61
  def topic
61
- options['topic']
62
+ options["topic"]
62
63
  end
63
64
 
64
- def address
65
- options['address']
65
+ def path
66
+ options["path"]
66
67
  end
67
68
  end
68
69
  end
@@ -1,5 +1,13 @@
1
1
  class <%= @job_class_name %> < ActiveJob::Base
2
- def perform(shop_domain:, webhook:)
2
+ extend ShopifyAPI::Webhooks::Handler
3
+
4
+ class << self
5
+ def handle(topic:, shop:, body:)
6
+ perform_later(topic: topic, shop_domain: shop, webhook: body)
7
+ end
8
+ end
9
+
10
+ def perform(topic:, shop_domain:, webhook:)
3
11
  shop = Shop.find_by(shopify_domain: shop_domain)
4
12
 
5
13
  if shop.nil?
@@ -1,23 +1,24 @@
1
1
  # frozen_string_literal: true
2
- require 'rails/generators/base'
2
+
3
+ require "rails/generators/base"
3
4
 
4
5
  module ShopifyApp
5
6
  module Generators
6
7
  class AppProxyControllerGenerator < Rails::Generators::Base
7
- source_root File.expand_path('../templates', __FILE__)
8
+ source_root File.expand_path("../templates", __FILE__)
8
9
 
9
10
  def create_app_proxy_controller
10
- template('app_proxy_controller.rb', 'app/controllers/app_proxy_controller.rb')
11
+ template("app_proxy_controller.rb", "app/controllers/app_proxy_controller.rb")
11
12
  end
12
13
 
13
14
  def create_app_proxy_index_view
14
- copy_file('index.html.erb', 'app/views/app_proxy/index.html.erb')
15
+ copy_file("index.html.erb", "app/views/app_proxy/index.html.erb")
15
16
  end
16
17
 
17
18
  def add_app_proxy_route
18
19
  inject_into_file(
19
- 'config/routes.rb',
20
- File.read(File.expand_path(find_in_source_paths('app_proxy_route.rb'))),
20
+ "config/routes.rb",
21
+ File.read(File.expand_path(find_in_source_paths("app_proxy_route.rb"))),
21
22
  after: "mount ShopifyApp::Engine, at: '/'\n"
22
23
  )
23
24
  end
@@ -1,8 +1,9 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  class AppProxyController < ApplicationController
3
4
  include ShopifyApp::AppProxyVerification
4
5
 
5
6
  def index
6
- render(layout: false, content_type: 'application/liquid')
7
+ render(layout: false, content_type: "application/liquid")
7
8
  end
8
9
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  namespace :app_proxy do
4
- root action: 'index'
4
+ root action: "index"
5
5
  # simple routes without a specified controller will go to AppProxyController
6
6
 
7
7
  # more complex routes will go to controllers in the AppProxy namespace
@@ -1,14 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'rails/generators/base'
3
+ require "rails/generators/base"
4
4
 
5
5
  module ShopifyApp
6
6
  module Generators
7
7
  class AuthenticatedControllerGenerator < Rails::Generators::Base
8
- source_root File.expand_path('../templates', __FILE__)
8
+ source_root File.expand_path("../templates", __FILE__)
9
9
 
10
- def create_home_controller
11
- template('authenticated_controller.rb', 'app/controllers/authenticated_controller.rb')
10
+ def create_authenticated_controller
11
+ template("authenticated_controller.rb", "app/controllers/authenticated_controller.rb")
12
12
  end
13
13
  end
14
14
  end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
- require 'rails/generators/base'
2
+
3
+ require "rails/generators/base"
3
4
 
4
5
  module ShopifyApp
5
6
  module Generators
@@ -8,21 +9,21 @@ module ShopifyApp
8
9
 
9
10
  def create_controllers
10
11
  controllers.each do |controller|
11
- copy_file controller
12
+ copy_file(controller)
12
13
  end
13
14
  end
14
15
 
15
16
  private
16
17
 
17
18
  def controllers
18
- files_within_root('.', 'app/controllers/shopify_app/*.*')
19
+ files_within_root(".", "app/controllers/shopify_app/*.*")
19
20
  end
20
21
 
21
22
  def files_within_root(prefix, glob)
22
23
  root = "#{self.class.source_root}/#{prefix}"
23
24
 
24
25
  Dir["#{root}/#{glob}"].sort.map do |full_path|
25
- full_path.sub(root, '.').gsub('/./', '/')
26
+ full_path.sub(root, ".").gsub("/./", "/")
26
27
  end
27
28
  end
28
29
  end
@@ -1,26 +1,49 @@
1
1
  # frozen_string_literal: true
2
- require 'rails/generators/base'
2
+
3
+ require "rails/generators/base"
3
4
 
4
5
  module ShopifyApp
5
6
  module Generators
6
7
  class HomeControllerGenerator < Rails::Generators::Base
7
- source_root File.expand_path('../templates', __FILE__)
8
+ source_root File.expand_path("../templates", __FILE__)
9
+
10
+ class_option :embedded, type: :string, default: "true"
8
11
 
9
12
  def create_home_controller
10
- template('home_controller.rb', 'app/controllers/home_controller.rb')
13
+ template(home_controller_template, "app/controllers/home_controller.rb")
14
+ end
15
+
16
+ def create_products_controller
17
+ generate("shopify_app:products_controller") if embedded? || embedded_app?
11
18
  end
12
19
 
13
20
  def create_home_index_view
14
- copy_file('index.html.erb', 'app/views/home/index.html.erb')
21
+ template("index.html.erb", "app/views/home/index.html.erb")
15
22
  end
16
23
 
17
24
  def add_home_index_route
18
25
  route("root :to => 'home#index'")
19
26
  end
20
27
 
28
+ private
29
+
30
+ def embedded?
31
+ options["embedded"] == "true"
32
+ end
33
+
21
34
  def embedded_app?
22
35
  ShopifyApp.configuration.embedded_app?
23
36
  end
37
+
38
+ def home_controller_template
39
+ return "unauthenticated_home_controller.rb" unless authenticated_home_controller_required?
40
+
41
+ "home_controller.rb"
42
+ end
43
+
44
+ def authenticated_home_controller_required?
45
+ !embedded? || !embedded_app?
46
+ end
24
47
  end
25
48
  end
26
49
  end
@@ -1,8 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class HomeController < AuthenticatedController
4
+ include ShopifyApp::ShopAccessScopesVerification
5
+
6
+ before_action :set_host
7
+
4
8
  def index
5
- @products = ShopifyAPI::Product.find(:all, params: { limit: 10 })
6
- @webhooks = ShopifyAPI::Webhook.find(:all)
9
+ @products = ShopifyAPI::Product.all(limit: 10)
10
+ @webhooks = ShopifyAPI::Webhook.all
11
+ end
12
+
13
+ private
14
+
15
+ def set_host
16
+ @host = params[:host]
7
17
  end
8
18
  end