disco_app 0.5.6 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/app/assets/images/disco_app/icon.svg +1 -0
- data/app/assets/stylesheets/disco_app/{bootstrap-custom.scss → bootstrap/_custom.scss} +3 -4
- data/app/assets/stylesheets/disco_app/bootstrap/_variables.scss +872 -0
- data/app/assets/stylesheets/disco_app/disco/_buttons.scss +31 -0
- data/app/assets/stylesheets/disco_app/disco/_cards.scss +43 -0
- data/app/assets/stylesheets/disco_app/disco/_forms.scss +23 -0
- data/app/assets/stylesheets/disco_app/disco/_sections.scss +61 -0
- data/app/assets/stylesheets/disco_app/disco/_type.scss +21 -0
- data/app/assets/stylesheets/disco_app/disco/mixins/_flexbox.scss +394 -0
- data/app/assets/stylesheets/disco_app/disco_app.scss +12 -6
- data/app/controllers/disco_app/authenticated_controller.rb +1 -1
- data/app/controllers/disco_app/install_controller.rb +1 -1
- data/app/controllers/disco_app/webhooks_controller.rb +11 -9
- data/app/jobs/disco_app/app_installed_job.rb +1 -1
- data/app/jobs/disco_app/app_uninstalled_job.rb +2 -17
- data/app/jobs/disco_app/concerns/app_uninstalled_job.rb +19 -0
- data/app/jobs/disco_app/shop_job.rb +1 -1
- data/app/jobs/disco_app/shop_update_job.rb +1 -1
- data/app/models/disco_app/concerns/plan.rb +14 -0
- data/app/models/disco_app/concerns/shop.rb +62 -0
- data/app/models/disco_app/concerns/subscription.rb +14 -0
- data/app/models/disco_app/plan.rb +3 -0
- data/app/models/disco_app/session_storage.rb +18 -0
- data/app/models/disco_app/shop.rb +2 -43
- data/app/models/disco_app/subscription.rb +3 -0
- data/app/services/disco_app/subscription_service.rb +25 -0
- data/app/services/disco_app/webhook_service.rb +30 -0
- data/app/views/disco_app/shared/_card.html.erb +16 -0
- data/app/views/disco_app/shared/_section.html.erb +17 -0
- data/{lib/generators/disco_app/templates → app}/views/layouts/embedded_app.html.erb +14 -6
- data/db/migrate/20150525000000_create_shops_if_not_existent.rb +15 -0
- data/db/migrate/20151017231302_create_disco_app_plans.rb +13 -0
- data/db/migrate/20151017232027_create_disco_app_subscriptions.rb +15 -0
- data/db/migrate/20151017234409_move_shop_to_disco_app_engine.rb +5 -0
- data/lib/disco_app/engine.rb +5 -0
- data/lib/disco_app/version.rb +1 -1
- data/lib/generators/disco_app/disco_app_generator.rb +31 -54
- data/lib/generators/disco_app/mailify/mailify_generator.rb +55 -0
- data/lib/generators/disco_app/templates/initializers/shopify_session_repository.rb +7 -0
- data/test/controllers/disco_app/install_controller_test.rb +50 -0
- data/test/controllers/disco_app/webhooks_controller_test.rb +58 -0
- data/test/controllers/home_controller_test.rb +61 -0
- data/test/dummy/app/assets/javascripts/application.js +4 -0
- data/test/dummy/app/assets/stylesheets/application.scss +5 -0
- data/test/dummy/app/controllers/application_controller.rb +1 -0
- data/test/dummy/app/controllers/home_controller.rb +7 -0
- data/test/dummy/app/jobs/disco_app/app_uninstalled_job.rb +11 -0
- data/test/dummy/app/models/disco_app/shop.rb +15 -0
- data/test/dummy/app/views/home/index.html.erb +2 -0
- data/test/dummy/config/application.rb +11 -0
- data/test/dummy/config/environments/production.rb +7 -1
- data/test/dummy/config/initializers/disco_app.rb +1 -0
- data/test/dummy/config/initializers/omniauth.rb +9 -0
- data/test/dummy/config/initializers/shopify_app.rb +7 -0
- data/test/dummy/config/initializers/shopify_session_repository.rb +7 -0
- data/test/dummy/config/routes.rb +5 -1
- data/test/dummy/config/secrets.yml +2 -2
- data/test/dummy/db/schema.rb +70 -0
- data/test/fixtures/api/widget_store/shop.json +46 -0
- data/test/fixtures/disco_app/plans.yml +32 -0
- data/test/fixtures/disco_app/shops.yml +10 -0
- data/test/fixtures/disco_app/subscriptions.yml +26 -0
- data/test/fixtures/webhooks/app_uninstalled.json +46 -0
- data/test/jobs/disco_app/app_installed_job_test.rb +29 -0
- data/test/jobs/disco_app/app_uninstalled_job_test.rb +32 -0
- data/test/models/disco_app/plan_test.rb +5 -0
- data/test/models/disco_app/shop_test.rb +26 -0
- data/test/models/disco_app/subscription_test.rb +6 -0
- data/test/services/disco_app/subscription_service_test.rb +28 -0
- data/test/support/test_file_fixtures.rb +29 -0
- data/test/test_helper.rb +32 -1
- metadata +148 -30
- data/app/assets/stylesheets/disco_app/bootstrap-variables.scss +0 -12
- data/lib/generators/disco_app/templates/jobs/app_installed_job.rb +0 -2
- data/lib/generators/disco_app/templates/jobs/app_uninstalled_job.rb +0 -2
- data/lib/generators/disco_app/templates/jobs/shop_update_job.rb +0 -2
- data/lib/generators/disco_app/templates/models/shop.rb +0 -3
- data/test/dummy/README.rdoc +0 -28
- data/test/dummy/app/assets/stylesheets/application.css +0 -15
- data/test/dummy/app/views/layouts/application.html.erb +0 -14
- data/test/lib/generators/disco_app/disco_app_generator_test.rb +0 -16
- /data/{lib/generators/disco_app/templates → app}/views/layouts/application.html.erb +0 -0
- /data/{lib/generators/disco_app/templates → app}/views/sessions/new.html.erb +0 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module DiscoApp::Concerns::AppUninstalledJob
|
|
2
|
+
extend ActiveSupport::Concern
|
|
3
|
+
|
|
4
|
+
included do
|
|
5
|
+
|
|
6
|
+
before_enqueue { @shop.awaiting_uninstall! }
|
|
7
|
+
before_perform { @shop.uninstalling! }
|
|
8
|
+
after_perform { @shop.uninstalled! }
|
|
9
|
+
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def perform(domain, shop_data)
|
|
13
|
+
# Mark the shop's charge status as "cancelled" unless charges have been waived.
|
|
14
|
+
unless @shop.charge_waived?
|
|
15
|
+
@shop.charge_cancelled!
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
end
|
|
@@ -9,7 +9,7 @@ module DiscoApp
|
|
|
9
9
|
shop_data = HashWithIndifferentAccess.new(shop_data)
|
|
10
10
|
|
|
11
11
|
# Update model attributes present in both our model and the data hash.
|
|
12
|
-
@shop.update_attributes(shop_data.except(:id, :created_at).slice(
|
|
12
|
+
@shop.update_attributes(shop_data.except(:id, :created_at).slice(*DiscoApp::Shop.column_names))
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module DiscoApp::Concerns::Plan
|
|
2
|
+
extend ActiveSupport::Concern
|
|
3
|
+
|
|
4
|
+
included do
|
|
5
|
+
|
|
6
|
+
has_many :subscriptions
|
|
7
|
+
has_many :shops, through: :subscriptions
|
|
8
|
+
|
|
9
|
+
enum status: [:available, :unavailable, :hidden]
|
|
10
|
+
|
|
11
|
+
scope :available, -> { where status: statuses[:available] }
|
|
12
|
+
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
module DiscoApp::Concerns::Shop
|
|
2
|
+
extend ActiveSupport::Concern
|
|
3
|
+
|
|
4
|
+
included do
|
|
5
|
+
include ShopifyApp::Shop
|
|
6
|
+
|
|
7
|
+
# Define relationships to plans and subscriptions.
|
|
8
|
+
has_many :subscriptions
|
|
9
|
+
has_many :plans, through: :subscriptions
|
|
10
|
+
|
|
11
|
+
# Define possible installation statuses as an enum.
|
|
12
|
+
enum status: [:never_installed, :awaiting_install, :installing, :installed, :awaiting_uninstall, :uninstalling, :uninstalled]
|
|
13
|
+
|
|
14
|
+
# Define possible charge statuses as an enum.
|
|
15
|
+
enum charge_status: [:charge_none, :charge_pending, :charge_accepted, :charge_declined, :charge_active, :charge_cancelled, :charge_waived]
|
|
16
|
+
|
|
17
|
+
# Define some useful scopes.
|
|
18
|
+
scope :status, -> (status) { where status: status }
|
|
19
|
+
scope :installed, -> { where status: statuses[:installed] }
|
|
20
|
+
scope :has_active_shopify_plan, -> { where.not(plan_name: [:cancelled, :frozen]) }
|
|
21
|
+
|
|
22
|
+
# Alias 'with_shopify_session' as 'temp', as per our existing conventions.
|
|
23
|
+
alias_method :temp, :with_shopify_session
|
|
24
|
+
|
|
25
|
+
# Return a hash of attributes that should be used to create a new charge for this shop.
|
|
26
|
+
# This method can be overridden by the inheriting Shop class in order to provide charges
|
|
27
|
+
# customised to a particular shop. Otherwise, the default settings configured in application.rb
|
|
28
|
+
# will be used.
|
|
29
|
+
def new_charge_attributes
|
|
30
|
+
{
|
|
31
|
+
type: Rails.configuration.x.shopify_charges_default_type,
|
|
32
|
+
name: Rails.configuration.x.shopify_app_name,
|
|
33
|
+
price: Rails.configuration.x.shopify_charges_default_price,
|
|
34
|
+
trial_days: Rails.configuration.x.shopify_charges_default_trial_days,
|
|
35
|
+
}
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Update this Shop's charge_status attribute based on the given Shopify charge object.
|
|
39
|
+
def update_charge_status(shopify_charge)
|
|
40
|
+
status_update_method_name = "charge_#{shopify_charge.status}!"
|
|
41
|
+
self.public_send(status_update_method_name) if self.respond_to? status_update_method_name
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Convenience method to get the currently active subscription for this Shop.
|
|
45
|
+
def current_subscription
|
|
46
|
+
subscriptions.active.first
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Return the absolute URL to the shop's storefront.
|
|
50
|
+
# @TODO: Account for HTTPS.
|
|
51
|
+
def url
|
|
52
|
+
"http://#{domain}"
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Return the absolute URL to the shop's admin.
|
|
56
|
+
def admin_url
|
|
57
|
+
"https://#{shopify_domain}/admin"
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module DiscoApp::Concerns::Subscription
|
|
2
|
+
extend ActiveSupport::Concern
|
|
3
|
+
|
|
4
|
+
included do
|
|
5
|
+
|
|
6
|
+
belongs_to :shop
|
|
7
|
+
belongs_to :plan
|
|
8
|
+
|
|
9
|
+
enum status: [:active, :replaced, :cancelled]
|
|
10
|
+
|
|
11
|
+
scope :active, -> { where status: statuses[:active] }
|
|
12
|
+
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module DiscoApp
|
|
2
|
+
class SessionStorage
|
|
3
|
+
def self.store(session)
|
|
4
|
+
shop = Shop.find_or_initialize_by(shopify_domain: session.url)
|
|
5
|
+
shop.shopify_token = session.token
|
|
6
|
+
shop.save!
|
|
7
|
+
shop.id
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def self.retrieve(id)
|
|
11
|
+
return unless id
|
|
12
|
+
shop = Shop.find(id)
|
|
13
|
+
ShopifyAPI::Session.new(shop.shopify_domain, shop.shopify_token)
|
|
14
|
+
rescue ActiveRecord::RecordNotFound
|
|
15
|
+
nil
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -1,44 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
extend ActiveSupport::Concern
|
|
4
|
-
|
|
5
|
-
# Include the base ShopifyApp functionality.
|
|
6
|
-
include ShopifyApp::Shop
|
|
7
|
-
|
|
8
|
-
included do
|
|
9
|
-
# Define possible installation statuses as an enum.
|
|
10
|
-
enum status: [:never_installed, :awaiting_install, :installing, :installed, :awaiting_uninstall, :uninstalling, :uninstalled]
|
|
11
|
-
|
|
12
|
-
# Define possible charge statuses as an enum.
|
|
13
|
-
enum charge_status: [:charge_none, :charge_pending, :charge_accepted, :charge_declined, :charge_active, :charge_cancelled, :charge_waived]
|
|
14
|
-
|
|
15
|
-
# Define some useful scopes.
|
|
16
|
-
scope :status, -> (status) { where status: status }
|
|
17
|
-
scope :installed, -> { where status: ::Shop.statuses[:installed] }
|
|
18
|
-
scope :has_active_shopify_plan, -> { where.not(plan_name: [:cancelled, :frozen]) }
|
|
19
|
-
|
|
20
|
-
# Alias 'with_shopify_session' as 'temp', as per our existing conventions.
|
|
21
|
-
alias_method :temp, :with_shopify_session
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
# Return a hash of attributes that should be used to create a new charge for this shop.
|
|
25
|
-
# This method can be overridden by the inheriting Shop class in order to provide charges
|
|
26
|
-
# customised to a particular shop. Otherwise, the default settings configured in application.rb
|
|
27
|
-
# will be used.
|
|
28
|
-
def new_charge_attributes
|
|
29
|
-
{
|
|
30
|
-
type: Rails.configuration.x.shopify_charges_default_type,
|
|
31
|
-
name: Rails.configuration.x.shopify_app_name,
|
|
32
|
-
price: Rails.configuration.x.shopify_charges_default_price,
|
|
33
|
-
trial_days: Rails.configuration.x.shopify_charges_default_trial_days,
|
|
34
|
-
}
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
# Update this Shop's charge_status attribute based on the given Shopify charge object.
|
|
38
|
-
def update_charge_status(shopify_charge)
|
|
39
|
-
status_update_method_name = "charge_#{shopify_charge.status}!"
|
|
40
|
-
self.public_send(status_update_method_name) if self.respond_to? status_update_method_name
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
end
|
|
1
|
+
class DiscoApp::Shop < ActiveRecord::Base
|
|
2
|
+
include DiscoApp::Concerns::Shop
|
|
44
3
|
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
class DiscoApp::SubscriptionService
|
|
2
|
+
|
|
3
|
+
# Subscribe the given shop to the given plan.
|
|
4
|
+
def self.subscribe(shop, plan)
|
|
5
|
+
# Mark all existing active subscriptions as replaced.
|
|
6
|
+
shop.subscriptions.active.update_all(status: DiscoApp::Subscription.statuses[:replaced])
|
|
7
|
+
|
|
8
|
+
# Add the new subscription.
|
|
9
|
+
DiscoApp::Subscription.create!(
|
|
10
|
+
shop: shop,
|
|
11
|
+
plan: plan,
|
|
12
|
+
status: DiscoApp::Subscription.statuses[:active],
|
|
13
|
+
name: plan.name,
|
|
14
|
+
charge_type: plan.charge_type,
|
|
15
|
+
price: plan.default_price,
|
|
16
|
+
trial_days: plan.default_trial_days
|
|
17
|
+
)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Cancel any active subscription for the given shop.
|
|
21
|
+
def self.cancel(shop)
|
|
22
|
+
shop.subscriptions.active.update_all(status: DiscoApp::Subscription.statuses[:cancelled])
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
class DiscoApp::WebhookService
|
|
2
|
+
|
|
3
|
+
# Return true iff the provided hmac_to_verify matches that calculated from the
|
|
4
|
+
# give data and secret.
|
|
5
|
+
def self.is_valid_hmac?(body, secret, hmac_to_verify)
|
|
6
|
+
self.calculated_hmac(body, secret) == hmac_to_verify
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
# Calculate the HMAC for the given data and secret.
|
|
10
|
+
def self.calculated_hmac(body, secret)
|
|
11
|
+
digest = OpenSSL::Digest.new('sha256')
|
|
12
|
+
Base64.encode64(OpenSSL::HMAC.digest(digest, secret, body)).strip
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Try to find a job class for the given webhook topic.
|
|
16
|
+
def self.find_job_class(topic)
|
|
17
|
+
begin
|
|
18
|
+
# First try to find a top-level matching job class.
|
|
19
|
+
"#{topic}_job".gsub('/', '_').classify.constantize
|
|
20
|
+
rescue NameError
|
|
21
|
+
# If that fails, try to find a DiscoApp:: prefixed job class.
|
|
22
|
+
begin
|
|
23
|
+
%Q{DiscoApp::#{"#{topic}_job".gsub('/', '_').classify}}.constantize
|
|
24
|
+
rescue NameError
|
|
25
|
+
nil
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<% disabled ||= false %>
|
|
2
|
+
<div class="next-card <% if disabled %>next-card--disabled<% end %>">
|
|
3
|
+
<% if content_for?(:card_header) %>
|
|
4
|
+
<header class="next-card__header">
|
|
5
|
+
<%= content_for :card_header %>
|
|
6
|
+
</header>
|
|
7
|
+
<% end %>
|
|
8
|
+
<section class="next-card__section">
|
|
9
|
+
<%= content_for :card_content %>
|
|
10
|
+
</section>
|
|
11
|
+
<% if content_for?(:card_footer) %>
|
|
12
|
+
<footer class="next-card__footer">
|
|
13
|
+
<%= content_for :card_footer %>
|
|
14
|
+
</footer>
|
|
15
|
+
<% end %>
|
|
16
|
+
</div>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<section class="section">
|
|
2
|
+
<div class="layout-content">
|
|
3
|
+
|
|
4
|
+
<aside class="layout-content__sidebar layout-content__first">
|
|
5
|
+
<% if content_for?(:section_summary) %>
|
|
6
|
+
<div class="section-summary">
|
|
7
|
+
<%= content_for :section_summary %>
|
|
8
|
+
</div>
|
|
9
|
+
<% end %>
|
|
10
|
+
</aside>
|
|
11
|
+
|
|
12
|
+
<section class="layout-content__main">
|
|
13
|
+
<%= content_for :section_content %>
|
|
14
|
+
</section>
|
|
15
|
+
|
|
16
|
+
</div>
|
|
17
|
+
</section>
|
|
@@ -5,17 +5,12 @@
|
|
|
5
5
|
|
|
6
6
|
<script src="//cdn.shopify.com/s/assets/external/app.js?<%= Time.now.strftime('%Y%m%d%H') %>"></script>
|
|
7
7
|
<script type="text/javascript">
|
|
8
|
+
// Initialise the Shopify App.
|
|
8
9
|
ShopifyApp.init({
|
|
9
10
|
"apiKey": "<%= ShopifyApp.configuration.api_key %>",
|
|
10
11
|
"shopOrigin": "<%= "https://#{ @shop_session.url }" if @shop_session %>",
|
|
11
12
|
"debug": <%= Rails.env.development? ? 'true' : 'false' %>
|
|
12
13
|
});
|
|
13
|
-
|
|
14
|
-
ShopifyApp.ready(function() {
|
|
15
|
-
ShopifyApp.Bar.initialize({
|
|
16
|
-
title: "<%= yield(:title) %>"
|
|
17
|
-
});
|
|
18
|
-
});
|
|
19
14
|
</script>
|
|
20
15
|
|
|
21
16
|
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %>
|
|
@@ -25,9 +20,22 @@
|
|
|
25
20
|
<%= yield :extra_head %>
|
|
26
21
|
</head>
|
|
27
22
|
<body>
|
|
23
|
+
<script type="text/javascript">
|
|
24
|
+
ShopifyApp.Bar.initialize({
|
|
25
|
+
title: "<%= yield(:title) %>",
|
|
26
|
+
icon: "<%= image_url("disco_app/icon.svg") %>",
|
|
27
|
+
buttons: <%= content_for?(:buttons) ? content_for(:buttons) : '{}' %>
|
|
28
|
+
});
|
|
29
|
+
</script>
|
|
28
30
|
|
|
29
31
|
<%= yield %>
|
|
30
32
|
|
|
33
|
+
<% flash.each do |key, message| %>
|
|
34
|
+
<script type="text/javascript">
|
|
35
|
+
ShopifyApp.flash<%= (key == 'error') ? 'Error' : 'Notice' %>('<%= message %>');
|
|
36
|
+
</script>
|
|
37
|
+
<% end %>
|
|
38
|
+
|
|
31
39
|
<%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
|
|
32
40
|
</body>
|
|
33
41
|
</html>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
class CreateShopsIfNotExistent < ActiveRecord::Migration
|
|
2
|
+
|
|
3
|
+
def change
|
|
4
|
+
unless table_exists? :shops or table_exists? :disco_app_shops
|
|
5
|
+
create_table :shops do |t|
|
|
6
|
+
t.string :shopify_domain, null: false
|
|
7
|
+
t.string :shopify_token, null: false
|
|
8
|
+
t.timestamps
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
add_index :shops, :shopify_domain, unique: true
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
class CreateDiscoAppPlans < ActiveRecord::Migration
|
|
2
|
+
def change
|
|
3
|
+
create_table :disco_app_plans do |t|
|
|
4
|
+
t.integer :status
|
|
5
|
+
t.string :name
|
|
6
|
+
t.integer :charge_type
|
|
7
|
+
t.decimal :default_price
|
|
8
|
+
t.integer :default_trial_days
|
|
9
|
+
|
|
10
|
+
t.timestamps null: false
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
class CreateDiscoAppSubscriptions < ActiveRecord::Migration
|
|
2
|
+
def change
|
|
3
|
+
create_table :disco_app_subscriptions do |t|
|
|
4
|
+
t.belongs_to :shop, index: true
|
|
5
|
+
t.belongs_to :plan, index: true
|
|
6
|
+
t.integer :status
|
|
7
|
+
t.string :name
|
|
8
|
+
t.integer :charge_type
|
|
9
|
+
t.decimal :price
|
|
10
|
+
t.integer :trial_days
|
|
11
|
+
|
|
12
|
+
t.timestamps null: false
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
data/lib/disco_app/engine.rb
CHANGED
data/lib/disco_app/version.rb
CHANGED
|
@@ -27,14 +27,14 @@ class DiscoAppGenerator < Rails::Generators::Base
|
|
|
27
27
|
gsub_file 'Gemfile', /^# Use sqlite3 as the database for Active Record\ngem 'sqlite3'/m, ''
|
|
28
28
|
|
|
29
29
|
# Add gems common to all environments.
|
|
30
|
-
gem 'shopify_app', '~> 6.
|
|
31
|
-
gem 'sidekiq', '~> 3.5.
|
|
32
|
-
gem 'puma', '~> 2.
|
|
30
|
+
gem 'shopify_app', '~> 6.2.0'
|
|
31
|
+
gem 'sidekiq', '~> 3.5.1'
|
|
32
|
+
gem 'puma', '~> 2.14.0'
|
|
33
33
|
gem 'bootstrap-sass', '~> 3.3.5.1'
|
|
34
34
|
|
|
35
35
|
# Add gems for development and testing only.
|
|
36
36
|
gem_group :development, :test do
|
|
37
|
-
gem 'sqlite3', '~> 1.3.
|
|
37
|
+
gem 'sqlite3', '~> 1.3.11'
|
|
38
38
|
gem 'dotenv-rails', '~> 2.0.2'
|
|
39
39
|
gem 'minitest-reporters', '~> 1.0.19'
|
|
40
40
|
gem 'guard', '~> 2.13.0'
|
|
@@ -43,7 +43,7 @@ class DiscoAppGenerator < Rails::Generators::Base
|
|
|
43
43
|
|
|
44
44
|
# Add gems for production only.
|
|
45
45
|
gem_group :production do
|
|
46
|
-
gem 'pg', '~> 0.18.
|
|
46
|
+
gem 'pg', '~> 0.18.3'
|
|
47
47
|
gem 'rails_12factor', '~> 0.0.3'
|
|
48
48
|
end
|
|
49
49
|
end
|
|
@@ -86,45 +86,18 @@ class DiscoAppGenerator < Rails::Generators::Base
|
|
|
86
86
|
# Create Rakefiles
|
|
87
87
|
def create_rakefiles
|
|
88
88
|
rakefile 'start.rake' do
|
|
89
|
-
|
|
89
|
+
<<-RAKEFILE.strip_heredoc
|
|
90
90
|
task start: :environment do
|
|
91
91
|
system 'bundle exec rails server -b 127.0.0.1 -p 3000'
|
|
92
92
|
end
|
|
93
|
-
|
|
93
|
+
RAKEFILE
|
|
94
94
|
end
|
|
95
95
|
rakefile 'console.rake' do
|
|
96
|
-
|
|
96
|
+
<<-RAKEFILE.strip_heredoc
|
|
97
97
|
task console: :environment do
|
|
98
98
|
system 'bundle exec rails console'
|
|
99
99
|
end
|
|
100
|
-
|
|
101
|
-
end
|
|
102
|
-
end
|
|
103
|
-
|
|
104
|
-
# Run shopify_app:install and shopify_app:shop_model
|
|
105
|
-
def shopify_app_install
|
|
106
|
-
generate 'shopify_app:install'
|
|
107
|
-
generate 'shopify_app:shop_model'
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
# Set up initializers, overriding some of the defaults generated by shopify_app:install and shopify_app:shop_model
|
|
111
|
-
def setup_initializers
|
|
112
|
-
['shopify_app', 'disco_app'].each do |initializer_name|
|
|
113
|
-
copy_file "initializers/#{initializer_name}.rb", "config/initializers/#{initializer_name}.rb"
|
|
114
|
-
end
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
# Set up models, overriding some of the defaults generated by shopify_app:install and shopify_app:shop_model
|
|
118
|
-
def setup_models
|
|
119
|
-
['shop'].each do |model_name|
|
|
120
|
-
copy_file "models/#{model_name}.rb", "app/models/#{model_name}.rb"
|
|
121
|
-
end
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
# Set up default jobs.
|
|
125
|
-
def setup_jobs
|
|
126
|
-
['app_installed', 'app_uninstalled', 'shop_update'].each do |job_name|
|
|
127
|
-
copy_file "jobs/#{job_name}_job.rb", "app/jobs/#{job_name}_job.rb"
|
|
100
|
+
RAKEFILE
|
|
128
101
|
end
|
|
129
102
|
end
|
|
130
103
|
|
|
@@ -133,29 +106,33 @@ class DiscoAppGenerator < Rails::Generators::Base
|
|
|
133
106
|
route "mount DiscoApp::Engine, at: '/'"
|
|
134
107
|
end
|
|
135
108
|
|
|
136
|
-
#
|
|
137
|
-
def
|
|
138
|
-
|
|
139
|
-
copy_file "controllers/#{controller_name}_controller.rb", "app/controllers/#{controller_name}_controller.rb"
|
|
140
|
-
end
|
|
109
|
+
# Run shopify_app:install
|
|
110
|
+
def shopify_app_install
|
|
111
|
+
generate 'shopify_app:install'
|
|
141
112
|
end
|
|
142
113
|
|
|
143
|
-
#
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
114
|
+
# Copy template files to the appropriate location. In some cases, we'll be
|
|
115
|
+
# overwriting or removing existing files or those created by ShopifyApp.
|
|
116
|
+
def copy_and_remove_files
|
|
117
|
+
# Copy initializers
|
|
118
|
+
copy_file 'initializers/shopify_app.rb', 'config/initializers/shopify_app.rb'
|
|
119
|
+
copy_file 'initializers/disco_app.rb', 'config/initializers/disco_app.rb'
|
|
120
|
+
copy_file 'initializers/shopify_session_repository.rb', 'config/initializers/shopify_session_repository.rb'
|
|
149
121
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
['javascripts/application.js', 'stylesheets/application.scss'].each do |asset_name|
|
|
154
|
-
copy_file "assets/#{asset_name}", "app/assets/#{asset_name}"
|
|
155
|
-
end
|
|
122
|
+
# Copy default home controller and view
|
|
123
|
+
copy_file 'controllers/home_controller.rb', 'app/controllers/home_controller.rb'
|
|
124
|
+
copy_file 'views/home/index.html.erb', 'app/views/home/index.html.erb'
|
|
156
125
|
|
|
157
|
-
#
|
|
126
|
+
# Copy assets
|
|
127
|
+
copy_file 'assets/javascripts/application.js', 'app/assets/javascripts/application.js'
|
|
128
|
+
copy_file 'assets/stylesheets/application.scss', 'app/assets/stylesheets/application.scss'
|
|
129
|
+
|
|
130
|
+
# Remove application.css
|
|
158
131
|
remove_file 'app/assets/stylesheets/application.css'
|
|
132
|
+
|
|
133
|
+
# Remove the layout files created by ShopifyApp
|
|
134
|
+
remove_file 'app/views/layout/application.html.erb'
|
|
135
|
+
remove_file 'app/views/layout/embedded_app.html.erb'
|
|
159
136
|
end
|
|
160
137
|
|
|
161
138
|
# Copy engine migrations over.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
module DiscoApp
|
|
2
|
+
module Generators
|
|
3
|
+
class MailifyGenerator < Rails::Generators::Base
|
|
4
|
+
|
|
5
|
+
source_root File.expand_path('../templates', __FILE__)
|
|
6
|
+
|
|
7
|
+
# Install the react-rails gem and run its setup.
|
|
8
|
+
def install_gem
|
|
9
|
+
# Add premailer gem to Gemfile.
|
|
10
|
+
gem 'premailer-rails', '~> 1.8.2'
|
|
11
|
+
|
|
12
|
+
# Add explicit dependency on Nokogiri
|
|
13
|
+
gem 'nokogiri', '~> 1.6.6.1'
|
|
14
|
+
|
|
15
|
+
# Add the Mailgun rails gem (production only)
|
|
16
|
+
gem_group :production do
|
|
17
|
+
gem 'mailgun_rails', '~> 0.7.0'
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Install gem.
|
|
21
|
+
Bundler.with_clean_env do
|
|
22
|
+
run 'bundle install'
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Set application configuration
|
|
27
|
+
def configure_application
|
|
28
|
+
configuration = <<-CONFIG.strip_heredoc
|
|
29
|
+
|
|
30
|
+
# Configure ActionMailer to use MailGun
|
|
31
|
+
if ENV['MAILGUN_API_KEY']
|
|
32
|
+
config.action_mailer.delivery_method = :mailgun
|
|
33
|
+
config.action_mailer.mailgun_settings = {
|
|
34
|
+
api_key: ENV['MAILGUN_API_KEY'],
|
|
35
|
+
domain: ENV['MAILGUN_API_DOMAIN']
|
|
36
|
+
}
|
|
37
|
+
end
|
|
38
|
+
CONFIG
|
|
39
|
+
application configuration, env: :production
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Add entries to .env and .env.sample
|
|
43
|
+
def add_env_variables
|
|
44
|
+
configuration = <<-CONFIG.strip_heredoc
|
|
45
|
+
|
|
46
|
+
MAILGUN_API_KEY=
|
|
47
|
+
MAILGUN_API_DOMAIN=
|
|
48
|
+
CONFIG
|
|
49
|
+
append_to_file '.env', configuration
|
|
50
|
+
append_to_file '.env.sample', configuration
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
require 'test_helper'
|
|
2
|
+
|
|
3
|
+
class DiscoApp::InstallControllerTest < ActionController::TestCase
|
|
4
|
+
include ActiveJob::TestHelper
|
|
5
|
+
|
|
6
|
+
def setup
|
|
7
|
+
@shop = disco_app_shops(:widget_store)
|
|
8
|
+
@routes = DiscoApp::Engine.routes
|
|
9
|
+
log_in_as(@shop)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def teardown
|
|
13
|
+
@shop = nil
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
test 'logged-in but uninstalled user triggers installation from install page' do
|
|
17
|
+
get(:install)
|
|
18
|
+
assert_redirected_to :installing
|
|
19
|
+
assert_enqueued_jobs 1
|
|
20
|
+
@shop.reload
|
|
21
|
+
assert @shop.awaiting_install?
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
test 'logged-in and installed user is redirected to installing url for install/uninstalling actions' do
|
|
25
|
+
@shop.installed!
|
|
26
|
+
[:install, :uninstalling].each do |action|
|
|
27
|
+
get(:install)
|
|
28
|
+
assert_redirected_to :installing
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
test 'logged-in and installed user is redirected to root url for installing' do
|
|
33
|
+
@shop.installed!
|
|
34
|
+
get(:installing)
|
|
35
|
+
assert_redirected_to Rails.application.routes.url_helpers.root_path
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
test 'logged-in and uninstalling user sees uninstalling page' do
|
|
39
|
+
@shop.uninstalling!
|
|
40
|
+
get(:uninstalling)
|
|
41
|
+
assert_response :success
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
test 'logged-in and uninstalled user starts install process again' do
|
|
45
|
+
@shop.uninstalled!
|
|
46
|
+
get(:uninstalling)
|
|
47
|
+
assert_redirected_to :install
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
end
|