disco_app 0.8.5 → 0.8.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/disco_app/components/filterable_shop_list.js.jsx +65 -0
  3. data/app/assets/javascripts/disco_app/components/shop_filter_tab.js.jsx +34 -0
  4. data/app/assets/javascripts/disco_app/components/shop_filter_tabs.js.jsx +21 -0
  5. data/app/assets/javascripts/disco_app/components/shop_list.js.jsx +140 -0
  6. data/app/assets/javascripts/disco_app/components/shop_row.js.jsx +27 -0
  7. data/app/controllers/disco_app/admin/app_settings_controller.rb +3 -0
  8. data/app/controllers/disco_app/admin/application_controller.rb +3 -0
  9. data/app/controllers/disco_app/admin/concerns/app_settings_controller.rb +24 -0
  10. data/app/controllers/disco_app/admin/concerns/authenticated_controller.rb +20 -0
  11. data/app/controllers/disco_app/admin/concerns/shops_controller.rb +7 -0
  12. data/app/controllers/disco_app/admin/resources/shops_controller.rb +3 -0
  13. data/app/controllers/disco_app/admin/shops_controller.rb +3 -0
  14. data/app/controllers/disco_app/charges_controller.rb +1 -1
  15. data/app/controllers/disco_app/concerns/app_proxy_controller.rb +40 -0
  16. data/app/controllers/disco_app/concerns/authenticated_controller.rb +42 -0
  17. data/app/controllers/disco_app/concerns/carrier_request_controller.rb +21 -0
  18. data/app/controllers/disco_app/install_controller.rb +1 -1
  19. data/app/models/disco_app/app_settings.rb +3 -0
  20. data/app/models/disco_app/concerns/app_settings.rb +7 -0
  21. data/app/models/disco_app/concerns/shop.rb +5 -0
  22. data/app/models/disco_app/concerns/{synchronises_with_shopify.rb → synchronises.rb} +16 -3
  23. data/app/resources/disco_app/admin/resources/concerns/shop_resource.rb +46 -0
  24. data/app/resources/disco_app/admin/resources/shop_resource.rb +4 -0
  25. data/app/views/disco_app/admin/app_settings/edit.html.erb +5 -0
  26. data/app/views/disco_app/admin/shops/index.html.erb +12 -0
  27. data/app/views/layouts/admin/_navbar.html.erb +24 -0
  28. data/app/views/layouts/admin.html.erb +27 -0
  29. data/config/routes.rb +15 -0
  30. data/db/migrate/20150525000000_create_shops_if_not_existent.rb +1 -1
  31. data/db/migrate/20160112233706_create_disco_app_sessions.rb +2 -2
  32. data/db/migrate/20160223111044_create_disco_app_settings.rb +8 -0
  33. data/lib/disco_app/engine.rb +1 -0
  34. data/lib/disco_app/version.rb +1 -1
  35. data/lib/generators/disco_app/adminify/adminify_generator.rb +35 -0
  36. data/lib/generators/disco_app/disco_app_generator.rb +1 -0
  37. data/lib/generators/disco_app/templates/controllers/home_controller.rb +2 -2
  38. data/test/controllers/disco_app/admin/shops_controller_test.rb +54 -0
  39. data/test/dummy/app/controllers/disco_app/admin/shops_controller.rb +8 -0
  40. data/test/dummy/app/controllers/home_controller.rb +2 -2
  41. data/test/dummy/app/controllers/proxy_controller.rb +1 -1
  42. data/test/dummy/app/jobs/products_create_job.rb +7 -0
  43. data/test/dummy/app/jobs/products_delete_job.rb +7 -0
  44. data/test/dummy/app/jobs/products_update_job.rb +7 -0
  45. data/test/dummy/app/models/product.rb +6 -0
  46. data/test/dummy/config/database.codeship.yml +23 -0
  47. data/test/dummy/config/database.yml +8 -13
  48. data/test/dummy/db/migrate/20160307182229_create_products.rb +11 -0
  49. data/test/dummy/db/schema.rb +27 -10
  50. data/test/fixtures/products.yml +4 -0
  51. data/test/fixtures/webhooks/product_created.json +167 -0
  52. data/test/fixtures/webhooks/product_deleted.json +3 -0
  53. data/test/fixtures/webhooks/product_updated.json +167 -0
  54. data/test/integration/synchronises_test.rb +55 -0
  55. metadata +69 -9
  56. data/app/controllers/disco_app/app_proxy_controller.rb +0 -42
  57. data/app/controllers/disco_app/authenticated_controller.rb +0 -44
  58. data/app/controllers/disco_app/carrier_request_controller.rb +0 -23
  59. data/test/integration/navigation_test.rb +0 -10
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f1ef0c316e6efff6bf35a0d4306476e47e6f63b31d53040af71522754a8e2de6
4
- data.tar.gz: ed4c9043346a208cab235d6dc451df93cb0893cd7688b780bccdf0c6c876576b
3
+ metadata.gz: 56c1ba4929abd6bf9f7539edd256f66061ed90ddd974f583b229c5803cc71fdc
4
+ data.tar.gz: 75f3dcee0e4087e98c2ec9933211cdc5d5ce586a5d674f6e1507e7134267f3bc
5
5
  SHA512:
