zuora_connect-D 1.6.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (94) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/Rakefile +38 -0
  4. data/app/assets/javascripts/zuora_connect/api/v1/app_instance.js +2 -0
  5. data/app/assets/javascripts/zuora_connect/application.js +13 -0
  6. data/app/assets/stylesheets/zuora_connect/api/v1/app_instance.css +4 -0
  7. data/app/assets/stylesheets/zuora_connect/application.css +15 -0
  8. data/app/controllers/zuora_connect/admin/tenant_controller.rb +11 -0
  9. data/app/controllers/zuora_connect/api/v1/app_instance_controller.rb +37 -0
  10. data/app/controllers/zuora_connect/application_controller.rb +8 -0
  11. data/app/controllers/zuora_connect/static_controller.rb +26 -0
  12. data/app/helpers/zuora_connect/api/v1/app_instance_helper.rb +4 -0
  13. data/app/helpers/zuora_connect/application_helper.rb +5 -0
  14. data/app/models/zuora_connect/app_instance.rb +5 -0
  15. data/app/models/zuora_connect/app_instance_base.rb +755 -0
  16. data/app/models/zuora_connect/login.rb +37 -0
  17. data/app/views/layouts/zuora_connect/application.html.erb +14 -0
  18. data/app/views/sql/refresh_aggregate_table.txt +84 -0
  19. data/app/views/zuora_connect/static/invalid_app_instance_error.html.erb +65 -0
  20. data/app/views/zuora_connect/static/session_error.html.erb +63 -0
  21. data/config/initializers/apartment.rb +95 -0
  22. data/config/initializers/object_method_hooks.rb +27 -0
  23. data/config/initializers/redis.rb +10 -0
  24. data/config/initializers/resque.rb +5 -0
  25. data/config/initializers/to_bool.rb +24 -0
  26. data/config/routes.rb +12 -0
  27. data/db/migrate/20100718151733_create_connect_app_instances.rb +9 -0
  28. data/db/migrate/20101024162319_add_tokens_to_app_instance.rb +6 -0
  29. data/db/migrate/20101024220705_add_token_to_app_instance.rb +5 -0
  30. data/db/migrate/20110131211919_add_sessions_table.rb +13 -0
  31. data/db/migrate/20110411200303_add_expiration_to_app_instance.rb +5 -0
  32. data/db/migrate/20110413191512_add_new_api_token.rb +5 -0
  33. data/db/migrate/20110503003602_add_catalog_data_to_app_instance.rb +6 -0
  34. data/db/migrate/20110503003603_add_catalog_mappings_to_app_instance.rb +5 -0
  35. data/db/migrate/20110503003604_catalog_default.rb +5 -0
  36. data/db/migrate/20180301052853_add_catalog_attempted_at.rb +5 -0
  37. data/lib/resque/additions.rb +53 -0
  38. data/lib/resque/dynamic_queues.rb +142 -0
  39. data/lib/resque/self_lookup.rb +19 -0
  40. data/lib/tasks/zuora_connect_tasks.rake +24 -0
  41. data/lib/zuora_connect.rb +38 -0
  42. data/lib/zuora_connect/configuration.rb +40 -0
  43. data/lib/zuora_connect/controllers/helpers.rb +165 -0
  44. data/lib/zuora_connect/engine.rb +30 -0
  45. data/lib/zuora_connect/exceptions.rb +67 -0
  46. data/lib/zuora_connect/railtie.rb +35 -0
  47. data/lib/zuora_connect/version.rb +3 -0
  48. data/lib/zuora_connect/views/helpers.rb +9 -0
  49. data/test/controllers/zuora_connect/api/v1/app_instance_controller_test.rb +13 -0
  50. data/test/dummy/README.rdoc +28 -0
  51. data/test/dummy/Rakefile +6 -0
  52. data/test/dummy/app/assets/javascripts/application.js +13 -0
  53. data/test/dummy/app/assets/stylesheets/application.css +15 -0
  54. data/test/dummy/app/controllers/application_controller.rb +5 -0
  55. data/test/dummy/app/helpers/application_helper.rb +2 -0
  56. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  57. data/test/dummy/bin/bundle +3 -0
  58. data/test/dummy/bin/rails +4 -0
  59. data/test/dummy/bin/rake +4 -0
  60. data/test/dummy/bin/setup +29 -0
  61. data/test/dummy/config.ru +4 -0
  62. data/test/dummy/config/application.rb +26 -0
  63. data/test/dummy/config/boot.rb +5 -0
  64. data/test/dummy/config/database.yml +25 -0
  65. data/test/dummy/config/environment.rb +5 -0
  66. data/test/dummy/config/environments/development.rb +41 -0
  67. data/test/dummy/config/environments/production.rb +79 -0
  68. data/test/dummy/config/environments/test.rb +42 -0
  69. data/test/dummy/config/initializers/assets.rb +11 -0
  70. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  71. data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
  72. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  73. data/test/dummy/config/initializers/inflections.rb +16 -0
  74. data/test/dummy/config/initializers/mime_types.rb +4 -0
  75. data/test/dummy/config/initializers/session_store.rb +3 -0
  76. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  77. data/test/dummy/config/locales/en.yml +23 -0
  78. data/test/dummy/config/routes.rb +4 -0
  79. data/test/dummy/config/secrets.yml +22 -0
  80. data/test/dummy/db/development.sqlite3 +0 -0
  81. data/test/dummy/db/test.sqlite3 +0 -0
  82. data/test/dummy/log/development.log +2 -0
  83. data/test/dummy/log/test.log +0 -0
  84. data/test/dummy/public/404.html +67 -0
  85. data/test/dummy/public/422.html +67 -0
  86. data/test/dummy/public/500.html +66 -0
  87. data/test/dummy/public/favicon.ico +0 -0
  88. data/test/fixtures/zuora_connect/app_instances.yml +11 -0
  89. data/test/integration/navigation_test.rb +8 -0
  90. data/test/lib/generators/zuora_connect/datatable_generator_test.rb +16 -0
  91. data/test/models/zuora_connect/app_instance_test.rb +9 -0
  92. data/test/test_helper.rb +21 -0
  93. data/test/zuora_connect_test.rb +7 -0
  94. metadata +408 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: f4a32832d52271db57ae297dfac278ad65acdcf6421ab297fb1f1e2fcae0e360
