app_manager 0.1.2 → 1.0.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/Gemfile.lock +16 -1
- data/README.md +110 -1
- data/app/controllers/app_manager/application_controller.rb +7 -0
- data/app/controllers/app_manager/banners_controller.rb +7 -2
- data/app/controllers/app_manager/charges_controller.rb +122 -0
- data/app/controllers/app_manager/plans_controller.rb +157 -0
- data/app/controllers/concerns/app_manager/authenticate.rb +10 -0
- data/app_manager.gemspec +2 -2
- data/config/routes.rb +14 -1
- data/lib/app_manager/actions.rb +10 -0
- data/lib/app_manager/api_cache_handler.rb +67 -0
- data/lib/app_manager/client/banners.rb +1 -1
- data/lib/app_manager/client/connection.rb +37 -3
- data/lib/app_manager/client/plans.rb +40 -0
- data/lib/app_manager/client.rb +9 -3
- data/lib/app_manager/config.rb +82 -0
- data/lib/app_manager/fail_safe.rb +324 -0
- data/lib/app_manager/graphql_helper.rb +125 -0
- data/lib/app_manager/model.rb +98 -0
- data/lib/app_manager/protection.rb +21 -0
- data/lib/app_manager/railtie.rb +10 -0
- data/lib/app_manager/response_cache.rb +40 -0
- data/lib/app_manager/tasks/sync/local_app_manager.rake +12 -0
- data/lib/app_manager/version.rb +1 -1
- data/lib/app_manager.rb +22 -2
- data/lib/generators/app_manager/install/install_generator.rb +8 -0
- data/lib/generators/app_manager/install/templates/app_manager.rb.tt +61 -0
- metadata +45 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5d1ca5e305b62425f916bcb20e5677a2bc0be1e4b16f2d68879dcce443da5ffe
|
4
|
+
data.tar.gz: 1d174e5fbd19029a498c539ae908089dfcab7dea8b4d2f43d8e3bf48b9b354ca
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1b846e8b38a8fcb8301715c137f23e875828195c9b73bca8e1bbb814cf66fee9565690a296465757955d267fc290f574ba3434a63449657444c52144e0ad7c31
|
7
|
+
data.tar.gz: 00cdb184943ace69f37c5e166950791e193c67e071a88692a7b81d45de79a5d648ee2c70887c5f3cc1eea1d2ec5df82a7632aa9ae241c7f518c7f8ef52f31abe
|
data/Gemfile.lock
CHANGED
@@ -1,9 +1,11 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
app_manager (0.
|
4
|
+
app_manager (1.0.0)
|
5
5
|
httparty
|
6
|
+
kaminari (>= 0.16.3)
|
6
7
|
rails (>= 5.2.0)
|
8
|
+
sqlite3 (~> 1.3.0)
|
7
9
|
|
8
10
|
GEM
|
9
11
|
remote: https://rubygems.org/
|
@@ -94,6 +96,18 @@ GEM
|
|
94
96
|
i18n (1.10.0)
|
95
97
|
concurrent-ruby (~> 1.0)
|
96
98
|
io-wait (0.2.1)
|
99
|
+
kaminari (1.2.2)
|
100
|
+
activesupport (>= 4.1.0)
|
101
|
+
kaminari-actionview (= 1.2.2)
|
102
|
+
kaminari-activerecord (= 1.2.2)
|
103
|
+
kaminari-core (= 1.2.2)
|
104
|
+
kaminari-actionview (1.2.2)
|
105
|
+
actionview
|
106
|
+
kaminari-core (= 1.2.2)
|
107
|
+
kaminari-activerecord (1.2.2)
|
108
|
+
activerecord
|
109
|
+
kaminari-core (= 1.2.2)
|
110
|
+
kaminari-core (1.2.2)
|
97
111
|
loofah (2.15.0)
|
98
112
|
crass (~> 1.0.2)
|
99
113
|
nokogiri (>= 1.5.9)
|
@@ -178,6 +192,7 @@ GEM
|
|
178
192
|
diff-lcs (>= 1.2.0, < 2.0)
|
179
193
|
rspec-support (~> 3.11.0)
|
180
194
|
rspec-support (3.11.0)
|
195
|
+
sqlite3 (1.3.13)
|
181
196
|
strscan (3.0.1)
|
182
197
|
thor (1.2.1)
|
183
198
|
timeout (0.2.0)
|
data/README.md
CHANGED
@@ -18,17 +18,126 @@ Step 2) Execute following in terminal:
|
|
18
18
|
rails g app_manager:install
|
19
19
|
|
20
20
|
|
21
|
-
this install command will mount routes in your rails route.
|
21
|
+
this install command will mount routes in your rails route and will create a config file at ```config/initializers/app_manager.rb```
|
22
22
|
|
23
|
+
```
|
24
|
+
AppManager.configure do |config|
|
25
|
+
config.enable_caching = false # Optional, True to enable app-manager api response caching, default is enabled from gem
|
26
|
+
config.expires_in = 1.days # Optional, Example: 30.seconds, 5.minutes or 2.days Default caching is for 1.days from gem
|
27
|
+
config.app_url = '' # App URL like https://volumediscount.hulkapps.dev/ or #https://5044-2409-4052-209a-69da-9d7-925a-9418-a9c3.ngrok.io
|
28
|
+
config.shopify_api_key = '' # Shopify api key of app
|
29
|
+
config.shopify_api_version = '' # Must be 2022-04 or latest
|
30
|
+
config.shopify_table_name = 'shops' # Table name which is generated by shopify mostly it is 'shops'
|
31
|
+
config.shopify_domain_field = 'shopify_domain' #shopify domain field
|
32
|
+
config.plan_id_or_name_field = 'plan_id'
|
33
|
+
config.field_names = {
|
34
|
+
'name' => 'shopify_domain', # demo-rahul-tiwari.myshopify.com
|
35
|
+
'shopify_email' => 'email', # rahul.t@hulkapps.com
|
36
|
+
'shopify_token' => 'shopify_token',
|
37
|
+
'shopify_plan' => 'plan_name', # partner_test
|
38
|
+
'plan_id' => 'plan_id', # 1. t
|
39
|
+
'created_at' => 'created_at', # 2022-04-15 10:43:05
|
40
|
+
'trial_activated_at' => 'trial_activated_at' # field name that stores trial start/activated date
|
41
|
+
}
|
42
|
+
config.plan_features = [
|
43
|
+
{
|
44
|
+
"uuid" => "b48a3a6c-c1fb-11ec-9d64-0242ac120002",
|
45
|
+
"name" => "Features 1",
|
46
|
+
"slug" => "feature-1",
|
47
|
+
"description" => "Feature Description",
|
48
|
+
"value_type" => "integer",
|
49
|
+
"format" => "count",
|
50
|
+
"display_order" => 1
|
51
|
+
},
|
52
|
+
{
|
53
|
+
"uuid" => "9f18f95a-bfaf-11ec-9d64-0242ac120002",
|
54
|
+
"name" => "Features 2",
|
55
|
+
"slug" => "feature-2",
|
56
|
+
"description" => "Feature Description",
|
57
|
+
"value_type" => "boolean",
|
58
|
+
"format" => "percentage",
|
59
|
+
"display_order" => 2
|
60
|
+
},
|
61
|
+
{
|
62
|
+
"uuid" => "9f190a26-bfaf-11ec-9d64-0242ac120002",
|
63
|
+
"name" => "Features 3",
|
64
|
+
"slug" => "feature-3",
|
65
|
+
"description" => "Feature Description",
|
66
|
+
"value_type" => "string",
|
67
|
+
"format" => "string",
|
68
|
+
"display_order" => 3
|
69
|
+
},
|
70
|
+
{
|
71
|
+
"uuid" => "9f191340-bfaf-11ec-9d64-0242ac12000",
|
72
|
+
"name" => "Features 4",
|
73
|
+
"slug" => "feature-4",
|
74
|
+
"description" => "Feature Description",
|
75
|
+
"value_type" => "array",
|
76
|
+
"values" => [
|
77
|
+
"val 1",
|
78
|
+
"val 2"
|
79
|
+
],
|
80
|
+
"format" => "string",
|
81
|
+
"display_order" => 4
|
82
|
+
}
|
83
|
+
] #Required, Values type : integer, boolean, string, array
|
84
|
+
end
|
85
|
+
```
|
86
|
+
|
87
|
+
|
88
|
+
Please note: Your shops table must have 'plan_id' as int/bigint and 'trial_activated_at' as datetime otherwise add its migration. Sample example:
|
89
|
+
```
|
90
|
+
class AddPlanIdToShops < ActiveRecord::Migration[5.2]
|
91
|
+
def change
|
92
|
+
add_column :shops, :plan_id, :bigint
|
93
|
+
add_column :shops, :trial_activated_at, :datetime
|
94
|
+
end
|
95
|
+
end
|
96
|
+
```
|
97
|
+
|
98
|
+
and map these new fields in 'config.field_names' in above initializer file.
|
23
99
|
|
24
100
|
Step 3) You must have to set ENV variable with key 'APP_MANAGER_ACCESS_TOKEN' in your application.yml or .env file like this:
|
25
101
|
|
26
102
|
APP_MANAGER_ACCESS_TOKEN: 'XXXXXXXXXXXXXXXXXXX'
|
27
103
|
APP_MANAGER_API_URL: 'https://XXXXXXXX.com'
|
28
104
|
|
105
|
+
Steps 4) App Manager provides a rake task that must needs to include in your existing app cron for every 10 minutes. Like if you are using [whenever](https://github.com/javan/whenever) gem then you must need to include following rake take in your 'schedule.rb' (created by whenever gem)
|
106
|
+
|
107
|
+
```
|
108
|
+
every 10.minutes do
|
109
|
+
rake 'sync:local_app_manager'
|
110
|
+
end
|
111
|
+
```
|
112
|
+
|
113
|
+
Steps 5) App Manager provides a helper module which needs to include in your 'shop' model something like below
|
114
|
+
|
115
|
+
```
|
116
|
+
class Shop < ActiveRecord::Base
|
117
|
+
include AppManager::Model
|
118
|
+
end
|
119
|
+
```
|
120
|
+
|
29
121
|
|
30
122
|
## Usage
|
31
123
|
|
124
|
+
Using above 5th step you can access following methods from your shop object.
|
125
|
+
|
126
|
+
```
|
127
|
+
@shop.has_plan # return true or false
|
128
|
+
|
129
|
+
@shop.plan_features # return array of plan features of your shop's plan
|
130
|
+
|
131
|
+
@shop.has_feature('feature-1') # provide slug of feature which you set in your 'config.plan_features' in config/initializers/app_manager.rb
|
132
|
+
#return true/false
|
133
|
+
|
134
|
+
@shop.get_feature('feature-3') # provide slug of feature and this returns value of feature which is set in portal.
|
135
|
+
|
136
|
+
@shop.get_remaining_days # return integer based on trial activated date.
|
137
|
+
```
|
138
|
+
|
139
|
+
|
140
|
+
|
32
141
|
#### As a helper:
|
33
142
|
|
34
143
|
|
@@ -1,5 +1,12 @@
|
|
1
1
|
module AppManager
|
2
2
|
class ApplicationController < ActionController::Base
|
3
|
+
include AppManager::Actions
|
4
|
+
cache_response_of_app_manager
|
3
5
|
protect_from_forgery with: :exception
|
6
|
+
include AppManager::Authenticate
|
7
|
+
# rescue_from StandardError do |exception|
|
8
|
+
# render json: { :error => exception.message }, :status => 500
|
9
|
+
# end
|
10
|
+
|
4
11
|
end
|
5
12
|
end
|
@@ -3,8 +3,13 @@ require_dependency "app_manager/application_controller"
|
|
3
3
|
module AppManager
|
4
4
|
class BannersController < ApplicationController
|
5
5
|
def marketing_banner
|
6
|
-
|
7
|
-
|
6
|
+
begin
|
7
|
+
banner_obj = AppManager::Client.new
|
8
|
+
render json: banner_obj.get_banners
|
9
|
+
rescue Exception => e
|
10
|
+
@fs = AppManager::FailSafe.new
|
11
|
+
render json: @fs.get_local_app_structures
|
12
|
+
end
|
8
13
|
end
|
9
14
|
end
|
10
15
|
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
require_dependency "app_manager/application_controller"
|
2
|
+
|
3
|
+
module AppManager
|
4
|
+
class ChargesController < ApplicationController
|
5
|
+
include HTTParty
|
6
|
+
skip_before_action :verify_authenticity_token, :only => [:process_plan]
|
7
|
+
before_action :params_permit
|
8
|
+
require 'time'
|
9
|
+
require 'rack'
|
10
|
+
|
11
|
+
def process_plan
|
12
|
+
if params[:shop].present? && params[:plan_id].present?
|
13
|
+
@shop = shop_data
|
14
|
+
if !@shop.nil?
|
15
|
+
plan_obj = AppManager::Client.new
|
16
|
+
plan_data = plan_obj.get_plan(params[:plan_id],params[:shop])
|
17
|
+
if plan_data.present? && plan_data.is_a?(Hash)
|
18
|
+
request_data = {'shop' => @shop.shopify_domain, 'timestamp' => Time.now.to_i, 'plan' => params[:plan_id]}
|
19
|
+
return_url = "#{app_url}#{plan_callback_path}?#{Rack::Utils.build_query(request_data)}"
|
20
|
+
gq_obj = AppManager::GraphqlHelper.new(@shop.shopify_domain,@shop.shopify_token)
|
21
|
+
data = gq_obj.recurring_charge_api_call(plan_data,return_url,@shop)
|
22
|
+
if !data["errors"].present? && (data["data"].present? && data["data"]["appSubscriptionCreate"].present? && ( !data["data"]["appSubscriptionCreate"]["userErrors"].any? && data["data"]["appSubscriptionCreate"]["confirmationUrl"]))
|
23
|
+
redirect_charge = data["data"]["appSubscriptionCreate"]["confirmationUrl"]
|
24
|
+
render json: {'redirect_url' => redirect_charge}
|
25
|
+
else
|
26
|
+
render json: {'error' => data["errors"].as_json}
|
27
|
+
end
|
28
|
+
else
|
29
|
+
render json: {'error' => data["errors"]}
|
30
|
+
end
|
31
|
+
else
|
32
|
+
render json: {'error' => 'Shop not found'}
|
33
|
+
end
|
34
|
+
else
|
35
|
+
render json: {'error' => 'Missing Shop domain or plan id in params'}
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
def callback
|
41
|
+
if params[:charge_id].present? && params[:shop].present? && params[:plan].present?
|
42
|
+
@shop = shop_data
|
43
|
+
shopify_token = @field_names['shopify_token']
|
44
|
+
shopify_domain = @field_names['name']
|
45
|
+
if !@shop.nil?
|
46
|
+
headers = {"X-Shopify-Access-Token" => @shop[shopify_token]}
|
47
|
+
charges = HTTParty.get('https://'+@shop[shopify_domain]+'/admin/api/'+@api_version+'/recurring_application_charges/'+params[:charge_id]+'.json', :headers => headers)
|
48
|
+
|
49
|
+
if charges.parsed_response && charges.parsed_response.is_a?(Hash) && charges.parsed_response.has_key?('recurring_application_charge')
|
50
|
+
|
51
|
+
plan_obj = AppManager::Client.new
|
52
|
+
plan_data = plan_obj.get_plan(params[:plan],params[:shop])
|
53
|
+
|
54
|
+
charge = charges.parsed_response['recurring_application_charge']
|
55
|
+
charge['charge_id'] = charge['id'];
|
56
|
+
charge['type'] = 'recurring';
|
57
|
+
charge['plan_id'] = params[:plan];
|
58
|
+
charge['shop_domain'] = params[:shop];
|
59
|
+
charge['interval'] = plan_data['interval']['value'];
|
60
|
+
|
61
|
+
if !@plan_field.nil?
|
62
|
+
begin
|
63
|
+
plan_obj.cancel_charge(@shop[shopify_domain],@shop[@plan_field])
|
64
|
+
rescue Exception => e
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
68
|
+
charge_ob = AppManager::Client.new(nil,json_req=true)
|
69
|
+
response = charge_ob.store_charge(charge.to_json)
|
70
|
+
|
71
|
+
if response['message'] == "success"
|
72
|
+
model.update(@plan_field => params[:plan])
|
73
|
+
end
|
74
|
+
redirect_to "/?shop=#{params[:shop]}"
|
75
|
+
else
|
76
|
+
|
77
|
+
render json: {'error' => 'Invalid shopify charge'}
|
78
|
+
end
|
79
|
+
else
|
80
|
+
render json: {'error' => 'Shop not found'}
|
81
|
+
end
|
82
|
+
else
|
83
|
+
render json: {'error' => 'Invalid params'}
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
def params_permit
|
90
|
+
params.permit!
|
91
|
+
end
|
92
|
+
|
93
|
+
def model
|
94
|
+
@models = ActiveRecord::Base.connection.tables
|
95
|
+
@config_table = AppManager.configuration.shopify_table_name
|
96
|
+
@shopify_domain = AppManager.configuration.shopify_domain_field
|
97
|
+
@plan_field = AppManager.configuration.plan_id_or_name_field
|
98
|
+
@field_names = AppManager.configuration.field_names
|
99
|
+
@api_version = AppManager.configuration.shopify_api_version
|
100
|
+
if @models.include?(@config_table) && !@plan_field.nil?
|
101
|
+
return @config_table.classify.constantize
|
102
|
+
else
|
103
|
+
return nil
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def shop_data
|
108
|
+
if model
|
109
|
+
return model.where(@shopify_domain => params[:shop]).first rescue nil
|
110
|
+
else
|
111
|
+
return nil
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def app_url
|
116
|
+
AppManager.configuration.app_url
|
117
|
+
end
|
118
|
+
|
119
|
+
|
120
|
+
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,157 @@
|
|
1
|
+
require_dependency "app_manager/application_controller"
|
2
|
+
|
3
|
+
module AppManager
|
4
|
+
class PlansController < ApplicationController
|
5
|
+
skip_before_action :verify_authenticity_token, :only => [:active_without_plan,:burst_cache,:fail_safe_backup]
|
6
|
+
def index
|
7
|
+
render :json => {"features" => AppManager.configuration.plan_features || []}
|
8
|
+
end
|
9
|
+
|
10
|
+
|
11
|
+
def plans
|
12
|
+
active_plan_id_or_name = shopify_plan = plan = nil
|
13
|
+
default_plan_id = nil
|
14
|
+
plan_obj = AppManager::Client.new
|
15
|
+
plans = plan_obj.get_plans
|
16
|
+
if params[:shop_domain].present? && !AppManager.configuration.plan_features.nil?
|
17
|
+
@shop = shop_data
|
18
|
+
if !@shop.nil?
|
19
|
+
active_plan_id_or_name = @shop[@plan_field]
|
20
|
+
if !@field_names.nil? && @field_names.has_key?('shopify_plan') && !@field_names.nil?
|
21
|
+
shopify_plan_field = AppManager.configuration.field_names['shopify_plan']
|
22
|
+
shopify_plan = @shop[shopify_plan_field] rescue nil
|
23
|
+
plan = plans.any? && !active_plan_id_or_name.nil? ? (plans.find{|x| x["id"] == active_plan_id_or_name}.present? ? plans.find{|x| x["id"] == active_plan_id_or_name} : nil ) : nil
|
24
|
+
end
|
25
|
+
else
|
26
|
+
Rails.logger.info "APP MANGAGER >>>> Either model is defined wrong or config.plan_id_or_name_field is nil in initializer ==="
|
27
|
+
end
|
28
|
+
else
|
29
|
+
Rails.logger.info "APP MANGAGER >>>> Either params missing store_domain or config.plan_features is nil in initializer ==="
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
default_plan_id = 0;
|
34
|
+
default_plan_data = plans.select{|x| x['interval'] == 'EVERY_30_DAYS' }.sort_by { |k| -k["price"] }
|
35
|
+
store_base_plan = default_plan_data.map{|e| e['store_base_plan']}.first rescue false
|
36
|
+
|
37
|
+
if store_base_plan
|
38
|
+
begin
|
39
|
+
shopify_plans = default_plan_data.map { |e| e.map { |key, value| [e['shopify_plans'], e['id']] }.to_h }
|
40
|
+
shopify_plans.each do |shp|
|
41
|
+
if shp.keys && shp.keys.first && shp.keys.first.class == Array
|
42
|
+
if shp.keys.first.include?(shopify_plan) && shp.values && shp.values.first && shp.values.first.to_i > 0
|
43
|
+
default_plan_id = shp.values.first.to_i
|
44
|
+
break
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
rescue Exception => e
|
49
|
+
Rails.logger.info "APP MANGAGER >>>> ERROR #{e.inspect}"
|
50
|
+
end
|
51
|
+
else
|
52
|
+
default_plan_id = default_plan_data.map{|e| e['id']}.first if default_plan_data.any? rescue nil
|
53
|
+
end
|
54
|
+
|
55
|
+
response = {
|
56
|
+
'plans' => plan_obj.get_plans,
|
57
|
+
'shopify_plan' => shopify_plan,
|
58
|
+
'plan' => plan,
|
59
|
+
'default_plan_id' => default_plan_id
|
60
|
+
}
|
61
|
+
render json: response
|
62
|
+
end
|
63
|
+
|
64
|
+
def users
|
65
|
+
if model
|
66
|
+
data = model.page(params[:page]).per(10)
|
67
|
+
users = {
|
68
|
+
"current_page" => data.current_page,
|
69
|
+
"data" => data.collect{|st| @field_names.map{ |key, value| [key, "#{st[value]}"] }.to_h },
|
70
|
+
"first_page_url" => "#{app_url}/api/app-manager/users?page=1",
|
71
|
+
"from" => data.total_pages ? 1 : nil,
|
72
|
+
"last_page" => data.total_pages,
|
73
|
+
"last_page_url" => data.total_pages ? "#{app_url}/api/app-manager/users?page=#{data.total_pages}" : nil,
|
74
|
+
"links"=> '',
|
75
|
+
"next_page_url" => data.next_page ? "#{app_url}/api/app-manager/users?page=#{data.next_page}" : nil,
|
76
|
+
"path" => "#{app_url}/api/app-manager/users",
|
77
|
+
"per_page" => 1,
|
78
|
+
"prev_page_url" => data.prev_page ? "#{app_url}/api/app-manager/users?page=#{data.prev_page}" : nil,
|
79
|
+
"to" => data.total_pages,
|
80
|
+
"total" => data.total_pages
|
81
|
+
}
|
82
|
+
render json: users
|
83
|
+
|
84
|
+
else
|
85
|
+
render json: nil
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def active_without_plan
|
90
|
+
if params[:shop_domain].present? && params[:plan_id].present? && model
|
91
|
+
@trial_activated_field = AppManager.configuration.field_names['trial_activated_at']
|
92
|
+
model.update(@plan_field => params[:plan_id],@trial_activated_field => DateTime.now)
|
93
|
+
render json: {'status' => true}
|
94
|
+
else
|
95
|
+
render json: {'status' => false,'error' => 'Shop not found or missing shop'}, status: 422
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
|
100
|
+
def burst_cache
|
101
|
+
AppManager.clear_cache
|
102
|
+
head :ok
|
103
|
+
end
|
104
|
+
|
105
|
+
|
106
|
+
def fail_safe_backup
|
107
|
+
params_permit
|
108
|
+
@fs = AppManager::FailSafe.new
|
109
|
+
begin
|
110
|
+
@fs.sync_app_manager
|
111
|
+
rescue Exception => e
|
112
|
+
Rails.logger.info "APP MANGAGER >>>> LOCAL DB couldn't sync with POTAL DB #{e.inspect}"
|
113
|
+
end
|
114
|
+
|
115
|
+
begin
|
116
|
+
@fs.save_api_data(params)
|
117
|
+
rescue Exception => e
|
118
|
+
Rails.logger.info "APP MANGAGER >>>> #{e.inspect}"
|
119
|
+
end
|
120
|
+
head :ok
|
121
|
+
end
|
122
|
+
|
123
|
+
|
124
|
+
private
|
125
|
+
|
126
|
+
def params_permit
|
127
|
+
params.permit!
|
128
|
+
end
|
129
|
+
|
130
|
+
def model
|
131
|
+
@models = ActiveRecord::Base.connection.tables
|
132
|
+
@config_table = AppManager.configuration.shopify_table_name
|
133
|
+
@shopify_domain = AppManager.configuration.shopify_domain_field
|
134
|
+
@plan_field = AppManager.configuration.plan_id_or_name_field
|
135
|
+
@field_names = AppManager.configuration.field_names
|
136
|
+
@api_version = AppManager.configuration.shopify_api_version
|
137
|
+
if @models.include?(@config_table) && !@plan_field.nil?
|
138
|
+
return @config_table.classify.constantize
|
139
|
+
else
|
140
|
+
return nil
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def shop_data
|
145
|
+
if model
|
146
|
+
return model.where(@shopify_domain => params[:shop_domain]).first rescue nil
|
147
|
+
else
|
148
|
+
return nil
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def app_url
|
153
|
+
AppManager.configuration.app_url
|
154
|
+
end
|
155
|
+
|
156
|
+
end
|
157
|
+
end
|
data/app_manager.gemspec
CHANGED
@@ -32,8 +32,8 @@ Gem::Specification.new do |spec|
|
|
32
32
|
# spec.add_dependency "example-gem", "~> 1.0"
|
33
33
|
spec.add_dependency "httparty"
|
34
34
|
spec.add_dependency "rails", '>= 5.2.0'
|
35
|
-
|
36
|
-
|
35
|
+
spec.add_dependency "kaminari", '>= 0.16.3'
|
36
|
+
spec.add_dependency "sqlite3", '~> 1.3.0'
|
37
37
|
# For more information and examples about making a new gem, checkout our
|
38
38
|
# guide at: https://bundler.io/guides/creating_gem.html
|
39
39
|
end
|
data/config/routes.rb
CHANGED
@@ -1,5 +1,18 @@
|
|
1
1
|
AppManager::Engine.routes.draw do
|
2
2
|
scope 'api/app-manager' do
|
3
3
|
get 'marketing-banners' => 'banners#marketing_banner'
|
4
|
+
|
5
|
+
get 'plan-features' => 'plans#index'
|
6
|
+
get 'plans' => 'plans#plans'
|
7
|
+
get 'users' => 'plans#users'
|
8
|
+
post 'active-without-plan' => 'plans#active_without_plan'
|
9
|
+
post 'burst-cache' => 'plans#burst_cache'
|
10
|
+
post 'fail-safe-backup' => 'plans#fail_safe_backup'
|
11
|
+
get 'plan/process/:plan_id' => 'charges#process_plan'
|
12
|
+
get 'plan/callback' => 'charges#callback', as: :plan_callback
|
13
|
+
|
14
|
+
scope 'app-manager-api' do
|
15
|
+
post 'store-charge' => 'plans#store_charge'
|
16
|
+
end
|
4
17
|
end
|
5
|
-
end
|
18
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module AppManager
|
2
|
+
module Actions
|
3
|
+
class ApiCacheHandler
|
4
|
+
def initialize
|
5
|
+
|
6
|
+
end
|
7
|
+
|
8
|
+
|
9
|
+
def around(controller)
|
10
|
+
init(controller)
|
11
|
+
if should_response_cache?
|
12
|
+
log_info
|
13
|
+
render_cached_response
|
14
|
+
else
|
15
|
+
yield
|
16
|
+
@response_cache.write_cache(controller.response) if @request.get?
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
protected
|
21
|
+
|
22
|
+
def should_response_cache?
|
23
|
+
return @request.get? && @response_cache.cached_response.present?
|
24
|
+
end
|
25
|
+
|
26
|
+
def log_info
|
27
|
+
# processor = "#{@controller.class.name}##{@controller.action_name}".blue
|
28
|
+
# responder = Rainbow('API Response Cache').green
|
29
|
+
Rails.logger.info "=== #{@controller.class.name}##{@controller.action_name} response by App Manager Response Cache ==="
|
30
|
+
end
|
31
|
+
|
32
|
+
def init(controller)
|
33
|
+
@controller = controller
|
34
|
+
@request = controller.request
|
35
|
+
@response_cache = ResponseCache.new(cache_path)
|
36
|
+
end
|
37
|
+
|
38
|
+
def cache_path
|
39
|
+
@cache_path = "app-manager-cache"
|
40
|
+
|
41
|
+
path_only = @request.fullpath.split('?').first
|
42
|
+
@cache_path = "#{@cache_path}#{path_only}"
|
43
|
+
|
44
|
+
if AppManager.configuration.cache_by_headers.present?
|
45
|
+
headers = AppManager.configuration.cache_by_headers.map do |header|
|
46
|
+
@request.headers[header].to_s
|
47
|
+
end
|
48
|
+
headers_cache_path = headers.join('-')
|
49
|
+
@cache_path = "#{@cache_path}/#{headers_cache_path}"
|
50
|
+
end
|
51
|
+
|
52
|
+
@cache_path
|
53
|
+
end
|
54
|
+
|
55
|
+
def render_cached_response
|
56
|
+
body = @response_cache.body
|
57
|
+
status = @response_cache.status
|
58
|
+
headers = @response_cache.headers
|
59
|
+
headers.try(:each) do |key, value|
|
60
|
+
@controller.response.headers[key] = value
|
61
|
+
end
|
62
|
+
@controller.render plain: body, status: status
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
@@ -24,9 +24,19 @@ module AppManager
|
|
24
24
|
|
25
25
|
def request(http_method, path, options)
|
26
26
|
response = self.class.send(http_method, path, { body: options })
|
27
|
-
|
28
|
-
|
29
|
-
|
27
|
+
if path.include? "/get-status"
|
28
|
+
data = response
|
29
|
+
elsif path.include? "/sync-charge"
|
30
|
+
data = response.parsed_response
|
31
|
+
data = parse_data(response.parsed_response)
|
32
|
+
else
|
33
|
+
if response.code.to_s.start_with?('2') or response.code.to_s.start_with?('4')
|
34
|
+
data = response.parsed_response
|
35
|
+
data = parse_data(response.parsed_response)
|
36
|
+
else
|
37
|
+
data = response_from_failsafe(path,options)
|
38
|
+
end
|
39
|
+
end
|
30
40
|
end
|
31
41
|
|
32
42
|
def parse_data(original_data)
|
@@ -36,7 +46,31 @@ module AppManager
|
|
36
46
|
else
|
37
47
|
return original_data
|
38
48
|
end
|
49
|
+
end
|
39
50
|
|
51
|
+
def response_from_failsafe(path,options)
|
52
|
+
params = Rack::Utils.parse_query URI(path).query
|
53
|
+
path = path.delete_prefix('/')
|
54
|
+
path = path[/[^?]+/]
|
55
|
+
@fs = AppManager::FailSafe.new
|
56
|
+
case path
|
57
|
+
when "static-contents"
|
58
|
+
return @fs.get_local_app_structures
|
59
|
+
when 'plans'
|
60
|
+
return @fs.get_local_plans
|
61
|
+
when 'plan'
|
62
|
+
return @fs.get_local_plan(params)
|
63
|
+
when 'store-charge'
|
64
|
+
return @fs.store_local_charge(params,options)
|
65
|
+
when 'cancel-charge'
|
66
|
+
return @fs.store_cancel_charge(params,options)
|
67
|
+
when 'get-remaining-days'
|
68
|
+
return @fs.get_local_remaining_days(params,options)
|
69
|
+
when 'get-charge'
|
70
|
+
return @fs.get_local_charge(params,options)
|
71
|
+
else
|
72
|
+
return nil
|
73
|
+
end
|
40
74
|
end
|
41
75
|
|
42
76
|
end
|