6
- metadata.gz: a73f597562488bcf93a07f6fe713857d21304eb5c109f1fc9e8fbbfae870cb2cf6f869ea82edd7053081aaf2da95de8788e3e62d76d240bae4cfc2bd84dc2369
7
- data.tar.gz: 40b93ede5ac8bb089e402a9f73c48a3e42e44ab71188fcc590f879faef8dd1a0886820b531d4d7499607397ea8edbacc76580d54b92b29d7d08cb5629a548372
6
+ metadata.gz: ae82c3a87f6b50d7c6cf878bf2eb34b047fd2d180e2609e4b15226a85dd742a973c0454363f8d4eb64ba4ea65af23b6adce122f1e4160290c9cab524a38fe45f
7
+ data.tar.gz: 6d184f92b1aed8bc41c8733b0d67869b7e5f70c82be4462657efb731c49c138bbce16c3f83322f82acfc0723f3d0d58d30c3ee5309ed6f69df19c01515bb9e28
@@ -0,0 +1,65 @@
1
+ var FilterableShopList = React.createClass({
2
+
3
+ getDefaultProps: function() {
4
+ return {
5
+ filterTabs: [
6
+ { label: 'All Shops', filter: {} },
7
+ { label: 'Never Installed', filter: { 'filter[status]': 'never_installed' } },
8
+ { label: 'Awaiting Install', filter: { 'filter[status]': 'awaiting_install' } },
9
+ { label: 'Installing', filter: { 'filter[status]': 'installing' } },
10
+ { label: 'Installed', filter: { 'filter[status]': 'installed' } },
11
+ { label: 'Awaiting Uninstall', filter: { 'filter[status]': 'awaiting_uninstall' } },
12
+ { label: 'Uninstalling', filter: { 'filter[status]': 'uninstalling' } },
13
+ { label: 'Uninstalled', filter: { 'filter[status]': 'uninstalled' } }
14
+ ],
15
+ availableFilters: {
16
+ 'filter[status]': {
17
+ getLabel: function(value) {
18
+ return 'Status is ' + value;
19
+ }
20
+ }
21
+ }
22
+ }
23
+ },
24
+
25
+ getInitialState: function() {
26
+ return {
27
+ filter: {}
28
+ };
29
+ },
30
+
31
+ onFilterReplace: function(filter) {
32
+ this.setState({
33
+ filter: filter
34
+ });
35
+ },
36
+
37
+ onFilterSet: function(name, value) {
38
+ this.onFiltersSet([{
39
+ name: name,
40
+ value: value
41
+ }]);
42
+ },
43
+
44
+ onFiltersSet: function(filters) {
45
+ var nextFilter = $.extend({}, this.state.filter);
46
+ filters.forEach(function(filter) {
47
+ if(!filter.value) {
48
+ delete nextFilter[filter.name];
49
+ } else {
50
+ nextFilter[filter.name] = filter.value;
51
+ }
52
+ });
53
+ this.onFilterReplace(nextFilter);
54
+ },
55
+
56
+ render: function() {
57
+ return (
58
+ <div className="next-card">
59
+ <ShopFilterTabs filterTabs={this.props.filterTabs} filter={this.state.filter} onFilterReplace={this.onFilterReplace} />
60
+ <ShopList shopsUrl={this.props.shopsUrl} editShopUrl={this.props.editShopUrl} filter={this.state.filter} />
61
+ </div>
62
+ );
63
+ }
64
+
65
+ });
@@ -0,0 +1,34 @@
1
+ var ShopFilterTab = React.createClass({
2
+
3
+ /**
4
+ * Handle a click event on the individual tab link.
5
+ * @param e
6
+ */
7
+ handleClick: function(e) {
8
+ e.preventDefault();
9
+
10
+ // Don't do anything if this is already the currently selected tab.
11
+ if(this.isActive()) return;
12
+
13
+ this.props.onFilterTabSelected(this.props.filterTab);
14
+ },
15
+
16
+ /**
17
+ * Return true if this is the currently active tab, determined by whether the filter values for this tab match the
18
+ * currently active filter.
19
+ * @returns {boolean}
20
+ */
21
+ isActive: function() {
22
+ return (JSON.stringify(this.props.filterTab.filter) == JSON.stringify(this.props.filter));
23
+ },
24
+
25
+ render: function() {
26
+ var tabClassName = this.isActive() ? 'next-tab next-tab--is-active' : 'next-tab';
27
+ return (
28
+ <li role="presentation">
29
+ <a href="#" className={tabClassName} onClick={this.handleClick}>{this.props.filterTab.label}</a>
30
+ </li>
31
+ )
32
+ }
33
+
34
+ });
@@ -0,0 +1,21 @@
1
+ var ShopFilterTabs = React.createClass({
2
+
3
+ onFilterTabSelected: function(filterTab) {
4
+ this.props.onFilterReplace(filterTab.filter);
5
+ },
6
+
7
+ render: function() {
8
+ var filterTabNodes = this.props.filterTabs.map(function(filterTab) {
9
+ return (
10
+ <ShopFilterTab key={filterTab.label} filter={this.props.filter} filterTab={filterTab} onFilterTabSelected={this.onFilterTabSelected} />
11
+ )
12
+ }, this);
13
+
14
+ return (
15
+ <ul className="next-tab__list">
16
+ {filterTabNodes}
17
+ </ul>
18
+ )
19
+ }
20
+
21
+ });
@@ -0,0 +1,140 @@
1
+ var ShopList = React.createClass({
2
+
3
+ getDefaultProps: function() {
4
+ return {
5
+ pageSize: 50
6
+ }
7
+ },
8
+
9
+ getInitialState: function() {
10
+ return {
11
+ shops: [],
12
+ page: 1
13
+ }
14
+ },
15
+
16
+ componentDidMount: function() {
17
+ this.getShops();
18
+ $(document).on('shops.changed', this.onShopsChanged);
19
+ },
20
+
21
+ componentWillUnmount: function() {
22
+ $(document).off('shops.changed', this.onShopsChanged);
23
+ },
24
+
25
+ componentDidUpdate: function (prevProps, prevState) {
26
+ if(prevProps.filter != this.props.filter) {
27
+ this.getShops();
28
+ }
29
+ if(prevState.page != this.state.page) {
30
+ this.getShops();
31
+ }
32
+ },
33
+
34
+ onPageChanged: function(increment) {
35
+ this.setState({page: this.state.page + increment});
36
+ },
37
+
38
+ onShopsChanged: function() {
39
+ this.setState({page: 1});
40
+ this.getShops();
41
+ },
42
+
43
+ getShops: function() {
44
+ //ShopifyApp.Bar.loadingOn();
45
+
46
+ // Set up request data with filter and pagination parameters.
47
+ var data = $.extend({
48
+ 'page[size]': this.props.pageSize,
49
+ 'page[number]': this.state.page
50
+ }, this.props.filter);
51
+
52
+ // Make the request to fetch the order list.
53
+ $.getJSON(this.props.shopsUrl, data, function(result) {
54
+ if(this.isMounted()) {
55
+ this.setState({
56
+ shops: result.data
57
+ });
58
+ }
59
+ }.bind(this))
60
+ .always(function() {
61
+ //ShopifyApp.Bar.loadingOff();
62
+ });
63
+ },
64
+
65
+ render: function() {
66
+ var shopRows = this.state.shops.map(function(shop) {
67
+ return (
68
+ <ShopRow shop={shop} editShopUrl={this.props.editShopUrl} key={shop.id} />
69
+ )
70
+ }.bind(this));
71
+
72
+ return (
73
+ <div>
74
+ <div className="next-card__section">
75
+ <table className="table-hover expanded">
76
+ <thead>
77
+ <tr>
78
+ <th>ID</th>
79
+ <th>Shopify Domain</th>
80
+ <th>Status</th>
81
+ <th>Email</th>
82
+ <th>Country Name</th>
83
+ <th>Currency</th>
84
+ <th>Domain</th>
85
+ <th>Plan</th>
86
+ <th>Created</th>
87
+ <th>Installed Duration</th>
88
+ </tr>
89
+ </thead>
90
+ <tbody>
91
+ {shopRows}
92
+ </tbody>
93
+ </table>
94
+ </div>
95
+ <div className="next-card__section">
96
+ <div className="next-grid next-grid--no-padding next-grid--center-aligned">
97
+ <div className="next-grid__cell next-grid__cell--no-flex">
98
+ <ShopList.Paginator page={this.state.page} pageSize={this.props.pageSize} items={this.state.shops} onPageChanged={this.onPageChanged} />
99
+ </div>
100
+ </div>
101
+ </div>
102
+ </div>
103
+ )
104
+ }
105
+
106
+ });
107
+
108
+ /**
109
+ * Paginator.
110
+ */
111
+ ShopList.Paginator = React.createClass({
112
+
113
+ handlePreviousClick: function(e) {
114
+ this.props.onPageChanged(-1);
115
+ },
116
+
117
+ handleNextClick: function(e) {
118
+ this.props.onPageChanged(1);
119
+ },
120
+
121
+ render: function() {
122
+ return (
123
+ <div className="btn-group" role="group">
124
+ <ShopList.PaginatorButton label="&lsaquo;" isDisabled={this.props.page < 2} onClick={this.handlePreviousClick} />
125
+ <ShopList.PaginatorButton label="&rsaquo;" isDisabled={this.props.items.length < this.props.pageSize} onClick={this.handleNextClick} />
126
+ </div>
127
+ )
128
+ }
129
+
130
+ });
131
+
132
+ /**
133
+ * Individual button within a paginator.
134
+ */
135
+ ShopList.PaginatorButton = (props) => {
136
+ var className = 'btn btn-default' + (props.isDisabled ? ' disabled' : '');
137
+ return (
138
+ <button className={className} disabled={props.isDisabled} onClick={props.onClick}>{props.label}</button>
139
+ )
140
+ };
@@ -0,0 +1,27 @@
1
+ var ShopRow = (props) => {
2
+
3
+ var shop = props.shop,
4
+ editShopUrl = props.editShopUrl.replace(':id', shop.id),
5
+ shopifyDomain = shop.attributes['shopify_domain'],
6
+ countryName = shop.attributes['country_name'],
7
+ currency = shop.attributes['currency'],
8
+ domainName = shop.attributes['domain'],
9
+ planName = shop.attributes['plan_display_name'],
10
+ createdDate = shop.attributes['created_at'],
11
+ installedDuration = shop.attributes['installed_duration']
12
+
13
+ return (
14
+ <tr>
15
+ <td><a href={editShopUrl}>#{shop.id}</a></td>
16
+ <td>{shopifyDomain}</td>
17
+ <td>{shop.attributes.status}</td>
18
+ <td>{shop.attributes.email}</td>
19
+ <td>{countryName}</td>
20
+ <td>{shop.attributes.currency}</td>
21
+ <td>{shop.attributes.domain}</td>
22
+ <td>{planName}</td>
23
+ <td>{createdDate}</td>
24
+ <td>{installedDuration}</td>
25
+ </tr>
26
+ )
27
+ };
@@ -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,7 @@
1
+ module DiscoApp::Admin::Concerns::ShopsController
2
+ extend ActiveSupport::Concern
3
+
4
+ def index
5
+ end
6
+
7
+ 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
@@ -1,6 +1,6 @@
1
1
  module DiscoApp
