zuora_connect_oauth_alpha 2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/Rakefile +38 -0
- data/app/assets/javascripts/zuora_connect/api/v1/app_instance.js +2 -0
- data/app/assets/javascripts/zuora_connect/application.js +13 -0
- data/app/assets/stylesheets/zuora_connect/api/v1/app_instance.css +4 -0
- data/app/assets/stylesheets/zuora_connect/application.css +15 -0
- data/app/controllers/zuora_connect/admin/tenant_controller.rb +11 -0
- data/app/controllers/zuora_connect/api/v1/app_instance_controller.rb +37 -0
- data/app/controllers/zuora_connect/application_controller.rb +8 -0
- data/app/controllers/zuora_connect/static_controller.rb +26 -0
- data/app/helpers/zuora_connect/api/v1/app_instance_helper.rb +4 -0
- data/app/helpers/zuora_connect/application_helper.rb +5 -0
- data/app/models/zuora_connect/app_instance.rb +5 -0
- data/app/models/zuora_connect/app_instance_base.rb +734 -0
- data/app/models/zuora_connect/login.rb +37 -0
- data/app/views/layouts/zuora_connect/application.html.erb +14 -0
- data/app/views/sql/refresh_aggregate_table.txt +84 -0
- data/app/views/zuora_connect/static/invalid_app_instance_error.html.erb +65 -0
- data/app/views/zuora_connect/static/session_error.html.erb +63 -0
- data/config/initializers/apartment.rb +95 -0
- data/config/initializers/object_method_hooks.rb +27 -0
- data/config/initializers/redis.rb +10 -0
- data/config/initializers/resque.rb +5 -0
- data/config/initializers/to_bool.rb +24 -0
- data/config/routes.rb +12 -0
- data/db/migrate/20100718151733_create_connect_app_instances.rb +9 -0
- data/db/migrate/20101024162319_add_tokens_to_app_instance.rb +6 -0
- data/db/migrate/20101024220705_add_token_to_app_instance.rb +5 -0
- data/db/migrate/20110131211919_add_sessions_table.rb +13 -0
- data/db/migrate/20110411200303_add_expiration_to_app_instance.rb +5 -0
- data/db/migrate/20110413191512_add_new_api_token.rb +5 -0
- data/db/migrate/20110503003602_add_catalog_data_to_app_instance.rb +6 -0
- data/db/migrate/20110503003603_add_catalog_mappings_to_app_instance.rb +5 -0
- data/db/migrate/20110503003604_catalog_default.rb +5 -0
- data/db/migrate/20180301052853_add_catalog_attempted_at.rb +5 -0
- data/lib/resque/additions.rb +53 -0
- data/lib/resque/dynamic_queues.rb +131 -0
- data/lib/resque/self_lookup.rb +19 -0
- data/lib/tasks/zuora_connect_tasks.rake +24 -0
- data/lib/zuora_connect_oauth_alpha.rb +38 -0
- data/lib/zuora_connect_oauth_alpha/configuration.rb +40 -0
- data/lib/zuora_connect_oauth_alpha/controllers/helpers.rb +165 -0
- data/lib/zuora_connect_oauth_alpha/engine.rb +30 -0
- data/lib/zuora_connect_oauth_alpha/exceptions.rb +50 -0
- data/lib/zuora_connect_oauth_alpha/railtie.rb +35 -0
- data/lib/zuora_connect_oauth_alpha/version.rb +4 -0
- data/lib/zuora_connect_oauth_alpha/views/helpers.rb +9 -0
- data/test/controllers/zuora_connect/api/v1/app_instance_controller_test.rb +13 -0
- data/test/dummy/README.rdoc +28 -0
- data/test/dummy/Rakefile +6 -0
- data/test/dummy/app/assets/javascripts/application.js +13 -0
- data/test/dummy/app/assets/stylesheets/application.css +15 -0
- data/test/dummy/app/controllers/application_controller.rb +5 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/views/layouts/application.html.erb +14 -0
- data/test/dummy/bin/bundle +3 -0
- data/test/dummy/bin/rails +4 -0
- data/test/dummy/bin/rake +4 -0
- data/test/dummy/bin/setup +29 -0
- data/test/dummy/config.ru +4 -0
- data/test/dummy/config/application.rb +26 -0
- data/test/dummy/config/boot.rb +5 -0
- data/test/dummy/config/database.yml +25 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +41 -0
- data/test/dummy/config/environments/production.rb +79 -0
- data/test/dummy/config/environments/test.rb +42 -0
- data/test/dummy/config/initializers/assets.rb +11 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
- data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/test/dummy/config/initializers/inflections.rb +16 -0
- data/test/dummy/config/initializers/mime_types.rb +4 -0
- data/test/dummy/config/initializers/session_store.rb +3 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/test/dummy/config/locales/en.yml +23 -0
- data/test/dummy/config/routes.rb +4 -0
- data/test/dummy/config/secrets.yml +22 -0
- data/test/dummy/public/404.html +67 -0
- data/test/dummy/public/422.html +67 -0
- data/test/dummy/public/500.html +66 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/fixtures/zuora_connect/app_instances.yml +11 -0
- data/test/integration/navigation_test.rb +8 -0
- data/test/lib/generators/zuora_connect/datatable_generator_test.rb +16 -0
- data/test/models/zuora_connect/app_instance_test.rb +9 -0
- data/test/test_helper.rb +21 -0
- data/test/zuora_connect_test.rb +7 -0
- metadata +361 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 2690030bcc5cae84498de1732142c9290aa988ae2ab862a901a387cb9aa0cb71
|
4
|
+
data.tar.gz: 1328b2fc3ae1d0b61f5d3bbc0abac9cb67adb5ea4f88839c0fd9a53920fd8cae
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d5b3f010c31d92e4f0e7ee2e208ca31b2f5b103de53e53a6364afb7602dc6d0894b551138a9cf729aa95ac78009315e0b1ff07fd286df5885763f722c9610db7
|
7
|
+
data.tar.gz: b0a2dc9acb401029083b2f69303958754c04c922366163b001a4e98a9ef05ee836e8bc3fc5c2684a5b0efdde30f0b0b2a43604876b87af44b4ebbdcf40d7804e
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2016 Matthew Ingle
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
begin
|
2
|
+
require 'bundler/setup'
|
3
|
+
rescue LoadError
|
4
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'rdoc/task'
|
8
|
+
require 'apartment'
|
9
|
+
Apartment.db_migrate_tenants = false
|
10
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
11
|
+
rdoc.rdoc_dir = 'rdoc'
|
12
|
+
rdoc.title = 'Connect'
|
13
|
+
rdoc.options << '--line-numbers'
|
14
|
+
rdoc.rdoc_files.include('README.rdoc')
|
15
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
16
|
+
end
|
17
|
+
|
18
|
+
APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__)
|
19
|
+
load 'rails/tasks/engine.rake'
|
20
|
+
|
21
|
+
|
22
|
+
load 'rails/tasks/statistics.rake'
|
23
|
+
|
24
|
+
|
25
|
+
|
26
|
+
Bundler::GemHelper.install_tasks
|
27
|
+
|
28
|
+
require 'rake/testtask'
|
29
|
+
|
30
|
+
Rake::TestTask.new(:test) do |t|
|
31
|
+
t.libs << 'lib'
|
32
|
+
t.libs << 'test'
|
33
|
+
t.pattern = 'test/**/*_test.rb'
|
34
|
+
t.verbose = false
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
task default: :test
|
@@ -0,0 +1,13 @@
|
|
1
|
+
// This is a manifest file that'll be compiled into application.js, which will include all the files
|
2
|
+
// listed below.
|
3
|
+
//
|
4
|
+
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
|
5
|
+
// or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
|
6
|
+
//
|
7
|
+
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
|
8
|
+
// compiled file.
|
9
|
+
//
|
10
|
+
// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
|
11
|
+
// about supported directives.
|
12
|
+
//
|
13
|
+
//= require_tree .
|
@@ -0,0 +1,15 @@
|
|
1
|
+
/*
|
2
|
+
* This is a manifest file that'll be compiled into application.css, which will include all the files
|
3
|
+
* listed below.
|
4
|
+
*
|
5
|
+
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
|
6
|
+
* or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
|
7
|
+
*
|
8
|
+
* You're free to add application-wide styles to this file and they'll appear at the bottom of the
|
9
|
+
* compiled file so the styles you add here take precedence over styles defined in any styles
|
10
|
+
* defined in the other CSS/SCSS files in this directory. It is generally better to create a new
|
11
|
+
* file per style scope.
|
12
|
+
*
|
13
|
+
*= require_tree .
|
14
|
+
*= require_self
|
15
|
+
*/
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require_dependency "zuora_connect/application_controller"
|
2
|
+
|
3
|
+
module ZuoraConnect
|
4
|
+
class Api::V1::AppInstanceController < ApplicationController
|
5
|
+
|
6
|
+
def create
|
7
|
+
Apartment::Tenant.create(session['AppInstance'])
|
8
|
+
respond_to do |format|
|
9
|
+
format.json {render :json => "Created"}
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def drop
|
14
|
+
instance_id = @appinstance.id
|
15
|
+
if session["#{instance_id}::destroy"] && ZuoraConnect::AppInstance.where(:id => instance_id).size != 0
|
16
|
+
ZuoraConnect::AppInstance.destroy(instance_id)
|
17
|
+
msg = Apartment::Tenant.drop(instance_id)
|
18
|
+
respond_to do |format|
|
19
|
+
message = Hash.new
|
20
|
+
message = {"error" => {:message => msg.error_message}} if msg.error_message != ""
|
21
|
+
message["message"] = msg.result_status == 1 ? "success" : "error"
|
22
|
+
format.json {render :json => message}
|
23
|
+
end
|
24
|
+
else
|
25
|
+
respond_to do |format|
|
26
|
+
format.json {render :json => { "message" => "Unauthorized"}}
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def status
|
32
|
+
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module ZuoraConnect
|
2
|
+
class StaticController < ApplicationController
|
3
|
+
before_filter :authenticate_connect_app_request, :except => [:health, :session_error, :invalid_app_instance_error]
|
4
|
+
after_filter :persist_connect_app_session, :except => [:health, :session_error, :invalid_app_instance_error]
|
5
|
+
def session_error
|
6
|
+
respond_to do |format|
|
7
|
+
format.html
|
8
|
+
format.json { render json: { message: "Session Error", status: 500 }, status: 500 }
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def invalid_app_instance_error
|
13
|
+
respond_to do |format|
|
14
|
+
format.html
|
15
|
+
format.json {render json: { message: "Invalid App Instance", status: 500 }, status: 500 }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def health
|
20
|
+
render json: {
|
21
|
+
message: "Alive",
|
22
|
+
status: 200
|
23
|
+
}, status: 200
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,734 @@
|
|
1
|
+
module ZuoraConnect
|
2
|
+
class AppInstanceBase < ActiveRecord::Base
|
3
|
+
default_scope {select(ZuoraConnect::AppInstance.column_names.delete_if {|x| ["catalog_mapping", "catalog"].include?(x) }) }
|
4
|
+
after_initialize :init
|
5
|
+
self.table_name = "zuora_connect_app_instances"
|
6
|
+
attr_accessor :options, :mode, :logins, :task_data, :last_refresh, :username, :password, :s3_client, :api_version
|
7
|
+
|
8
|
+
REFRESH_TIMEOUT = 2.minute #Used to determine how long to wait on current refresh call before executing another
|
9
|
+
INSTANCE_REFRESH_WINDOW = 30.minutes #Used to set how how long till app starts attempting to refresh cached task connect data
|
10
|
+
INSTANCE_REDIS_CACHE_PERIOD = 60.minutes #Used to determine how long to cached task data will live for
|
11
|
+
API_LIMIT_TIMEOUT = 2.minutes #Used to set the default for expiring timeout when api rate limiting is in effect
|
12
|
+
BLANK_OBJECT_ID_LOOKUP = 'BlankValueSupplied'
|
13
|
+
|
14
|
+
def init
|
15
|
+
@options = Hash.new
|
16
|
+
@logins = Hash.new
|
17
|
+
@api_version = "v2"
|
18
|
+
self.attr_builder("timezone", ZuoraConnect.configuration.default_time_zone)
|
19
|
+
self.attr_builder("locale", ZuoraConnect.configuration.default_locale)
|
20
|
+
PaperTrail.whodunnit = "Backend" if defined?(PaperTrail)
|
21
|
+
if INSTANCE_REFRESH_WINDOW > INSTANCE_REDIS_CACHE_PERIOD
|
22
|
+
raise "The instance refresh window cannot be greater than the instance cache period"
|
23
|
+
end
|
24
|
+
self.apartment_switch(nil, true)
|
25
|
+
end
|
26
|
+
|
27
|
+
def apartment_switch(method = nil, migrate = false)
|
28
|
+
begin
|
29
|
+
Apartment::Tenant.switch!(self.id) if self.persisted?
|
30
|
+
rescue Apartment::TenantNotFound => ex
|
31
|
+
Apartment::Tenant.create(self.id.to_s)
|
32
|
+
retry
|
33
|
+
end
|
34
|
+
if migrate && ActiveRecord::Migrator.needs_migration?
|
35
|
+
Apartment::Migrator.migrate(self.id)
|
36
|
+
end
|
37
|
+
Thread.current[:appinstance] = self
|
38
|
+
end
|
39
|
+
|
40
|
+
def new_session(session: self.data_lookup, username: self.access_token, password: self.refresh_token, holding_pattern: false)
|
41
|
+
@api_version = "v2"
|
42
|
+
@username = username
|
43
|
+
@password = password
|
44
|
+
@last_refresh = session["#{self.id}::last_refresh"]
|
45
|
+
|
46
|
+
## DEV MODE TASK DATA MOCKUP
|
47
|
+
if ZuoraConnect.configuration.mode != "Production"
|
48
|
+
mock_task_data = {
|
49
|
+
"mode" => ZuoraConnect.configuration.dev_mode_mode
|
50
|
+
}
|
51
|
+
|
52
|
+
case ZuoraConnect.configuration.dev_mode_options.class
|
53
|
+
when Hash
|
54
|
+
@options = ZuoraConnect.configuration.dev_mode_options
|
55
|
+
when Array
|
56
|
+
mock_task_data["options"] = ZuoraConnect.configuration.dev_mode_options
|
57
|
+
end
|
58
|
+
|
59
|
+
ZuoraConnect.configuration.dev_mode_logins.each do |k,v|
|
60
|
+
v = v.merge({"entities": [] }) if !v.keys.include?("entities")
|
61
|
+
mock_task_data[k] = v
|
62
|
+
end
|
63
|
+
|
64
|
+
build_task(mock_task_data, session)
|
65
|
+
else
|
66
|
+
time_expire = (session["#{self.id}::last_refresh"] || Time.now).to_i - INSTANCE_REFRESH_WINDOW.ago.to_i
|
67
|
+
|
68
|
+
if session.empty?
|
69
|
+
Rails.logger.info("[#{self.id}] REFRESHING - Session Empty")
|
70
|
+
raise ZuoraConnect::Exceptions::HoldingPattern if holding_pattern && !self.mark_for_refresh
|
71
|
+
self.refresh(session)
|
72
|
+
|
73
|
+
elsif (self.id != session["appInstance"].to_i)
|
74
|
+
Rails.logger.info("[#{self.id}] REFRESHING - AppInstance ID(#{self.id}) does not match session id(#{session["appInstance"].to_i})")
|
75
|
+
raise ZuoraConnect::Exceptions::HoldingPattern if holding_pattern && !self.mark_for_refresh
|
76
|
+
self.refresh(session)
|
77
|
+
|
78
|
+
elsif session["#{self.id}::task_data"].blank?
|
79
|
+
Rails.logger.info("[#{self.id}] REFRESHING - Task Data Blank")
|
80
|
+
raise ZuoraConnect::Exceptions::HoldingPattern if holding_pattern && !self.mark_for_refresh
|
81
|
+
self.refresh(session)
|
82
|
+
|
83
|
+
elsif session["#{self.id}::last_refresh"].blank?
|
84
|
+
Rails.logger.info("[#{self.id}] REFRESHING - No Time on Cookie")
|
85
|
+
raise ZuoraConnect::Exceptions::HoldingPattern if holding_pattern && !self.mark_for_refresh
|
86
|
+
self.refresh(session)
|
87
|
+
|
88
|
+
# If the cache is expired and we can aquire a refresh lock
|
89
|
+
elsif (session["#{self.id}::last_refresh"].to_i < INSTANCE_REFRESH_WINDOW.ago.to_i) && self.mark_for_refresh
|
90
|
+
Rails.logger.info("[#{self.id}] REFRESHING - Session Old by #{time_expire.abs} second")
|
91
|
+
self.refresh(session)
|
92
|
+
else
|
93
|
+
if time_expire < 0
|
94
|
+
Rails.logger.info(["[#{self.id}] REBUILDING - Expired by #{time_expire} seconds", self.marked_for_refresh? ? " cache updating as of #{self.reset_mark_refreshed_at} seconds ago" : nil].compact.join(','))
|
95
|
+
else
|
96
|
+
Rails.logger.info("[#{self.id}] REBUILDING - Expires in #{time_expire} seconds")
|
97
|
+
end
|
98
|
+
build_task(session["#{self.id}::task_data"], session)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
begin
|
102
|
+
I18n.locale = self.locale
|
103
|
+
rescue I18n::InvalidLocale => ex
|
104
|
+
Rails.logger.error("Invalid Locale: #{ex.message}")
|
105
|
+
end
|
106
|
+
Time.zone = self.timezone
|
107
|
+
return self
|
108
|
+
rescue ZuoraConnect::Exceptions::HoldingPattern => ex
|
109
|
+
while self.marked_for_refresh?
|
110
|
+
Rails.logger.info("[#{self.id}] Holding - Expires in #{self.reset_mark_expires_at}")
|
111
|
+
sleep(5)
|
112
|
+
end
|
113
|
+
self.reload_attributes([:refresh_token, :oauth_expires_at, :access_token])
|
114
|
+
session = self.data_lookup(session: session)
|
115
|
+
retry
|
116
|
+
end
|
117
|
+
|
118
|
+
def refresh(session = nil)
|
119
|
+
refresh_count ||= 0
|
120
|
+
|
121
|
+
start = Time.now
|
122
|
+
response = HTTParty.get(ZuoraConnect.configuration.url + "/api/#{self.api_version}/tools/tasks/#{self.id}.json",:body => {:access_token => self.access_token})
|
123
|
+
response_time = Time.now - start
|
124
|
+
|
125
|
+
Rails.logger.info("[#{self.id}] REFRESH TASK - Connect Task Info Request Time #{response_time.round(2).to_s}")
|
126
|
+
if response.code == 200
|
127
|
+
build_task(JSON.parse(response.body), session)
|
128
|
+
@last_refresh = Time.now.to_i
|
129
|
+
self.cache_app_instance
|
130
|
+
self.reset_mark_for_refresh
|
131
|
+
else
|
132
|
+
Rails.logger.fatal("[#{self.id}] REFRESH TASK - Failed Code #{response.code}")
|
133
|
+
raise ZuoraConnect::Exceptions::ConnectCommunicationError.new("Error Communicating with Connect", response.body, response.code)
|
134
|
+
end
|
135
|
+
rescue Net::ReadTimeout, Net::OpenTimeout, Errno::EPIPE, Errno::ECONNRESET, Errno::ECONNREFUSED, SocketError => ex
|
136
|
+
if (refresh_count += 1) < 3
|
137
|
+
Rails.logger.info("[#{self.id}] REFRESH TASK - #{ex.class} Retrying(#{refresh_count})")
|
138
|
+
retry
|
139
|
+
else
|
140
|
+
Rails.logger.fatal("[#{self.id}] REFRESH TASK - #{ex.class} Failed #{refresh_count}x")
|
141
|
+
raise
|
142
|
+
end
|
143
|
+
rescue ZuoraConnect::Exceptions::ConnectCommunicationError => ex
|
144
|
+
if (refresh_count += 1) < 3
|
145
|
+
Rails.logger.info("[#{self.id}] REFRESH TASK - Failed Retrying(#{refresh_count})")
|
146
|
+
if ex.code == 401
|
147
|
+
self.refresh_oauth
|
148
|
+
end
|
149
|
+
retry
|
150
|
+
else
|
151
|
+
Rails.logger.fatal("[#{self.id}] REFRESH TASK - Failed #{refresh_count}x")
|
152
|
+
raise
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
#### START Task Mathods ####
|
157
|
+
def build_task(task_data, session)
|
158
|
+
@task_data = task_data
|
159
|
+
@mode = @task_data["mode"]
|
160
|
+
@task_data.each do |k,v|
|
161
|
+
if k.match(/^(.*)_login$/)
|
162
|
+
tmp = ZuoraConnect::Login.new(v)
|
163
|
+
if session.present? && v["tenant_type"] == "Zuora"
|
164
|
+
if tmp.entities.size > 0
|
165
|
+
tmp.entities.each do |value|
|
166
|
+
entity_id = value["id"]
|
167
|
+
tmp.client(entity_id).z_session_token = session["#{self.id}::#{k}::#{entity_id}:z_session_token"] if session["#{self.id}::#{k}::#{entity_id}:z_session_token"]
|
168
|
+
tmp.client(entity_id).bearer_token = session["#{self.id}::#{k}::#{entity_id}:bearer_token"] if session["#{self.id}::#{k}::#{entity_id}:bearer_token"]
|
169
|
+
tmp.client(entity_id).oauth_session_expires_at = session["#{self.id}::#{k}::#{entity_id}:oauth_session_expires_at"] if session["#{self.id}::#{k}::#{entity_id}:oauth_session_expires_at"]
|
170
|
+
end
|
171
|
+
else
|
172
|
+
tmp.client.z_session_token = session["#{self.id}::#{k}:z_session_token"] if session["#{self.id}::#{k}:z_session_token"]
|
173
|
+
tmp.client.bearer_token = session["#{self.id}::#{k}:bearer_token"] if session["#{self.id}::#{k}:bearer_token"]
|
174
|
+
tmp.client.oauth_session_expires_at = session["#{self.id}::#{k}:oauth_session_expires_at"] if session["#{self.id}::#{k}:oauth_session_expires_at"]
|
175
|
+
end
|
176
|
+
end
|
177
|
+
@logins[k] = tmp
|
178
|
+
self.attr_builder(k, @logins[k])
|
179
|
+
elsif k == "options"
|
180
|
+
v.each do |opt|
|
181
|
+
@options[opt["config_name"]] = opt
|
182
|
+
end
|
183
|
+
elsif k == "user_settings"
|
184
|
+
self.timezone = v["timezone"]
|
185
|
+
self.locale = v["local"]
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
def updateOption(optionId, value)
|
191
|
+
return HTTParty.get(ZuoraConnect.configuration.url + "/api/#{self.api_version}/tools/application_options/#{optionId}/edit?value=#{value}",:body => {:access_token => self.username})
|
192
|
+
end
|
193
|
+
|
194
|
+
#This can update an existing login, add a new login, change to another existing login
|
195
|
+
#EXAMPLE: {"name": "ftp_login_14","username": "ftplogin7","tenant_type": "Custom","password": "test2","url": "www.ftp.com","custom_data": { "path": "/var/usr/test"}}
|
196
|
+
def update_logins(options)
|
197
|
+
update_login_count ||= 0
|
198
|
+
|
199
|
+
response = HTTParty.post(ZuoraConnect.configuration.url + "/api/#{self.api_version}/tools/tasks/#{self.id}/logins",:body => {:access_token => self.username}.merge(options))
|
200
|
+
if response.code == 200
|
201
|
+
return JSON.parse(response.body)
|
202
|
+
else
|
203
|
+
raise ZuoraConnect::Exceptions::ConnectCommunicationError.new("Error Communicating with Connect", response.body, response.code)
|
204
|
+
end
|
205
|
+
rescue Net::ReadTimeout, Net::OpenTimeout, Errno::EPIPE, Errno::ECONNRESET, Errno::ECONNREFUSED, SocketError
|
206
|
+
if (update_login_count += 1) < 3
|
207
|
+
retry
|
208
|
+
else
|
209
|
+
raise
|
210
|
+
end
|
211
|
+
rescue ZuoraConnect::Exceptions::ConnectCommunicationError => ex
|
212
|
+
if (update_login_count += 1) < 3
|
213
|
+
if ex.code == 401
|
214
|
+
self.refresh_oauth
|
215
|
+
end
|
216
|
+
retry
|
217
|
+
else
|
218
|
+
raise
|
219
|
+
end
|
220
|
+
end
|
221
|
+
#### END Task Mathods ####
|
222
|
+
|
223
|
+
#### START Connect OAUTH methods ####
|
224
|
+
def check_oauth_state(method)
|
225
|
+
#Refresh token if already expired
|
226
|
+
if self.oauth_expired?
|
227
|
+
Rails.logger.debug("[#{self.id}] Before '#{method}' method, Oauth expired")
|
228
|
+
self.refresh_oauth
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
def oauth_expired?
|
233
|
+
(self.oauth_expires_at < Time.now)
|
234
|
+
end
|
235
|
+
|
236
|
+
def refresh_oauth
|
237
|
+
refresh_oauth_count ||= 0
|
238
|
+
|
239
|
+
start = Time.now
|
240
|
+
params = {
|
241
|
+
:grant_type => "refresh_token",
|
242
|
+
:redirect_uri => ZuoraConnect.configuration.oauth_client_redirect_uri,
|
243
|
+
:refresh_token => self.refresh_token
|
244
|
+
}
|
245
|
+
response = HTTParty.post("#{ZuoraConnect.configuration.url}/oauth/token",:body => params)
|
246
|
+
response_time = Time.now - start
|
247
|
+
Rails.logger.info("[#{self.id}] REFRESH OAUTH - In #{response_time.round(2).to_s}")
|
248
|
+
|
249
|
+
if response.code == 200
|
250
|
+
response_body = JSON.parse(response.body)
|
251
|
+
|
252
|
+
self.refresh_token = response_body["refresh_token"]
|
253
|
+
self.access_token = response_body["access_token"]
|
254
|
+
self.oauth_expires_at = Time.at(response_body["created_at"].to_i) + response_body["expires_in"].seconds
|
255
|
+
self.save(:validate => false)
|
256
|
+
else
|
257
|
+
Rails.logger.fatal("[#{self.id}] REFRESH OAUTH - Failed Code #{response.code}")
|
258
|
+
raise ZuoraConnect::Exceptions::ConnectCommunicationError.new("Error Refreshing Access Token", response.body, response.code)
|
259
|
+
end
|
260
|
+
rescue Net::ReadTimeout, Net::OpenTimeout, Errno::EPIPE, Errno::ECONNRESET, Errno::ECONNREFUSED, SocketError => ex
|
261
|
+
if (refresh_oauth_count += 1) < 3
|
262
|
+
Rails.logger.info("[#{self.id}] REFRESH OAUTH - #{ex.class} Retrying(#{refresh_oauth_count})")
|
263
|
+
retry
|
264
|
+
else
|
265
|
+
Rails.logger.fatal("[#{self.id}] REFRESH OAUTH - #{ex.class} Failed #{refresh_oauth_count}x")
|
266
|
+
raise
|
267
|
+
end
|
268
|
+
rescue ZuoraConnect::Exceptions::ConnectCommunicationError => ex
|
269
|
+
sleep(5)
|
270
|
+
self.reload_attributes([:refresh_token, :oauth_expires_at, :access_token]) #Reload only the refresh token for retry
|
271
|
+
|
272
|
+
#After reload, if nolonger expired return
|
273
|
+
return if !self.oauth_expired?
|
274
|
+
|
275
|
+
if (refresh_oauth_count += 1) < 3
|
276
|
+
Rails.logger.info("[#{self.id}] REFRESH OAUTH - Failed Retrying(#{refresh_oauth_count})")
|
277
|
+
retry
|
278
|
+
else
|
279
|
+
Rails.logger.fatal("[#{self.id}] REFRESH OAUTH - Failed #{refresh_oauth_count}x")
|
280
|
+
raise
|
281
|
+
end
|
282
|
+
end
|
283
|
+
#### END Connect OAUTH methods ####
|
284
|
+
|
285
|
+
#### START AppInstance Temporary Persistance Methods ####
|
286
|
+
def marked_for_refresh?
|
287
|
+
return defined?(Redis.current) ? Redis.current.get("AppInstance:#{self.id}:Refreshing").to_bool : false
|
288
|
+
end
|
289
|
+
|
290
|
+
def reset_mark_for_refresh
|
291
|
+
Redis.current.del("AppInstance:#{self.id}:Refreshing") if defined?(Redis.current)
|
292
|
+
end
|
293
|
+
|
294
|
+
def reset_mark_refreshed_at
|
295
|
+
return defined?(Redis.current) ? REFRESH_TIMEOUT.to_i - Redis.current.ttl("AppInstance:#{self.id}:Refreshing") : 0
|
296
|
+
end
|
297
|
+
|
298
|
+
def reset_mark_expires_at
|
299
|
+
return defined?(Redis.current) ? Redis.current.ttl("AppInstance:#{self.id}:Refreshing") : 0
|
300
|
+
end
|
301
|
+
|
302
|
+
def mark_for_refresh
|
303
|
+
return defined?(Redis.current) ? Redis.current.set("AppInstance:#{self.id}:Refreshing", true, {:nx => true, :ex => REFRESH_TIMEOUT.to_i}) : true
|
304
|
+
end
|
305
|
+
|
306
|
+
def data_lookup(session: {})
|
307
|
+
if defined?(PaperTrail)
|
308
|
+
PaperTrail.whodunnit = session["#{self.id}::user::email"].present? ? session["#{self.id}::user::email"] : nil if session.present?
|
309
|
+
end
|
310
|
+
if defined?(Redis.current)
|
311
|
+
cached_instance = Redis.current.get("AppInstance:#{self.id}")
|
312
|
+
if cached_instance.blank?
|
313
|
+
Rails.logger.debug("[#{self.id}] Cached AppInstance Missing")
|
314
|
+
return session
|
315
|
+
else
|
316
|
+
Rails.logger.debug("[#{self.id}] Cached AppInstance Found")
|
317
|
+
return decrypt_data(data: cached_instance, rescue_return: session)
|
318
|
+
end
|
319
|
+
else
|
320
|
+
return session
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
def cache_app_instance
|
325
|
+
if defined?(Redis.current)
|
326
|
+
#Task data must be present and the last refresh cannot be old. We dont want to overwite new cache data with old
|
327
|
+
if self.task_data.present? && (self.last_refresh.to_i > INSTANCE_REFRESH_WINDOW.ago.to_i)
|
328
|
+
Rails.logger.info("[#{self.id}] Caching AppInstance")
|
329
|
+
Redis.current.setex("AppInstance:#{self.id}", INSTANCE_REDIS_CACHE_PERIOD.to_i, encrypt_data(data: self.save_data))
|
330
|
+
end
|
331
|
+
Redis.current.del("Deleted:#{self.id}")
|
332
|
+
end
|
333
|
+
end
|
334
|
+
|
335
|
+
def save_data(session = Hash.new)
|
336
|
+
self.logins.each do |key, login|
|
337
|
+
if login.tenant_type == "Zuora"
|
338
|
+
if login.available_entities.size > 1 && Rails.application.config.session_store != ActionDispatch::Session::CookieStore
|
339
|
+
login.available_entities.each do |entity_key|
|
340
|
+
session["#{self.id}::#{key}::#{entity_key}:z_session_token"] = login.client(entity_key).z_session_token if login.client.respond_to?(:z_session_token)
|
341
|
+
session["#{self.id}::#{key}::#{entity_key}:bearer_token"] = login.client(entity_key).bearer_token if login.client.respond_to?(:bearer_token)
|
342
|
+
session["#{self.id}::#{key}::#{entity_key}:oauth_session_expires_at"] = login.client(entity_key).oauth_session_expires_at if login.client.respond_to?(:oauth_session_expires_at)
|
343
|
+
end
|
344
|
+
else
|
345
|
+
session["#{self.id}::#{key}:z_session_token"] = login.client.z_session_token if login.client.respond_to?(:z_session_token)
|
346
|
+
session["#{self.id}::#{key}:bearer_token"] = login.client.bearer_token if login.client.respond_to?(:bearer_token)
|
347
|
+
session["#{self.id}::#{key}:oauth_session_expires_at"] = login.client.oauth_session_expires_at if login.client.respond_to?(:oauth_session_expires_at)
|
348
|
+
end
|
349
|
+
end
|
350
|
+
end
|
351
|
+
session["#{self.id}::task_data"] = self.task_data
|
352
|
+
session["#{self.id}::last_refresh"] = self.last_refresh
|
353
|
+
session["appInstance"] = self.id
|
354
|
+
return session
|
355
|
+
end
|
356
|
+
|
357
|
+
def encryptor
|
358
|
+
# Default values for Rails 4 apps
|
359
|
+
key_iter_num, key_size, salt, signed_salt = [1000, 64, "encrypted cookie", "signed encrypted cookie"]
|
360
|
+
key_generator = ActiveSupport::KeyGenerator.new(Rails.application.secrets.secret_key_base, iterations: key_iter_num)
|
361
|
+
secret, sign_secret = [key_generator.generate_key(salt), key_generator.generate_key(signed_salt)]
|
362
|
+
return ActiveSupport::MessageEncryptor.new(secret, sign_secret)
|
363
|
+
end
|
364
|
+
|
365
|
+
def decrypt_data(data: nil, rescue_return: nil)
|
366
|
+
return data if data.blank?
|
367
|
+
begin
|
368
|
+
if Rails.env == 'development'
|
369
|
+
return JSON.parse(data)
|
370
|
+
else
|
371
|
+
begin
|
372
|
+
return JSON.parse(encryptor.decrypt_and_verify(CGI::unescape(data)))
|
373
|
+
rescue ActiveSupport::MessageVerifier::InvalidSignature => ex
|
374
|
+
Rails.logger.fatal('Error Decrypting')
|
375
|
+
return rescue_return
|
376
|
+
end
|
377
|
+
end
|
378
|
+
rescue JSON::ParserError => ex
|
379
|
+
Rails.logger.fatal('Error Parsing')
|
380
|
+
return rescue_return
|
381
|
+
end
|
382
|
+
end
|
383
|
+
|
384
|
+
def encrypt_data(data: nil)
|
385
|
+
return data if data.blank?
|
386
|
+
|
387
|
+
if Rails.env == 'development'
|
388
|
+
return data.to_json
|
389
|
+
else
|
390
|
+
return encryptor.encrypt_and_sign(data.to_json)
|
391
|
+
end
|
392
|
+
end
|
393
|
+
#### END AppInstance Temporary Persistance Methods ####
|
394
|
+
|
395
|
+
### START Resque Helping Methods ####
|
396
|
+
def api_limit(start: true, time: API_LIMIT_TIMEOUT.to_i)
|
397
|
+
if start
|
398
|
+
Redis.current.setex("APILimits:#{self.id}", time, true)
|
399
|
+
else
|
400
|
+
Redis.current.del("APILimits:#{self.id}")
|
401
|
+
end
|
402
|
+
end
|
403
|
+
|
404
|
+
def api_limit?
|
405
|
+
return Redis.current.get("APILimits:#{self.id}").to_bool
|
406
|
+
end
|
407
|
+
|
408
|
+
def queue_paused?
|
409
|
+
return Redis.current.get("resque:PauseQueue:#{self.id}").to_bool
|
410
|
+
end
|
411
|
+
|
412
|
+
def queue_pause(time: nil)
|
413
|
+
if time.present?
|
414
|
+
raise "Time must be fixnum of seconds." if time.class != Fixnum
|
415
|
+
Redis.current.setex("resque:PauseQueue:#{self.id}", time, true)
|
416
|
+
else
|
417
|
+
Redis.current.set("resque:PauseQueue:#{self.id}", true)
|
418
|
+
end
|
419
|
+
end
|
420
|
+
|
421
|
+
def queue_start
|
422
|
+
Redis.current.del("resque:PauseQueue:#{self.id}")
|
423
|
+
end
|
424
|
+
### END Resque Helping Methods ####
|
425
|
+
|
426
|
+
### START Catalog Helping Methods #####
|
427
|
+
def get_catalog(page_size: 5, zuora_login: self.login_lookup(type: "Zuora").first, entity_id: nil)
|
428
|
+
self.update_column(:catalog_update_attempt_at, Time.now.utc)
|
429
|
+
|
430
|
+
entity_reference = entity_id.blank? ? 'Default' : entity_id
|
431
|
+
Rails.logger.info("Fetch Catalog")
|
432
|
+
Rails.logger.info("Zuora Entity: #{entity_id.blank? ? 'default' : entity_id}")
|
433
|
+
|
434
|
+
login = zuora_login.client(entity_reference)
|
435
|
+
|
436
|
+
old_logger = ActiveRecord::Base.logger
|
437
|
+
ActiveRecord::Base.logger = nil
|
438
|
+
ActiveRecord::Base.connection.execute('UPDATE "public"."zuora_connect_app_instances" SET "catalog" = jsonb_set("catalog", \'{tmp}\', \'{}\'), "catalog_mapping" = jsonb_set("catalog_mapping", \'{tmp}\', \'{}\') where "id" = %{id}' % {:id => self.id})
|
439
|
+
|
440
|
+
response = {'nextPage' => login.rest_endpoint("catalog/products?pageSize=#{page_size}")}
|
441
|
+
while !response["nextPage"].blank?
|
442
|
+
url = login.rest_endpoint(response["nextPage"].split('/v1/').last)
|
443
|
+
Rails.logger.debug("Fetch Catalog URL #{url}")
|
444
|
+
output_json, response = login.rest_call(:debug => false, :url => url, :errors => [ZuoraAPI::Exceptions::ZuoraAPISessionError], :timeout_retry => true)
|
445
|
+
Rails.logger.debug("Fetch Catalog Response Code #{response.code}")
|
446
|
+
|
447
|
+
if !output_json['success'] =~ (/(true|t|yes|y|1)$/i) || output_json['success'].class != TrueClass
|
448
|
+
Rails.logger.error("Fetch Catalog DATA #{output_json.to_json}")
|
449
|
+
raise ZuoraAPI::Exceptions::ZuoraAPIError.new("Error Getting Catalog: #{output_json}")
|
450
|
+
end
|
451
|
+
|
452
|
+
output_json["products"].each do |product|
|
453
|
+
ActiveRecord::Base.connection.execute('UPDATE "public"."zuora_connect_app_instances" SET "catalog_mapping" = jsonb_set("catalog_mapping", \'{tmp, %s}\', \'%s\') where "id" = %s' % [product["id"], {"productId" => product["id"]}.to_json.gsub("'", "''"), self.id])
|
454
|
+
rateplans = {}
|
455
|
+
|
456
|
+
product["productRatePlans"].each do |rateplan|
|
457
|
+
ActiveRecord::Base.connection.execute('UPDATE "public"."zuora_connect_app_instances" SET "catalog_mapping" = jsonb_set("catalog_mapping", \'{tmp, %s}\', \'%s\') where "id" = %s' % [rateplan["id"], {"productId" => product["id"], "productRatePlanId" => rateplan["id"]}.to_json.gsub("'", "''"), self.id])
|
458
|
+
charges = {}
|
459
|
+
|
460
|
+
rateplan["productRatePlanCharges"].each do |charge|
|
461
|
+
ActiveRecord::Base.connection.execute('UPDATE "public"."zuora_connect_app_instances" SET "catalog_mapping" = jsonb_set("catalog_mapping", \'{tmp, %s}\', \'%s\') where "id" = %s' % [charge["id"], {"productId" => product["id"], "productRatePlanId" => rateplan["id"], "productRatePlanChargeId" => charge["id"]}.to_json.gsub("'", "''"), self.id])
|
462
|
+
|
463
|
+
charges[charge["id"]] = charge.merge({"productId" => product["id"], "productName" => product["name"], "productRatePlanId" => rateplan["id"], "productRatePlanName" => rateplan["name"] })
|
464
|
+
end
|
465
|
+
|
466
|
+
rateplan["productRatePlanCharges"] = charges
|
467
|
+
rateplans[rateplan["id"]] = rateplan.merge({"productId" => product["id"], "productName" => product["name"]})
|
468
|
+
end
|
469
|
+
product["productRatePlans"] = rateplans
|
470
|
+
|
471
|
+
ActiveRecord::Base.connection.execute('UPDATE "public"."zuora_connect_app_instances" SET "catalog" = jsonb_set("catalog", \'{tmp, %s}\', \'%s\') where "id" = %s' % [product["id"], product.to_json.gsub("'", "''"), self.id])
|
472
|
+
end
|
473
|
+
end
|
474
|
+
|
475
|
+
# Move from tmp to actual
|
476
|
+
ActiveRecord::Base.connection.execute('UPDATE "public"."zuora_connect_app_instances" SET "catalog" = jsonb_set("catalog", \'{%{entity}}\', "catalog" #> \'{tmp}\'), "catalog_mapping" = jsonb_set("catalog_mapping", \'{%{entity}}\', "catalog_mapping" #> \'{tmp}\') where "id" = %{id}' % {:entity => entity_reference, :id => self.id})
|
477
|
+
if defined?(Redis.current)
|
478
|
+
Redis.current.keys("Catalog:#{self.id}:*").each do |key|
|
479
|
+
Redis.current.del(key.to_s)
|
480
|
+
end
|
481
|
+
end
|
482
|
+
# Clear tmp holder
|
483
|
+
ActiveRecord::Base.connection.execute('UPDATE "public"."zuora_connect_app_instances" SET "catalog" = jsonb_set("catalog", \'{tmp}\', \'{}\'), "catalog_mapping" = jsonb_set("catalog_mapping", \'{tmp}\', \'{}\') where "id" = %{id}' % {:id => self.id})
|
484
|
+
|
485
|
+
ActiveRecord::Base.logger = old_logger
|
486
|
+
self.update_column(:catalog_updated_at, Time.now.utc)
|
487
|
+
self.touch
|
488
|
+
|
489
|
+
# DO NOT RETURN CATALOG. THIS IS NOT SCALABLE WITH LARGE CATALOGS. USE THE CATALOG_LOOKUP method provided
|
490
|
+
return true
|
491
|
+
end
|
492
|
+
|
493
|
+
def catalog_outdated?(time: Time.now - 12.hours)
|
494
|
+
return self.catalog_updated_at.blank? || (self.catalog_updated_at < time)
|
495
|
+
end
|
496
|
+
|
497
|
+
def catalog_loaded?
|
498
|
+
return ActiveRecord::Base.connection.execute('SELECT id FROM "public"."zuora_connect_app_instances" WHERE "id" = %s AND catalog = \'{}\' LIMIT 1' % [self.id]).first.nil?
|
499
|
+
end
|
500
|
+
|
501
|
+
# Catalog lookup provides method to lookup zuora catalog efficiently.
|
502
|
+
# entity_id: If the using catalog json be field to store multiple entity product catalogs.
|
503
|
+
# object: The Object class desired to be returned. Available [:product, :rateplan, :charge]
|
504
|
+
# object_id: The id or id's of the object/objects to be returned.
|
505
|
+
# child_objects: Whether to include child objects of the object in question.
|
506
|
+
# cache: Store individual "1" object lookup in redis for caching.
|
507
|
+
def catalog_lookup(entity_id: nil, object: :product, object_id: nil, child_objects: false, cache: false)
|
508
|
+
entity_reference = entity_id.blank? ? 'Default' : entity_id
|
509
|
+
|
510
|
+
if object_id.present? && ![Array, String].include?(object_id.class)
|
511
|
+
raise "Object Id can only be a string or an array of strings"
|
512
|
+
end
|
513
|
+
|
514
|
+
if defined?(Redis.current) && object_id.present? && object_id.class == String && object_id.present?
|
515
|
+
stub_catalog = decrypt_data(data: Redis.current.get("Catalog:#{self.id}:#{object_id}:Children:#{child_objects}"))
|
516
|
+
object_hierarchy = decrypt_data(data: Redis.current.get("Catalog:#{self.id}:#{object_id}:Hierarchy"))
|
517
|
+
end
|
518
|
+
|
519
|
+
if defined?(object_hierarchy)
|
520
|
+
object_hierarchy ||= (JSON.parse(ActiveRecord::Base.connection.execute('SELECT catalog_mapping #> \'{%s}\' AS item FROM "public"."zuora_connect_app_instances" WHERE "id" = %s LIMIT 1' % [entity_reference, self.id]).first["item"] || "{}") [object_id] || {"productId" => "SAFTEY", "productRatePlanId" => "SAFTEY", "productRatePlanChargeId" => "SAFTEY"})
|
521
|
+
end
|
522
|
+
|
523
|
+
case object
|
524
|
+
when :product
|
525
|
+
if object_id.nil?
|
526
|
+
string =
|
527
|
+
"SELECT "\
|
528
|
+
"json_object_agg(product_id, product #{child_objects ? '' : '- \'productRatePlans\''}) AS item "\
|
529
|
+
"FROM "\
|
530
|
+
"\"public\".\"zuora_connect_app_instances\", "\
|
531
|
+
"jsonb_each((\"public\".\"zuora_connect_app_instances\".\"catalog\" #> '{%s}' )) AS e(product_id, product) "\
|
532
|
+
"WHERE "\
|
533
|
+
"\"id\" = %s" % [entity_reference, self.id]
|
534
|
+
else
|
535
|
+
if object_id.class == String
|
536
|
+
string =
|
537
|
+
"SELECT "\
|
538
|
+
"(catalog #> '{%s, %s}') #{child_objects ? '' : '- \'productRatePlans\''} AS item "\
|
539
|
+
"FROM "\
|
540
|
+
"\"public\".\"zuora_connect_app_instances\" "\
|
541
|
+
"WHERE "\
|
542
|
+
"\"id\" = %s" % [entity_reference, object_id.blank? ? BLANK_OBJECT_ID_LOOKUP : object_id, self.id]
|
543
|
+
elsif object_id.class == Array
|
544
|
+
string =
|
545
|
+
"SELECT "\
|
546
|
+
"json_object_agg(product_id, product #{child_objects ? '' : '- \'productRatePlans\''}) AS item "\
|
547
|
+
"FROM "\
|
548
|
+
"\"public\".\"zuora_connect_app_instances\", "\
|
549
|
+
"jsonb_each((\"public\".\"zuora_connect_app_instances\".\"catalog\" #> '{%s}' )) AS e(product_id, product) "\
|
550
|
+
"WHERE "\
|
551
|
+
"\"product_id\" IN (\'%s\') AND "\
|
552
|
+
"\"id\" = %s" % [entity_reference, object_id.join("\',\'"), self.id]
|
553
|
+
end
|
554
|
+
end
|
555
|
+
|
556
|
+
when :rateplan
|
557
|
+
if object_id.nil?
|
558
|
+
string =
|
559
|
+
"SELECT "\
|
560
|
+
"json_object_agg(rateplan_id, rateplan #{child_objects ? '' : '- \'productRatePlanCharges\''}) AS item "\
|
561
|
+
"FROM "\
|
562
|
+
"\"public\".\"zuora_connect_app_instances\", "\
|
563
|
+
"jsonb_each((\"public\".\"zuora_connect_app_instances\".\"catalog\" #> '{%s}' )) AS e(product_id, product), "\
|
564
|
+
"jsonb_each(product #> '{productRatePlans}') AS ee(rateplan_id, rateplan) "\
|
565
|
+
"WHERE "\
|
566
|
+
"\"id\" = %s" % [entity_reference, self.id]
|
567
|
+
else
|
568
|
+
if object_id.class == String
|
569
|
+
string =
|
570
|
+
"SELECT "\
|
571
|
+
"(catalog #> '{%s, %s, productRatePlans, %s}') #{child_objects ? '' : '- \'productRatePlanCharges\''} AS item "\
|
572
|
+
"FROM "\
|
573
|
+
"\"public\".\"zuora_connect_app_instances\" "\
|
574
|
+
"WHERE "\
|
575
|
+
"\"id\" = %s" % [entity_reference, object_hierarchy['productId'], object_id.blank? ? BLANK_OBJECT_ID_LOOKUP : object_id, self.id]
|
576
|
+
elsif object_id.class == Array
|
577
|
+
string =
|
578
|
+
"SELECT "\
|
579
|
+
"json_object_agg(rateplan_id, rateplan #{child_objects ? '' : '- \'productRatePlanCharges\''}) AS item "\
|
580
|
+
"FROM "\
|
581
|
+
"\"public\".\"zuora_connect_app_instances\", "\
|
582
|
+
"jsonb_each((\"public\".\"zuora_connect_app_instances\".\"catalog\" #> '{%s}' )) AS e(product_id, product), "\
|
583
|
+
"jsonb_each(product #> '{productRatePlans}') AS ee(rateplan_id, rateplan) "\
|
584
|
+
"WHERE "\
|
585
|
+
"\"rateplan_id\" IN (\'%s\') AND "\
|
586
|
+
"\"id\" = %s" % [entity_reference, object_id.join("\',\'"), self.id]
|
587
|
+
end
|
588
|
+
end
|
589
|
+
|
590
|
+
when :charge
|
591
|
+
if object_id.nil?
|
592
|
+
string =
|
593
|
+
"SELECT "\
|
594
|
+
"json_object_agg(charge_id, charge) as item "\
|
595
|
+
"FROM "\
|
596
|
+
"\"public\".\"zuora_connect_app_instances\", "\
|
597
|
+
"jsonb_each((\"public\".\"zuora_connect_app_instances\".\"catalog\" #> '{%s}' )) AS e(product_id, product), "\
|
598
|
+
"jsonb_each(product #> '{productRatePlans}') AS ee(rateplan_id, rateplan), "\
|
599
|
+
"jsonb_each(rateplan #> '{productRatePlanCharges}') AS eee(charge_id, charge) "\
|
600
|
+
"WHERE "\
|
601
|
+
"\"id\" = %s" % [entity_reference, self.id]
|
602
|
+
else
|
603
|
+
if object_id.class == String
|
604
|
+
string =
|
605
|
+
"SELECT "\
|
606
|
+
"catalog #> '{%s, %s, productRatePlans, %s, productRatePlanCharges, %s}' AS item "\
|
607
|
+
"FROM "\
|
608
|
+
"\"public\".\"zuora_connect_app_instances\" "\
|
609
|
+
"WHERE "\
|
610
|
+
"\"id\" = %s" % [entity_reference, object_hierarchy['productId'], object_hierarchy['productRatePlanId'], object_id.blank? ? BLANK_OBJECT_ID_LOOKUP : object_id , self.id]
|
611
|
+
|
612
|
+
elsif object_id.class == Array
|
613
|
+
string =
|
614
|
+
"SELECT "\
|
615
|
+
"json_object_agg(charge_id, charge) AS item "\
|
616
|
+
"FROM "\
|
617
|
+
"\"public\".\"zuora_connect_app_instances\", "\
|
618
|
+
"jsonb_each((\"public\".\"zuora_connect_app_instances\".\"catalog\" #> '{%s}' )) AS e(product_id, product), "\
|
619
|
+
"jsonb_each(product #> '{productRatePlans}') AS ee(rateplan_id, rateplan), "\
|
620
|
+
"jsonb_each(rateplan #> '{productRatePlanCharges}') AS eee(charge_id, charge) "\
|
621
|
+
"WHERE "\
|
622
|
+
"\"charge_id\" IN (\'%s\') AND "\
|
623
|
+
"\"id\" = %s" % [entity_reference, object_id.join("\',\'"), self.id]
|
624
|
+
end
|
625
|
+
end
|
626
|
+
else
|
627
|
+
raise "Available objects include [:product, :rateplan, :charge]"
|
628
|
+
end
|
629
|
+
|
630
|
+
stub_catalog ||= JSON.parse(ActiveRecord::Base.connection.execute(string).first["item"] || "{}")
|
631
|
+
|
632
|
+
if defined?(Redis.current) && object_id.present? && object_id.class == String && object_id.present?
|
633
|
+
Redis.current.set("Catalog:#{self.id}:#{object_id}:Hierarchy", encrypt_data(data: object_hierarchy))
|
634
|
+
Redis.current.set("Catalog:#{self.id}:#{object_id}:Children:#{child_objects}", encrypt_data(data: stub_catalog)) if cache
|
635
|
+
end
|
636
|
+
return stub_catalog
|
637
|
+
end
|
638
|
+
### END Catalog Helping Methods #####
|
639
|
+
|
640
|
+
### START S3 Helping Methods #####
|
641
|
+
def s3_client
|
642
|
+
require 'aws-sdk-s3'
|
643
|
+
if ZuoraConnect.configuration.mode == "Development"
|
644
|
+
@s3_client ||= Aws::S3::Resource.new(region: ZuoraConnect.configuration.aws_region,access_key_id: ZuoraConnect.configuration.dev_mode_access_key_id,secret_access_key: ZuoraConnect.configuration.dev_mode_secret_access_key)
|
645
|
+
else
|
646
|
+
@s3_client ||= Aws::S3::Resource.new(region: ZuoraConnect.configuration.aws_region)
|
647
|
+
end
|
648
|
+
end
|
649
|
+
|
650
|
+
def upload_to_s3(local_file,s3_path = nil)
|
651
|
+
s3_path = local_file.split("/").last if s3_path.nil?
|
652
|
+
obj = self.s3_client.bucket(ZuoraConnect.configuration.s3_bucket_name).object("#{ZuoraConnect.configuration.s3_folder_name}/#{self.id.to_s}/#{s3_path}}")
|
653
|
+
obj.upload_file(local_file, :server_side_encryption => 'AES256')
|
654
|
+
end
|
655
|
+
|
656
|
+
def get_s3_file_url(key)
|
657
|
+
require 'aws-sdk-s3'
|
658
|
+
signer = Aws::S3::Presigner.new(client: self.s3_client)
|
659
|
+
url = signer.presigned_url(:get_object, bucket: ZuoraConnect.configuration.s3_bucket_name, key: "#{ZuoraConnect.configuration.s3_folder_name}/#{self.id.to_s}/#{key}")
|
660
|
+
end
|
661
|
+
### END S3 Helping Methods #####
|
662
|
+
|
663
|
+
### START Aggregate Grouping Helping Methods ####
|
664
|
+
def self.refresh_aggregate_table(aggregate_name: 'all_tasks_processing', table_name: 'tasks', where_clause: "where status in ('Processing', 'Queued')", index_table: true)
|
665
|
+
self.update_functions
|
666
|
+
#Broke function into two parts to ensure transaction size was small enough
|
667
|
+
ActiveRecord::Base.connection.execute('SELECT "shared_extensions".refresh_aggregate_table(\'%s\', \'%s\', %s, \'Table\');' % [aggregate_name, table_name, ActiveRecord::Base.connection.quote(where_clause)])
|
668
|
+
ActiveRecord::Base.connection.execute('SELECT "shared_extensions".refresh_aggregate_table(\'%s\', \'%s\', %s, \'Index\');' % [aggregate_name, table_name, ActiveRecord::Base.connection.quote(where_clause)]) if index_table
|
669
|
+
end
|
670
|
+
|
671
|
+
def self.update_functions
|
672
|
+
ActiveRecord::Base.connection.execute(File.read("#{Gem.loaded_specs["zuora_connect"].gem_dir}/app/views/sql/refresh_aggregate_table.txt"))
|
673
|
+
end
|
674
|
+
### END Aggregate Grouping Helping Methods #####
|
675
|
+
|
676
|
+
# Overide this method to avoid the new session call for api requests that use the before filter authenticate_app_api_request.
|
677
|
+
# This can be usefull for apps that dont need connect metadata call, or credentials, to operate for api requests
|
678
|
+
def new_session_for_api_requests(params: {})
|
679
|
+
return true
|
680
|
+
end
|
681
|
+
|
682
|
+
# Overide this method to avoid the new session call for ui requests that use the before filter authenticate_connect_app_request.
|
683
|
+
# This can be usefull for apps that dont need connect metadata call, or credentials, to operate for ui requests
|
684
|
+
def new_session_for_ui_requests(params: {})
|
685
|
+
return true
|
686
|
+
end
|
687
|
+
|
688
|
+
def reload_attributes(selected_attributes)
|
689
|
+
raise "Attibutes must be array" if selected_attributes.class != Array
|
690
|
+
value_attributes = self.class.unscoped.where(:id=>id).select(selected_attributes).first.attributes
|
691
|
+
value_attributes.each do |key, value|
|
692
|
+
next if key == "id" && value.blank?
|
693
|
+
self.send(:write_attribute, key, value)
|
694
|
+
end
|
695
|
+
return self
|
696
|
+
end
|
697
|
+
|
698
|
+
def instance_failure(failure)
|
699
|
+
raise failure
|
700
|
+
end
|
701
|
+
|
702
|
+
def send_email
|
703
|
+
end
|
704
|
+
|
705
|
+
def login_lookup(type: "Zuora")
|
706
|
+
results = []
|
707
|
+
self.logins.each do |name, login|
|
708
|
+
results << login if login.tenant_type == type
|
709
|
+
end
|
710
|
+
return results
|
711
|
+
end
|
712
|
+
|
713
|
+
def self.decrypt_response(resp)
|
714
|
+
OpenSSL::PKey::RSA.new(ZuoraConnect.configuration.private_key).private_decrypt(resp)
|
715
|
+
end
|
716
|
+
|
717
|
+
def attr_builder(field,val)
|
718
|
+
singleton_class.class_eval { attr_accessor "#{field}" }
|
719
|
+
send("#{field}=", val)
|
720
|
+
end
|
721
|
+
|
722
|
+
def method_missing(method_sym, *arguments, &block)
|
723
|
+
if method_sym.to_s.include?("login")
|
724
|
+
Rails.logger.fatal("Method Missing #{method_sym}")
|
725
|
+
Rails.logger.fatal("Instance Data: #{self.task_data}")
|
726
|
+
Rails.logger.fatal("Instance Logins: #{self.logins}")
|
727
|
+
end
|
728
|
+
super
|
729
|
+
end
|
730
|
+
|
731
|
+
method_hook :refresh, :updateOption, :update_logins, :before => :check_oauth_state
|
732
|
+
method_hook :new_session, :refresh, :build_task, :after => :apartment_switch
|
733
|
+
end
|
734
|
+
end
|