4
+ data.tar.gz: b18107fc7f838bddb3f21209e494048b22ff8fa9c6d435a6bbfa3cc785c74770
5
+ SHA512:
6
+ metadata.gz: 76df3bdf6657807dc46fd9e7f4bca45db06259942e5ee156f80d49ea7ed189e1daaa2e56cb9ac490eb366e09e56dbc3fcfa5ffa07c90e372d4de157e0cfe1861
7
+ data.tar.gz: 89f952fb0febfbdb3ba87a9a442a3f3c0ebc0ac400f324bb2b31f09aaae55b640648614bfdc40a147ee0411d1625d5cb43c384bc4b5d8176d08a6a53adf780aa
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,2 @@
1
+ // Place all the behaviors and hooks related to the matching controller here.
2
+ // All this logic will automatically be available in application.js.
@@ -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,4 @@
1
+ /*
2
+ Place all the styles related to the matching controller here.
3
+ They will automatically be included in application.css.
4
+ */
@@ -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,11 @@
1
+ require_dependency "zuora_connect/application_controller"
2
+
3
+ module ZuoraConnect
4
+ class Admin::TenantController < ApplicationController
5
+ before_filter :check_admin
6
+ def index
7
+
8
+ end
9
+
10
+ end
11
+ end
@@ -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,8 @@
1
+ module ZuoraConnect
2
+ class ApplicationController < ActionController::Base
3
+ protect_from_forgery with: :exception
4
+ before_filter :authenticate_connect_app_request
5
+ after_filter :persist_connect_app_session
6
+
7
+ end
8
+ 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,4 @@
1
+ module ZuoraConnect
2
+ module Api::V1::AppInstanceHelper
3
+ end
4
+ end
@@ -0,0 +1,5 @@
1
+ module ZuoraConnect
2
+ module ApplicationHelper
3
+
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module ZuoraConnect
2
+ class AppInstance < ZuoraConnect::AppInstanceBase
3
+ default_scope {select(ZuoraConnect::AppInstance.column_names.delete_if {|x| ["catalog_mapping", "catalog"].include?(x) }) }
4
+ end
5
+ end
@@ -0,0 +1,755 @@
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.debug("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.debug("[#{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
+ self.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).current_session = session["#{self.id}::#{k}::#{entity_id}:current_session"] if session["#{self.id}::#{k}::#{entity_id}:current_session"]
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.current_session = session["#{self.id}::#{k}:current_session"] if session["#{self.id}::#{k}:current_session"]
173
+ tmp.client.bearer_token = session["#{self.id}::#{k}:bearer_token"] if session["#{self.id}::#{k}:bearer_token"] && tmp.client.respond_to?(:bearer_token) ## need incase session id goes from basic to aouth in same redis store
174
+ tmp.client.oauth_session_expires_at = session["#{self.id}::#{k}:oauth_session_expires_at"] if session["#{self.id}::#{k}:oauth_session_expires_at"] && tmp.client.respond_to?(:oauth_session_expires_at)
175
+ end
176
+ @logins[k] = tmp
177
+ self.attr_builder(k, @logins[k])
178
+ elsif k == "options"
179
+ v.each do |opt|
180
+ @options[opt["config_name"]] = opt
181
+ end
182
+ elsif k == "user_settings"
183
+ self.timezone = v["timezone"]
184
+ self.locale = v["local"]
185
+ end
186
+ end
187
+ rescue => ex
188
+ Rails.logger.error("Task Data: #{task_data}")
189
+ Rails.logger.error("Task Session: #{session}")
190
+ raise
191
+ end
192
+ end
193
+
194
+ def updateOption(optionId, value)
195
+ return HTTParty.get(ZuoraConnect.configuration.url + "/api/#{self.api_version}/tools/application_options/#{optionId}/edit?value=#{value}",:body => {:access_token => self.username})
196
+ end
197
+
198
+ #This can update an existing login, add a new login, change to another existing login
199
+ #EXAMPLE: {"name": "ftp_login_14","username": "ftplogin7","tenant_type": "Custom","password": "test2","url": "www.ftp.com","custom_data": { "path": "/var/usr/test"}}
200
+ def update_logins(options)
201
+ update_login_count ||= 0
202
+ response = HTTParty.post(ZuoraConnect.configuration.url + "/api/#{self.api_version}/tools/tasks/#{self.id}/logins",:body => {:access_token => self.username}.merge(options))
203
+ parsed_json = JSON.parse(response.body)
204
+ if response.code == 200
205
+ if defined?(Redis.current)
206
+ self.build_task(parsed_json, self.data_lookup)
207
+ self.last_refresh = Time.now.to_i
208
+ self.cache_app_instance
209
+ end
210
+ return parsed_json
211
+ elsif response.code == 400
212
+ raise ZuoraConnect::Exceptions::APIError.new(message: parsed_json['errors'].join(' '), response: response.body, code: response.code)
213
+ else
214
+ raise ZuoraConnect::Exceptions::ConnectCommunicationError.new("Error Communicating with Connect", response.body, response.code)
215
+ end
216
+ rescue Net::ReadTimeout, Net::OpenTimeout, Errno::EPIPE, Errno::ECONNRESET, Errno::ECONNREFUSED, SocketError
217
+ if (update_login_count += 1) < 3
218
+ retry
219
+ else
220
+ raise
221
+ end
222
+ rescue ZuoraConnect::Exceptions::ConnectCommunicationError => ex
223
+ if (update_login_count += 1) < 3
224
+ if ex.code == 401
225
+ self.refresh_oauth
226
+ end
227
+ retry
228
+ else
229
+ raise
230
+ end
231
+ end
232
+ #### END Task Mathods ####
233
+
234
+ #### START Connect OAUTH methods ####
235
+ def check_oauth_state(method)
236
+ #Refresh token if already expired
237
+ if self.oauth_expired?
238
+ Rails.logger.debug("[#{self.id}] Before '#{method}' method, Oauth expired")
239
+ self.refresh_oauth
240
+ end
241
+ end
242
+
243
+ def oauth_expired?
244
+ return self.oauth_expires_at.present? ? (self.oauth_expires_at < Time.now) : true
245
+ end
246
+
247
+ def refresh_oauth
248
+ refresh_oauth_count ||= 0
249
+
250
+ start = Time.now
251
+ params = {
252
+ :grant_type => "refresh_token",
253
+ :redirect_uri => ZuoraConnect.configuration.oauth_client_redirect_uri,
254
+ :refresh_token => self.refresh_token
255
+ }
256
+ response = HTTParty.post("#{ZuoraConnect.configuration.url}/oauth/token",:body => params)
257
+ response_time = Time.now - start
258
+ Rails.logger.info("[#{self.id}] REFRESH OAUTH - In #{response_time.round(2).to_s}")
259
+
260
+ if response.code == 200
261
+ response_body = JSON.parse(response.body)
262
+
263
+ self.refresh_token = response_body["refresh_token"]
264
+ self.access_token = response_body["access_token"]
265
+ self.oauth_expires_at = Time.at(response_body["created_at"].to_i) + response_body["expires_in"].seconds
266
+ self.save(:validate => false)
267
+ else
268
+ Rails.logger.fatal("[#{self.id}] REFRESH OAUTH - Failed Code #{response.code}")
269
+ raise ZuoraConnect::Exceptions::ConnectCommunicationError.new("Error Refreshing Access Token", response.body, response.code)
270
+ end
271
+ rescue Net::ReadTimeout, Net::OpenTimeout, Errno::EPIPE, Errno::ECONNRESET, Errno::ECONNREFUSED, SocketError => ex
272
+ if (refresh_oauth_count += 1) < 3
273
+ Rails.logger.info("[#{self.id}] REFRESH OAUTH - #{ex.class} Retrying(#{refresh_oauth_count})")
274
+ retry
275
+ else
276
+ Rails.logger.fatal("[#{self.id}] REFRESH OAUTH - #{ex.class} Failed #{refresh_oauth_count}x")
277
+ raise
278
+ end
279
+ rescue ZuoraConnect::Exceptions::ConnectCommunicationError => ex
280
+ sleep(5)
281
+ self.reload_attributes([:refresh_token, :oauth_expires_at, :access_token]) #Reload only the refresh token for retry
282
+
283
+ #After reload, if nolonger expired return
284
+ return if !self.oauth_expired?
285
+
286
+ if (refresh_oauth_count += 1) < 3
287
+ Rails.logger.info("[#{self.id}] REFRESH OAUTH - Failed Retrying(#{refresh_oauth_count})")
288
+ retry
289
+ else
290
+ Rails.logger.fatal("[#{self.id}] REFRESH OAUTH - Failed #{refresh_oauth_count}x")
291
+ raise
292
+ end
293
+ end
294
+ #### END Connect OAUTH methods ####
295
+
296
+ #### START AppInstance Temporary Persistance Methods ####
297
+ def marked_for_refresh?
298
+ return defined?(Redis.current) ? Redis.current.get("AppInstance:#{self.id}:Refreshing").to_bool : false
299
+ end
300
+
301
+ def reset_mark_for_refresh
302
+ Redis.current.del("AppInstance:#{self.id}:Refreshing") if defined?(Redis.current)
303
+ end
304
+
305
+ def reset_mark_refreshed_at
306
+ return defined?(Redis.current) ? REFRESH_TIMEOUT.to_i - Redis.current.ttl("AppInstance:#{self.id}:Refreshing") : 0
307
+ end
308
+
309
+ def reset_mark_expires_at
310
+ return defined?(Redis.current) ? Redis.current.ttl("AppInstance:#{self.id}:Refreshing") : 0
311
+ end
312
+
313
+ def mark_for_refresh
314
+ return defined?(Redis.current) ? Redis.current.set("AppInstance:#{self.id}:Refreshing", true, {:nx => true, :ex => REFRESH_TIMEOUT.to_i}) : true
315
+ end
316
+
317
+ def data_lookup(session: {})
318
+ if defined?(PaperTrail)
319
+ PaperTrail.whodunnit = session["#{self.id}::user::email"].present? ? session["#{self.id}::user::email"] : nil if session.present?
320
+ end
321
+ if defined?(Redis.current)
322
+ cached_instance = Redis.current.get("AppInstance:#{self.id}")
323
+ if cached_instance.blank?
324
+ Rails.logger.debug("[#{self.id}] Cached AppInstance Missing")
325
+ return session
326
+ else
327
+ Rails.logger.debug("[#{self.id}] Cached AppInstance Found")
328
+ return decrypt_data(data: cached_instance, rescue_return: session)
329
+ end
330
+ else
331
+ return session
332
+ end
333
+ end
334
+
335
+ def cache_app_instance
336
+ if defined?(Redis.current)
337
+ #Task data must be present and the last refresh cannot be old. We dont want to overwite new cache data with old
338
+ if self.task_data.present? && (self.last_refresh.to_i > INSTANCE_REFRESH_WINDOW.ago.to_i)
339
+ Rails.logger.info("[#{self.id}] Caching AppInstance")
340
+ Redis.current.setex("AppInstance:#{self.id}", INSTANCE_REDIS_CACHE_PERIOD.to_i, encrypt_data(data: self.save_data))
341
+ end
342
+ Redis.current.del("Deleted:#{self.id}")
343
+ end
344
+ end
345
+
346
+ def save_data(session = Hash.new)
347
+ self.logins.each do |key, login|
348
+ if login.tenant_type == "Zuora"
349
+ if login.available_entities.size > 1 && Rails.application.config.session_store != ActionDispatch::Session::CookieStore
350
+ login.available_entities.each do |entity_key|
351
+ session["#{self.id}::#{key}::#{entity_key}:current_session"] = login.client(entity_key).current_session if login.client.respond_to?(:current_session)
352
+ session["#{self.id}::#{key}::#{entity_key}:bearer_token"] = login.client(entity_key).bearer_token if login.client.respond_to?(:bearer_token)
353
+ 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)
354
+ end
355
+ else
356
+ session["#{self.id}::#{key}:current_session"] = login.client.current_session if login.client.respond_to?(:current_session)
357
+ session["#{self.id}::#{key}:bearer_token"] = login.client.bearer_token if login.client.respond_to?(:bearer_token)
358
+ session["#{self.id}::#{key}:oauth_session_expires_at"] = login.client.oauth_session_expires_at if login.client.respond_to?(:oauth_session_expires_at)
359
+ end
360
+ end
361
+ end
362
+
363
+ session["#{self.id}::task_data"] = self.task_data
364
+
365
+ #Redis is not defined strip out old data
366
+ if !defined?(Redis.current)
367
+ session["#{self.id}::task_data"].delete('applications')
368
+ session["#{self.id}::task_data"].select {|k,v| k.include?('login') && v['tenant_type'] == 'Zuora'}.each do |login_key, login_data|
369
+ session["#{self.id}::task_data"][login_key]['entities'] = (login_data.dig('entities') || []).map {|entity| entity.slice('id', 'tenantId')}
370
+ end
371
+ end
372
+
373
+ session["#{self.id}::last_refresh"] = self.last_refresh
374
+ session["appInstance"] = self.id
375
+ return session
376
+ end
377
+
378
+ def encryptor
379
+ # Default values for Rails 4 apps
380
+ key_iter_num, key_size, salt, signed_salt = [1000, 64, "encrypted cookie", "signed encrypted cookie"]
381
+ key_generator = ActiveSupport::KeyGenerator.new(Rails.application.secrets.secret_key_base, iterations: key_iter_num)
382
+ secret, sign_secret = [key_generator.generate_key(salt), key_generator.generate_key(signed_salt)]
383
+ return ActiveSupport::MessageEncryptor.new(secret, sign_secret)
384
+ end
385
+
386
+ def decrypt_data(data: nil, rescue_return: nil)
387
+ return data if data.blank?
388
+ begin
389
+ if Rails.env == 'development'
390
+ return JSON.parse(data)
391
+ else
392
+ begin
393
+ return JSON.parse(encryptor.decrypt_and_verify(CGI::unescape(data)))
394
+ rescue ActiveSupport::MessageVerifier::InvalidSignature => ex
395
+ Rails.logger.fatal('Error Decrypting')
396
+ return rescue_return
397
+ end
398
+ end
399
+ rescue JSON::ParserError => ex
400
+ Rails.logger.fatal('Error Parsing')
401
+ return rescue_return
402
+ end
403
+ end
404
+
405
+ def encrypt_data(data: nil)
406
+ return data if data.blank?
407
+
408
+ if ['development', 'test'].include?(Rails.env)
409
+ return data.to_json
410
+ else
411
+ return encryptor.encrypt_and_sign(data.to_json)
412
+ end
413
+ end
414
+ #### END AppInstance Temporary Persistance Methods ####
415
+
416
+ ### START Resque Helping Methods ####
417
+ def api_limit(start: true, time: API_LIMIT_TIMEOUT.to_i)
418
+ if start
419
+ Redis.current.setex("APILimits:#{self.id}", time, true)
420
+ else
421
+ Redis.current.del("APILimits:#{self.id}")
422
+ end
423
+ end
424
+
425
+ def api_limit?
426
+ return Redis.current.get("APILimits:#{self.id}").to_bool
427
+ end
428
+
429
+ def queue_paused?
430
+ return Redis.current.get("resque:PauseQueue:#{self.id}").to_bool
431
+ end
432
+
433
+ def queue_pause(time: nil)
434
+ if time.present?
435
+ raise "Time must be fixnum of seconds." if time.class != Fixnum
436
+ Redis.current.setex("resque:PauseQueue:#{self.id}", time, true)
437
+ else
438
+ Redis.current.set("resque:PauseQueue:#{self.id}", true)
439
+ end
440
+ end
441
+
442
+ def queue_start
443
+ Redis.current.del("resque:PauseQueue:#{self.id}")
444
+ end
445
+ ### END Resque Helping Methods ####
446
+
447
+ ### START Catalog Helping Methods #####
448
+ def get_catalog(page_size: 5, zuora_login: self.login_lookup(type: "Zuora").first, entity_id: nil)
449
+ self.update_column(:catalog_update_attempt_at, Time.now.utc)
450
+
451
+ entity_reference = entity_id.blank? ? 'Default' : entity_id
452
+ Rails.logger.debug("Fetch Catalog")
453
+ Rails.logger.debug("Zuora Entity: #{entity_id.blank? ? 'default' : entity_id}")
454
+
455
+ login = zuora_login.client(entity_reference)
456
+
457
+ old_logger = ActiveRecord::Base.logger
458
+ ActiveRecord::Base.logger = nil
459
+ 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})
460
+
461
+ response = {'nextPage' => login.rest_endpoint("catalog/products?pageSize=#{page_size}")}
462
+ while !response["nextPage"].blank?
463
+ url = login.rest_endpoint(response["nextPage"].split('/v1/').last)
464
+ Rails.logger.debug("Fetch Catalog URL #{url}")
465
+ output_json, response = login.rest_call(:debug => false, :url => url, :errors => [ZuoraAPI::Exceptions::ZuoraAPISessionError], :timeout_retry => true)
466
+ Rails.logger.debug("Fetch Catalog Response Code #{response.code}")
467
+
468
+ if !output_json['success'] =~ (/(true|t|yes|y|1)$/i) || output_json['success'].class != TrueClass
469
+ Rails.logger.error("Fetch Catalog DATA #{output_json.to_json}")
470
+ raise ZuoraAPI::Exceptions::ZuoraAPIError.new("Error Getting Catalog: #{output_json}")
471
+ end
472
+
473
+ output_json["products"].each do |product|
474
+ 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])
475
+ rateplans = {}
476
+
477
+ product["productRatePlans"].each do |rateplan|
478
+ 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])
479
+ charges = {}
480
+
481
+ rateplan["productRatePlanCharges"].each do |charge|
482
+ 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])
483
+
484
+ charges[charge["id"]] = charge.merge({"productId" => product["id"], "productName" => product["name"], "productRatePlanId" => rateplan["id"], "productRatePlanName" => rateplan["name"] })
485
+ end
486
+
487
+ rateplan["productRatePlanCharges"] = charges
488
+ rateplans[rateplan["id"]] = rateplan.merge({"productId" => product["id"], "productName" => product["name"]})
489
+ end
490
+ product["productRatePlans"] = rateplans
491
+
492
+ 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])
493
+ end
494
+ end
495
+
496
+ # Move from tmp to actual
497
+ 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})
498
+ if defined?(Redis.current)
499
+ Redis.current.keys("Catalog:#{self.id}:*").each do |key|
500
+ Redis.current.del(key.to_s)
501
+ end
502
+ end
503
+ # Clear tmp holder
504
+ 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})
505
+
506
+ ActiveRecord::Base.logger = old_logger
507
+ self.update_column(:catalog_updated_at, Time.now.utc)
508
+ self.touch
509
+
510
+ # DO NOT RETURN CATALOG. THIS IS NOT SCALABLE WITH LARGE CATALOGS. USE THE CATALOG_LOOKUP method provided
511
+ return true
512
+ end
513
+
514
+ def catalog_outdated?(time: Time.now - 12.hours)
515
+ return self.catalog_updated_at.blank? || (self.catalog_updated_at < time)
516
+ end
517
+
518
+ def catalog_loaded?
519
+ return ActiveRecord::Base.connection.execute('SELECT id FROM "public"."zuora_connect_app_instances" WHERE "id" = %s AND catalog = \'{}\' LIMIT 1' % [self.id]).first.nil?
520
+ end
521
+
522
+ # Catalog lookup provides method to lookup zuora catalog efficiently.
523
+ # entity_id: If the using catalog json be field to store multiple entity product catalogs.
524
+ # object: The Object class desired to be returned. Available [:product, :rateplan, :charge]
525
+ # object_id: The id or id's of the object/objects to be returned.
526
+ # child_objects: Whether to include child objects of the object in question.
527
+ # cache: Store individual "1" object lookup in redis for caching.
528
+ def catalog_lookup(entity_id: nil, object: :product, object_id: nil, child_objects: false, cache: false)
529
+ entity_reference = entity_id.blank? ? 'Default' : entity_id
530
+
531
+ if object_id.present? && ![Array, String].include?(object_id.class)
532
+ raise "Object Id can only be a string or an array of strings"
533
+ end
534
+
535
+ if defined?(Redis.current) && object_id.present? && object_id.class == String && object_id.present?
536
+ stub_catalog = decrypt_data(data: Redis.current.get("Catalog:#{self.id}:#{object_id}:Children:#{child_objects}"))
537
+ object_hierarchy = decrypt_data(data: Redis.current.get("Catalog:#{self.id}:#{object_id}:Hierarchy"))
538
+ end
539
+
540
+ if defined?(object_hierarchy)
541
+ 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"})
542
+ end
543
+
544
+ case object
545
+ when :product
546
+ if object_id.nil?
547
+ string =
548
+ "SELECT "\
549
+ "json_object_agg(product_id, product #{child_objects ? '' : '- \'productRatePlans\''}) AS item "\
550
+ "FROM "\
551
+ "\"public\".\"zuora_connect_app_instances\", "\
552
+ "jsonb_each((\"public\".\"zuora_connect_app_instances\".\"catalog\" #> '{%s}' )) AS e(product_id, product) "\
553
+ "WHERE "\
554
+ "\"id\" = %s" % [entity_reference, self.id]
555
+ else
556
+ if object_id.class == String
557
+ string =
558
+ "SELECT "\
559
+ "(catalog #> '{%s, %s}') #{child_objects ? '' : '- \'productRatePlans\''} AS item "\
560
+ "FROM "\
561
+ "\"public\".\"zuora_connect_app_instances\" "\
562
+ "WHERE "\
563
+ "\"id\" = %s" % [entity_reference, object_id.blank? ? BLANK_OBJECT_ID_LOOKUP : object_id, self.id]
564
+ elsif object_id.class == Array
565
+ string =
566
+ "SELECT "\
567
+ "json_object_agg(product_id, product #{child_objects ? '' : '- \'productRatePlans\''}) AS item "\
568
+ "FROM "\
569
+ "\"public\".\"zuora_connect_app_instances\", "\
570
+ "jsonb_each((\"public\".\"zuora_connect_app_instances\".\"catalog\" #> '{%s}' )) AS e(product_id, product) "\
571
+ "WHERE "\
572
+ "\"product_id\" IN (\'%s\') AND "\
573
+ "\"id\" = %s" % [entity_reference, object_id.join("\',\'"), self.id]
574
+ end
575
+ end
576
+
577
+ when :rateplan
578
+ if object_id.nil?
579
+ string =
580
+ "SELECT "\
581
+ "json_object_agg(rateplan_id, rateplan #{child_objects ? '' : '- \'productRatePlanCharges\''}) AS item "\
582
+ "FROM "\
583
+ "\"public\".\"zuora_connect_app_instances\", "\
584
+ "jsonb_each((\"public\".\"zuora_connect_app_instances\".\"catalog\" #> '{%s}' )) AS e(product_id, product), "\
585
+ "jsonb_each(product #> '{productRatePlans}') AS ee(rateplan_id, rateplan) "\
586
+ "WHERE "\
587
+ "\"id\" = %s" % [entity_reference, self.id]
588
+ else
589
+ if object_id.class == String
590
+ string =
591
+ "SELECT "\
592
+ "(catalog #> '{%s, %s, productRatePlans, %s}') #{child_objects ? '' : '- \'productRatePlanCharges\''} AS item "\
593
+ "FROM "\
594
+ "\"public\".\"zuora_connect_app_instances\" "\
595
+ "WHERE "\
596
+ "\"id\" = %s" % [entity_reference, object_hierarchy['productId'], object_id.blank? ? BLANK_OBJECT_ID_LOOKUP : object_id, self.id]
597
+ elsif object_id.class == Array
598
+ string =
599
+ "SELECT "\
600
+ "json_object_agg(rateplan_id, rateplan #{child_objects ? '' : '- \'productRatePlanCharges\''}) AS item "\
601
+ "FROM "\
602
+ "\"public\".\"zuora_connect_app_instances\", "\
603
+ "jsonb_each((\"public\".\"zuora_connect_app_instances\".\"catalog\" #> '{%s}' )) AS e(product_id, product), "\
604
+ "jsonb_each(product #> '{productRatePlans}') AS ee(rateplan_id, rateplan) "\
605
+ "WHERE "\
606
+ "\"rateplan_id\" IN (\'%s\') AND "\
607
+ "\"id\" = %s" % [entity_reference, object_id.join("\',\'"), self.id]
608
+ end
609
+ end
610
+
611
+ when :charge
612
+ if object_id.nil?
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
+ "\"id\" = %s" % [entity_reference, self.id]
623
+ else
624
+ if object_id.class == String
625
+ string =
626
+ "SELECT "\
627
+ "catalog #> '{%s, %s, productRatePlans, %s, productRatePlanCharges, %s}' AS item "\
628
+ "FROM "\
629
+ "\"public\".\"zuora_connect_app_instances\" "\
630
+ "WHERE "\
631
+ "\"id\" = %s" % [entity_reference, object_hierarchy['productId'], object_hierarchy['productRatePlanId'], object_id.blank? ? BLANK_OBJECT_ID_LOOKUP : object_id , self.id]
632
+
633
+ elsif object_id.class == Array
634
+ string =
635
+ "SELECT "\
636
+ "json_object_agg(charge_id, charge) AS item "\
637
+ "FROM "\
638
+ "\"public\".\"zuora_connect_app_instances\", "\
639
+ "jsonb_each((\"public\".\"zuora_connect_app_instances\".\"catalog\" #> '{%s}' )) AS e(product_id, product), "\
640
+ "jsonb_each(product #> '{productRatePlans}') AS ee(rateplan_id, rateplan), "\
641
+ "jsonb_each(rateplan #> '{productRatePlanCharges}') AS eee(charge_id, charge) "\
642
+ "WHERE "\
643
+ "\"charge_id\" IN (\'%s\') AND "\
644
+ "\"id\" = %s" % [entity_reference, object_id.join("\',\'"), self.id]
645
+ end
646
+ end
647
+ else
648
+ raise "Available objects include [:product, :rateplan, :charge]"
649
+ end
650
+
651
+ stub_catalog ||= JSON.parse(ActiveRecord::Base.connection.execute(string).first["item"] || "{}")
652
+
653
+ if defined?(Redis.current) && object_id.present? && object_id.class == String && object_id.present?
654
+ Redis.current.set("Catalog:#{self.id}:#{object_id}:Hierarchy", encrypt_data(data: object_hierarchy))
655
+ Redis.current.set("Catalog:#{self.id}:#{object_id}:Children:#{child_objects}", encrypt_data(data: stub_catalog)) if cache
656
+ end
657
+ return stub_catalog
658
+ end
659
+ ### END Catalog Helping Methods #####
660
+
661
+ ### START S3 Helping Methods #####
662
+ def s3_client
663
+ require 'aws-sdk-s3'
664
+ if ZuoraConnect.configuration.mode == "Development"
665
+ @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)
666
+ else
667
+ @s3_client ||= Aws::S3::Resource.new(region: ZuoraConnect.configuration.aws_region)
668
+ end
669
+ end
670
+
671
+ def upload_to_s3(local_file,s3_path = nil)
672
+ s3_path = local_file.split("/").last if s3_path.nil?
673
+ obj = self.s3_client.bucket(ZuoraConnect.configuration.s3_bucket_name).object("#{ZuoraConnect.configuration.s3_folder_name}/#{self.id.to_s}/#{s3_path}}")
674
+ obj.upload_file(local_file, :server_side_encryption => 'AES256')
675
+ end
676
+
677
+ def get_s3_file_url(key)
678
+ require 'aws-sdk-s3'
679
+ signer = Aws::S3::Presigner.new(client: self.s3_client)
680
+ url = signer.presigned_url(:get_object, bucket: ZuoraConnect.configuration.s3_bucket_name, key: "#{ZuoraConnect.configuration.s3_folder_name}/#{self.id.to_s}/#{key}")
681
+ end
682
+ ### END S3 Helping Methods #####
683
+
684
+ ### START Aggregate Grouping Helping Methods ####
685
+ def self.refresh_aggregate_table(aggregate_name: 'all_tasks_processing', table_name: 'tasks', where_clause: "where status in ('Processing', 'Queued')", index_table: true)
686
+ self.update_functions
687
+ #Broke function into two parts to ensure transaction size was small enough
688
+ ActiveRecord::Base.connection.execute('SELECT "shared_extensions".refresh_aggregate_table(\'%s\', \'%s\', %s, \'Table\');' % [aggregate_name, table_name, ActiveRecord::Base.connection.quote(where_clause)])
689
+ 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
690
+ end
691
+
692
+ def self.update_functions
693
+ ActiveRecord::Base.connection.execute(File.read("#{Gem.loaded_specs["zuora_connect"].gem_dir}/app/views/sql/refresh_aggregate_table.txt"))
694
+ end
695
+ ### END Aggregate Grouping Helping Methods #####
696
+
697
+ # Overide this method to avoid the new session call for api requests that use the before filter authenticate_app_api_request.
698
+ # This can be usefull for apps that dont need connect metadata call, or credentials, to operate for api requests
699
+ def new_session_for_api_requests(params: {})
700
+ return true
701
+ end
702
+
703
+ # Overide this method to avoid the new session call for ui requests that use the before filter authenticate_connect_app_request.
704
+ # This can be usefull for apps that dont need connect metadata call, or credentials, to operate for ui requests
705
+ def new_session_for_ui_requests(params: {})
706
+ return true
707
+ end
708
+
709
+ def reload_attributes(selected_attributes)
710
+ raise "Attibutes must be array" if selected_attributes.class != Array
711
+ value_attributes = self.class.unscoped.where(:id=>id).select(selected_attributes).first.attributes
712
+ value_attributes.each do |key, value|
713
+ next if key == "id" && value.blank?
714
+ self.send(:write_attribute, key, value)
715
+ end
716
+ return self
717
+ end
718
+
719
+ def instance_failure(failure)
720
+ raise failure
721
+ end
722
+
723
+ def send_email
724
+ end
725
+
726
+ def login_lookup(type: "Zuora")
727
+ results = []
728
+ self.logins.each do |name, login|
729
+ results << login if login.tenant_type == type
730
+ end
731
+ return results
732
+ end
733
+
734
+ def self.decrypt_response(resp)
735
+ OpenSSL::PKey::RSA.new(ZuoraConnect.configuration.private_key).private_decrypt(resp)
736
+ end
737
+
738
+ def attr_builder(field,val)
739
+ singleton_class.class_eval { attr_accessor "#{field}" }
740
+ send("#{field}=", val)
741
+ end
742
+
743
+ def method_missing(method_sym, *arguments, &block)
744
+ if method_sym.to_s.include?("login")
745
+ Rails.logger.fatal("Method Missing #{method_sym}")
746
+ Rails.logger.fatal("Instance Data: #{self.task_data}")
747
+ Rails.logger.fatal("Instance Logins: #{self.logins}")
748
+ end
749
+ super
750
+ end
751
+
752
+ method_hook :refresh, :updateOption, :update_logins, :before => :check_oauth_state
753
+ method_hook :new_session, :refresh, :build_task, :after => :apartment_switch
754
+ end
755
+ end