2
2
  class ChargesController < ApplicationController
3
- include DiscoApp::AuthenticatedController
3
+ include DiscoApp::Concerns::AuthenticatedController
4
4
 
5
5
  skip_before_action :verify_status, only: [:create, :activate]
6
6
 
@@ -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 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,42 @@
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 :verify_status
8
+ around_filter :shopify_session
9
+ layout 'embedded_app'
10
+ end
11
+
12
+ private
13
+
14
+ def shopify_shop
15
+ if shop_session
16
+ @shop = DiscoApp::Shop.find_by!(shopify_domain: @shop_session.url)
17
+ else
18
+ redirect_to_login
19
+ end
20
+ end
21
+
22
+ def verify_status
23
+ if not (@shop.charge_active? or @shop.charge_waived?)
24
+ redirect_if_not_current_path(disco_app.new_charge_path)
25
+ elsif @shop.charge_accepted?
26
+ redirect_if_not_current_path(disco_app.activate_charge_path)
27
+ elsif @shop.never_installed? or @shop.uninstalled?
28
+ redirect_if_not_current_path(disco_app.install_path)
29
+ elsif @shop.awaiting_install? or @shop.installing?
30
+ redirect_if_not_current_path(disco_app.installing_path)
31
+ elsif @shop.awaiting_uninstall? or @shop.uninstalling?
32
+ redirect_if_not_current_path(disco_app.uninstalling_path)
33
+ end
34
+ end
35
+
36
+ def redirect_if_not_current_path(target)
37
+ if request.path != target
38
+ redirect_to target
39
+ end
40
+ end
41
+
42
+ 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 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
@@ -1,6 +1,6 @@
1
1
  module DiscoApp
