diff --git a/CHANGELOG.md b/CHANGELOG.md index f03fae144..b89531aa9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ Unreleased * Emit a deprecation notice for wrongly-rescued exceptions [#1530](https://github.com/Shopify/shopify_app/pull/1530) * Log a deprecation warning for the use of incompatible controller concerns [#1560](https://github.com/Shopify/shopify_app/pull/1560) * Fixes bug with expired sessions for embedded apps returning a 500 instead of 401 [#1580](https://github.com/Shopify/shopify_app/pull/1580) +* Generator properly handles uninstall [#1597](https://github.com/Shopify/shopify_app/pull/1597) 21.2.0 (Oct 25, 2022) ---------- diff --git a/docs/shopify_app/webhooks.md b/docs/shopify_app/webhooks.md index d3c989bd6..ae5723bcd 100644 --- a/docs/shopify_app/webhooks.md +++ b/docs/shopify_app/webhooks.md @@ -3,6 +3,7 @@ #### Table of contents [Manage webhooks using `ShopifyApp::WebhooksManager`](#manage-webhooks-using-shopifyappwebhooksmanager) +[Mandatory GDPR Webhooks](#mandatory-gdpr-webhooks) ## Manage webhooks using `ShopifyApp::WebhooksManager` @@ -70,3 +71,15 @@ rails g shopify_app:add_webhook --topic carts/update --path webhooks/carts_updat ``` Where `--topic` is the topic and `--path` is the path the webhook should be sent to. + +## Mandatory GDPR Webhooks + +We have three mandatory GDPR webhooks + +1. `customers/data_request` +2. `customer/redact` +3. `shop/redact` + +The `generate shopify_app` command generated three job templates corresponding to all three of these webhooks. +To pass our approval process you will need to set these webhooks in your partner dashboard. +You can read more about that [here](https://shopify.dev/apps/webhooks/configuration/mandatory-webhooks). \ No newline at end of file diff --git a/lib/generators/shopify_app/add_app_uninstalled_job/add_app_uninstalled_job_generator.rb b/lib/generators/shopify_app/add_app_uninstalled_job/add_app_uninstalled_job_generator.rb new file mode 100644 index 000000000..e57eb15cd --- /dev/null +++ b/lib/generators/shopify_app/add_app_uninstalled_job/add_app_uninstalled_job_generator.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +require "rails/generators/base" + +module ShopifyApp + module Generators + class AddAppUninstalledJobGenerator < Rails::Generators::Base + source_root File.expand_path("../templates", __FILE__) + + def create_job + template("app_uninstalled_job.rb", "app/jobs/app_uninstalled_job.rb") + end + end + end +end diff --git a/lib/generators/shopify_app/add_app_uninstalled_job/templates/app_uninstalled_job.rb.tt b/lib/generators/shopify_app/add_app_uninstalled_job/templates/app_uninstalled_job.rb.tt new file mode 100644 index 000000000..e0271cc6e --- /dev/null +++ b/lib/generators/shopify_app/add_app_uninstalled_job/templates/app_uninstalled_job.rb.tt @@ -0,0 +1,22 @@ +class AppUninstalledJob < ActiveJob::Base + extend ShopifyAPI::Webhooks::Handler + + class << self + def handle(topic:, shop:, body:) + perform_later(topic: topic, shop_domain: shop, webhook: body) + end + end + + def perform(topic:, shop_domain:, webhook:) + shop = Shop.find_by(shopify_domain: shop_domain) + + if shop.nil? + logger.error("#{self.class} failed: cannot find shop with domain '#{shop_domain}'") + + raise ActiveRecord::RecordNotFound, "Shop Not Found" + end + + logger.info("#{self.class} started for shop '#{shop_domain}'") + shop.destroy + end +end \ No newline at end of file diff --git a/lib/generators/shopify_app/add_gdpr_jobs/add_gdpr_jobs_generator.rb b/lib/generators/shopify_app/add_gdpr_jobs/add_gdpr_jobs_generator.rb new file mode 100644 index 000000000..3e5434b6c --- /dev/null +++ b/lib/generators/shopify_app/add_gdpr_jobs/add_gdpr_jobs_generator.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +require "rails/generators/base" + +module ShopifyApp + module Generators + class AddGdprJobsGenerator < Rails::Generators::Base + source_root File.expand_path("../templates", __FILE__) + + def add_customer_data_request_job + template("customers_data_request_job.rb", "app/jobs/customers_data_request_job.rb") + end + + def add_shop_redact_job + template("shop_redact_job.rb", "app/jobs/shop_redact_job.rb") + end + + def add_customer_redact_job + template("customers_redact_job.rb", "app/jobs/customers_redact_job.rb") + end + end + end +end diff --git a/lib/generators/shopify_app/add_gdpr_jobs/templates/customers_data_request_job.rb.tt b/lib/generators/shopify_app/add_gdpr_jobs/templates/customers_data_request_job.rb.tt new file mode 100644 index 000000000..a199b76df --- /dev/null +++ b/lib/generators/shopify_app/add_gdpr_jobs/templates/customers_data_request_job.rb.tt @@ -0,0 +1,22 @@ +class CustomersDataRequestJob < ActiveJob::Base + extend ShopifyAPI::Webhooks::Handler + + class << self + def handle(topic:, shop:, body:) + perform_later(topic: topic, shop_domain: shop, webhook: body) + end + end + + def perform(topic:, shop_domain:, webhook:) + shop = Shop.find_by(shopify_domain: shop_domain) + + if shop.nil? + logger.error("#{self.class} failed: cannot find shop with domain '#{shop_domain}'") + + raise ActiveRecord::RecordNotFound, "Shop Not Found" + end + + shop.with_shopify_session do + end + end +end \ No newline at end of file diff --git a/lib/generators/shopify_app/add_gdpr_jobs/templates/customers_redact_job.rb.tt b/lib/generators/shopify_app/add_gdpr_jobs/templates/customers_redact_job.rb.tt new file mode 100644 index 000000000..bfaff91cc --- /dev/null +++ b/lib/generators/shopify_app/add_gdpr_jobs/templates/customers_redact_job.rb.tt @@ -0,0 +1,22 @@ +class CustomersRedactJob < ActiveJob::Base + extend ShopifyAPI::Webhooks::Handler + + class << self + def handle(topic:, shop:, body:) + perform_later(topic: topic, shop_domain: shop, webhook: body) + end + end + + def perform(topic:, shop_domain:, webhook:) + shop = Shop.find_by(shopify_domain: shop_domain) + + if shop.nil? + logger.error("#{self.class} failed: cannot find shop with domain '#{shop_domain}'") + + raise ActiveRecord::RecordNotFound, "Shop Not Found" + end + + shop.with_shopify_session do + end + end +end \ No newline at end of file diff --git a/lib/generators/shopify_app/add_gdpr_jobs/templates/shop_redact_job.rb.tt b/lib/generators/shopify_app/add_gdpr_jobs/templates/shop_redact_job.rb.tt new file mode 100644 index 000000000..fe5b26a21 --- /dev/null +++ b/lib/generators/shopify_app/add_gdpr_jobs/templates/shop_redact_job.rb.tt @@ -0,0 +1,22 @@ +class ShopRedactJob < ActiveJob::Base + extend ShopifyAPI::Webhooks::Handler + + class << self + def handle(topic:, shop:, body:) + perform_later(topic: topic, shop_domain: shop, webhook: body) + end + end + + def perform(topic:, shop_domain:, webhook:) + shop = Shop.find_by(shopify_domain: shop_domain) + + if shop.nil? + logger.error("#{self.class} failed: cannot find shop with domain '#{shop_domain}'") + + raise ActiveRecord::RecordNotFound, "Shop Not Found" + end + + shop.with_shopify_session do + end + end +end \ No newline at end of file diff --git a/lib/generators/shopify_app/add_webhook/templates/webhook_job.rb.tt b/lib/generators/shopify_app/add_webhook/templates/webhook_job.rb.tt index 6c05fce46..2c7021355 100644 --- a/lib/generators/shopify_app/add_webhook/templates/webhook_job.rb.tt +++ b/lib/generators/shopify_app/add_webhook/templates/webhook_job.rb.tt @@ -12,7 +12,8 @@ class <%= @job_class_name %> < ActiveJob::Base if shop.nil? logger.error("#{self.class} failed: cannot find shop with domain '#{shop_domain}'") - return + + raise ActiveRecord::RecordNotFound, "Shop Not Found" end shop.with_shopify_session do diff --git a/lib/generators/shopify_app/install/templates/shopify_app.rb.tt b/lib/generators/shopify_app/install/templates/shopify_app.rb.tt index e47fbe729..3c2ed8f07 100644 --- a/lib/generators/shopify_app/install/templates/shopify_app.rb.tt +++ b/lib/generators/shopify_app/install/templates/shopify_app.rb.tt @@ -9,6 +9,12 @@ ShopifyApp.configure do |config| config.shop_session_repository = 'Shop' config.log_level = :info config.reauth_on_access_scope_changes = true + config.webhooks = [ + { topic: "app/uninstalled", address: "webhooks/app_uninstalled"}, + { topic: "customers/data_request", address: "webhooks/customers_data_request" }, + { topic: "customer/redact", address: "webhooks/customers_redact"}, + { topic: "shop/redact", address: "webhooks/shop_redact"} + ] config.api_key = ENV.fetch('SHOPIFY_API_KEY', '').presence config.secret = ENV.fetch('SHOPIFY_API_SECRET', '').presence diff --git a/lib/generators/shopify_app/shopify_app_generator.rb b/lib/generators/shopify_app/shopify_app_generator.rb index c1495dd6c..baee3b0fe 100644 --- a/lib/generators/shopify_app/shopify_app_generator.rb +++ b/lib/generators/shopify_app/shopify_app_generator.rb @@ -9,6 +9,8 @@ def initialize(args, *options) end def run_all_generators + generate("shopify_app:add_app_uninstalled_job") + generate("shopify_app:add_gdpr_jobs") generate("shopify_app:install #{@opts.join(" ")}") generate("shopify_app:shop_model #{@opts.join(" ")}") generate("shopify_app:authenticated_controller") diff --git a/test/generators/add_app_uninstalled_job_generator_test.rb b/test/generators/add_app_uninstalled_job_generator_test.rb new file mode 100644 index 000000000..292c7f82d --- /dev/null +++ b/test/generators/add_app_uninstalled_job_generator_test.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require "test_helper" +require "generators/shopify_app/add_app_uninstalled_job/add_app_uninstalled_job_generator" + +class AddAppUninstalledJobGeneratorTest < Rails::Generators::TestCase + tests ShopifyApp::Generators::AddAppUninstalledJobGenerator + destination File.expand_path("../tmp", File.dirname(__FILE__)) + + setup do + ShopifyApp.configure do |config| + config.embedded_app = true + end + + prepare_destination + provide_existing_application_file + provide_existing_routes_file + provide_existing_application_controller + end + + test "creates app uninstalled job file" do + run_generator + + assert_file "app/jobs/app_uninstalled_job.rb" + end +end diff --git a/test/generators/add_gdpr_jobs_generator_job_test.rb b/test/generators/add_gdpr_jobs_generator_job_test.rb new file mode 100644 index 000000000..2927b1b6e --- /dev/null +++ b/test/generators/add_gdpr_jobs_generator_job_test.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +require "test_helper" +require "generators/shopify_app/add_gdpr_jobs/add_gdpr_jobs_generator" + +class AddGdprJobsGeneratorJobTest < Rails::Generators::TestCase + tests ShopifyApp::Generators::AddGdprJobsGenerator + destination File.expand_path("../tmp", File.dirname(__FILE__)) + + setup do + ShopifyApp.configure do |config| + config.embedded_app = true + end + + prepare_destination + provide_existing_application_file + provide_existing_routes_file + provide_existing_application_controller + end + + test "creates app uninstalled job file" do + run_generator + + assert_file "app/jobs/customers_data_request_job.rb" + assert_file "app/jobs/shop_redact_job.rb" + assert_file "app/jobs/customers_redact_job.rb" + end +end