2
2
  class InstallController < ApplicationController
3
- include DiscoApp::AuthenticatedController
3
+ include DiscoApp::Concerns::AuthenticatedController
4
4
 
5
5
  # Start the installation process for the current shop, then redirect to the installing screen.
6
6
  def install
@@ -0,0 +1,3 @@
1
+ class DiscoApp::AppSettings < ActiveRecord::Base
2
+ include DiscoApp::Concerns::AppSettings
3
+ end
@@ -0,0 +1,7 @@
1
+ module DiscoApp::Concerns::AppSettings
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ acts_as_singleton
6
+ end
7
+ end
@@ -3,6 +3,7 @@ module DiscoApp::Concerns::Shop
3
3
 
4
4
  included do
5
5
  include ShopifyApp::Shop
6
+ include ActionView::Helpers::DateHelper
6
7
 
7
8
  # Define relationships to plans and subscriptions.
8
9
  has_many :subscriptions
@@ -65,6 +66,10 @@ module DiscoApp::Concerns::Shop
65
66
  "https://#{shopify_domain}/admin"
66
67
  end
67
68
 
69
+ def installed_duration
70
+ distance_of_time_in_words_to_now(created_at.time)
71
+ end
72
+
68
73
  end
69
74
 
70
75
  end
@@ -1,11 +1,17 @@
1
- module DiscoApp::Concerns::SynchronisesWithShopify
1
+ module DiscoApp::Concerns::Synchronises
2
2
  extend ActiveSupport::Concern
3
3
 
4
4
  class_methods do
5
5
 
6
- def synchronise_from_shopify(shop, data)
6
+ def should_synchronise?(shop, data)
7
+ true
8
+ end
9
+
10
+ def synchronise(shop, data)
7
11
  data = data.with_indifferent_access
8
12
 
13
+ return unless should_synchronise?(shop, data)
14
+
9
15
  instance = self.find_or_create_by!(id: data[:id]) do |instance|
10
16
  instance.shop = shop
11
17
  instance.data = data
@@ -16,8 +22,15 @@ module DiscoApp::Concerns::SynchronisesWithShopify
16
22
  instance
17
23
  end
18
24
 
19
- def synchronise_deletion_from_shopify(shop, data)
25
+ def should_synchronise_deletion?(shop, data)
26
+ true
27
+ end
28
+
29
+ def synchronise_deletion(shop, data)
20
30
  data = data.with_indifferent_access
31
+
32
+ return unless should_synchronise_deletion?(shop, data)
33
+
21
34
  self.destroy_all(shop: shop, id: data[:id])
22
35
  end
23
36
 
@@ -0,0 +1,46 @@
1
+ require 'jsonapi/resource'
2
+
3
+ module DiscoApp::Admin::Resources::Concerns::ShopResource
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+
8
+ attributes :shopify_domain, :status, :email, :country_name
9
+ attributes :currency, :domain, :plan_display_name, :created_at
10
+ attributes :installed_duration
11
+
12
+ model_name 'DiscoApp::Shop'
13
+
14
+ filters :status
15
+
16
+ # Adjust the base records method to ensure only models for the authenticated domain are retrieved.
17
+ def self.records(options = {})
18
+ records = DiscoApp::Shop.order(created_at: :desc)
19
+ records
20
+ end
21
+
22
+ # Apply filters.
23
+ def self.apply_filter(records, filter, value, options)
24
+ return records if value.blank?
25
+
26
+ # Perform appropriate filtering.
27
+ case filter
28
+ when :status
29
+ return records.where(status: value.map { |v| DiscoApp::Shop.statuses[v.to_sym] } )
30
+ else
31
+ return super(records, filter, value)
32
+ end
33
+ end
34
+
35
+ # Don't allow the update of any fields via the API.
36
+ def self.updatable_fields(context)
37
+ []
38
+ end
39
+
40
+ # Don't allow the creation of any fields via the API.
41
+ def self.creatable_fields(context)
42
+ []
43
+ end
44
+
45
+ end
46
+ end
@@ -0,0 +1,4 @@
1
+ class DiscoApp::Admin::Resources::ShopResource < JSONAPI::Resource
2
+ include DiscoApp::Admin::Resources::Concerns::ShopResource
3
+
4
+ end
@@ -0,0 +1,5 @@
1
+ <% provide(:title, 'App Settings') %>
2
+
3
+ <p>Update 'app/views/disco_app/admin/app_settings/edit.html.erb' on your own rails app.</p>
4
+
5
+
@@ -0,0 +1,12 @@
1
+ <% provide(:title, 'Shops') %>
2
+
3
+ <div class="next-grid">
4
+ <div class="next-grid__cell">
5
+
6
+ <%= react_component('FilterableShopList', {
7
+ shopsUrl: admin_resources_shops_path(format: :json),
8
+ editShopUrl: edit_admin_shop_path(':id')
9
+ }) %>
10
+
11
+ </div>
12
+ </div>
@@ -0,0 +1,24 @@
1
+ <nav class="navbar navbar-default navbar-static-top">
2
+ <div class="container-fluid">
3
+
4
+ <!-- Brand & Toggle -->
5
+ <div class="navbar-header">
6
+ <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
7
+ <span class="sr-only">Toggle navigation</span>
8
+ <span class="icon-bar"></span>
9
+ <span class="icon-bar"></span>
10
+ <span class="icon-bar"></span>
11
+ </button>
12
+ <a class="navbar-brand" href="<%= admin_shops_path %>">Credible</a>
13
+ </div>
14
+
15
+ <!-- Collapsing Navigation Links -->
16
+ <div class="collapse navbar-collapse">
17
+ <ul class="nav navbar-nav">
18
+ <%= active_link_to 'Shops', admin_shops_path, wrap_tag: :li %>
19
+ <%= active_link_to 'Settings', edit_admin_app_settings_path, wrap_tag: :li %>
20
+ </ul>
21
+ </div>
22
+
23
+ </div>
24
+ </nav>