zuora_connectD 1.7.09

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) 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 +45 -0
  10. data/app/controllers/zuora_connect/application_controller.rb +8 -0
  11. data/app/controllers/zuora_connect/static_controller.rb +32 -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 +811 -0
  16. data/app/models/zuora_connect/login.rb +36 -0
  17. data/app/models/zuora_connect/telegraf.rb +88 -0
  18. data/app/views/layouts/zuora_connect/application.html.erb +14 -0
  19. data/app/views/sql/refresh_aggregate_table.txt +84 -0
  20. data/app/views/zuora_connect/static/invalid_app_instance_error.html.erb +65 -0
  21. data/app/views/zuora_connect/static/session_error.html.erb +63 -0
  22. data/config/initializers/apartment.rb +95 -0
  23. data/config/initializers/object_method_hooks.rb +27 -0
  24. data/config/initializers/postgresql_adapter.rb +32 -0
  25. data/config/initializers/prometheus.rb +41 -0
  26. data/config/initializers/redis.rb +10 -0
  27. data/config/initializers/resque.rb +6 -0
  28. data/config/initializers/to_bool.rb +24 -0
  29. data/config/initializers/unicorn.rb +9 -0
  30. data/config/routes.rb +13 -0
  31. data/db/migrate/20100718151733_create_connect_app_instances.rb +9 -0
  32. data/db/migrate/20101024162319_add_tokens_to_app_instance.rb +6 -0
  33. data/db/migrate/20101024220705_add_token_to_app_instance.rb +5 -0
  34. data/db/migrate/20110131211919_add_sessions_table.rb +13 -0
  35. data/db/migrate/20110411200303_add_expiration_to_app_instance.rb +5 -0
  36. data/db/migrate/20110413191512_add_new_api_token.rb +5 -0
  37. data/db/migrate/20110503003602_add_catalog_data_to_app_instance.rb +6 -0
  38. data/db/migrate/20110503003603_add_catalog_mappings_to_app_instance.rb +5 -0
  39. data/db/migrate/20110503003604_catalog_default.rb +5 -0
  40. data/db/migrate/20180301052853_add_catalog_attempted_at.rb +5 -0
  41. data/lib/metrics/influx/point_value.rb +79 -0
  42. data/lib/metrics/net.rb +218 -0
  43. data/lib/middleware/metrics_middleware.rb +110 -0
  44. data/lib/resque/additions.rb +53 -0
  45. data/lib/resque/dynamic_queues.rb +142 -0
  46. data/lib/resque/self_lookup.rb +19 -0
  47. data/lib/resque/silence_done.rb +71 -0
  48. data/lib/tasks/zuora_connect_tasks.rake +24 -0
  49. data/lib/zuora_connectD.rb +41 -0
  50. data/lib/zuora_connectD/configuration.rb +52 -0
  51. data/lib/zuora_connectD/controllers/helpers.rb +165 -0
  52. data/lib/zuora_connectD/engine.rb +30 -0
  53. data/lib/zuora_connectD/exceptions.rb +67 -0
  54. data/lib/zuora_connectD/railtie.rb +59 -0
  55. data/lib/zuora_connectD/version.rb +3 -0
  56. data/lib/zuora_connectD/views/helpers.rb +9 -0
  57. data/test/controllers/zuora_connect/api/v1/app_instance_controller_test.rb +13 -0
  58. data/test/dummy/README.rdoc +28 -0
  59. data/test/dummy/Rakefile +6 -0
  60. data/test/dummy/app/assets/javascripts/application.js +13 -0
  61. data/test/dummy/app/assets/stylesheets/application.css +15 -0
  62. data/test/dummy/app/controllers/application_controller.rb +5 -0
  63. data/test/dummy/app/helpers/application_helper.rb +2 -0
  64. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  65. data/test/dummy/bin/bundle +3 -0
  66. data/test/dummy/bin/rails +4 -0
  67. data/test/dummy/bin/rake +4 -0
  68. data/test/dummy/bin/setup +29 -0
  69. data/test/dummy/config.ru +4 -0
  70. data/test/dummy/config/application.rb +26 -0
  71. data/test/dummy/config/boot.rb +5 -0
  72. data/test/dummy/config/database.yml +25 -0
  73. data/test/dummy/config/environment.rb +5 -0
  74. data/test/dummy/config/environments/development.rb +41 -0
  75. data/test/dummy/config/environments/production.rb +79 -0
  76. data/test/dummy/config/environments/test.rb +42 -0
  77. data/test/dummy/config/initializers/assets.rb +11 -0
  78. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  79. data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
  80. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  81. data/test/dummy/config/initializers/inflections.rb +16 -0
  82. data/test/dummy/config/initializers/mime_types.rb +4 -0
  83. data/test/dummy/config/initializers/session_store.rb +3 -0
  84. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  85. data/test/dummy/config/locales/en.yml +23 -0
  86. data/test/dummy/config/routes.rb +4 -0
  87. data/test/dummy/config/secrets.yml +22 -0
  88. data/test/dummy/db/development.sqlite3 +0 -0
  89. data/test/dummy/db/test.sqlite3 +0 -0
  90. data/test/dummy/log/development.log +2 -0
  91. data/test/dummy/log/test.log +0 -0
  92. data/test/dummy/public/404.html +67 -0
  93. data/test/dummy/public/422.html +67 -0
  94. data/test/dummy/public/500.html +66 -0
  95. data/test/dummy/public/favicon.ico +0 -0
  96. data/test/fixtures/zuora_connect/app_instances.yml +11 -0
  97. data/test/integration/navigation_test.rb +8 -0
  98. data/test/lib/generators/zuora_connect/datatable_generator_test.rb +16 -0
  99. data/test/models/zuora_connect/app_instance_test.rb +9 -0
  100. data/test/test_helper.rb +21 -0
  101. data/test/zuora_connect_test.rb +7 -0
  102. metadata +416 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: f847498c74e46ed55694ac56caf3f51c45aa4aa67f9413804bcc18535a060e0f
4
+ data.tar.gz: ec3031dfc5d93febb0ee4543da2a0224c82f42f96ab776a3c213b257822cf89f
5
+ SHA512:
6
+ metadata.gz: 0624266b5c876766df28fcfcd0a710c1840f06eeb2ec5961ded68fc670d4baf68bdee7a07655ae471ebd496cf3c98d33c1e36e4664cd0d5f68450d260e5f4053
7
+ data.tar.gz: 0885bb3571d919947f5d75604bec602fd073ef19caf8c4f89faacd4412ded1fd58f7c4232688fa98e29da9a4ddd4267cabf9892c60e272f5eb5f25440ef8b366
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_connectD/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,45 @@
1
+ require_dependency "zuora_connectD/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
+ if @appinstance.drop_instance
17
+ ZuoraConnect::AppInstance.destroy(instance_id)
18
+ msg = Apartment::Tenant.drop(instance_id)
19
+
20
+ respond_to do |format|
21
+ if msg.error_message.present?
22
+ format.json {render json: {"message" => msg.error_message}, status: :bad_request }
23
+ else
24
+ format.json {render json: {}, status: :ok}
25
+ end
26
+ end
27
+ else
28
+ respond_to do |format|
29
+ format.json {render json: {"message" => @appinstance.drop_message}, status: :bad_request}
30
+ end
31
+ end
32
+ else
33
+ respond_to do |format|
34
+ format.json { render json: { "message" => "Unauthorized"}, status: :unauthorized }
35
+ end
36
+ end
37
+ end
38
+
39
+ def status
40
+
41
+
42
+ end
43
+
44
+ end
45
+ 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,32 @@
1
+ module ZuoraConnect
2
+ class StaticController < ApplicationController
3
+ before_filter :authenticate_connect_app_request, :except => [:metrics, :health, :session_error, :invalid_app_instance_error]
4
+ after_filter :persist_connect_app_session, :except => [:metrics, :health, :session_error, :invalid_app_instance_error]
5
+
6
+ def session_error
7
+ respond_to do |format|
8
+ format.html
9
+ format.json { render json: { message: "Session Error", status: 500 }, status: 500 }
10
+ end
11
+ end
12
+
13
+ def invalid_app_instance_error
14
+ respond_to do |format|
15
+ format.html
16
+ format.json {render json: { message: "Invalid App Instance", status: 500 }, status: 500 }
17
+ end
18
+ end
19
+
20
+ def metrics
21
+ type = params[:type].present? ? params[:type] : "versions"
22
+ render json: ZuoraConnect::AppInstance.get_metrics(type).to_json, status: 200
23
+ end
24
+
25
+ def health
26
+ render json: {
27
+ message: "Alive",
28
+ status: 200
29
+ }, status: 200
30
+ end
31
+ end
32
+ 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,811 @@
1
+ module ZuoraConnect
2
+ require "uri"
3
+ class AppInstanceBase < ActiveRecord::Base
4
+ default_scope {select(ZuoraConnect::AppInstance.column_names.delete_if {|x| ["catalog_mapping", "catalog"].include?(x) }) }
5
+ after_initialize :init
6
+ self.table_name = "zuora_connect_app_instances"
7
+ attr_accessor :options, :mode, :logins, :task_data, :last_refresh, :username, :password, :s3_client, :api_version, :drop_message, :new_session_message, :connect_user
8
+ @@telegraf_host = nil
9
+ REFRESH_TIMEOUT = 2.minute #Used to determine how long to wait on current refresh call before executing another
10
+ INSTANCE_REFRESH_WINDOW = 1.hours #Used to set how how long till app starts attempting to refresh cached task connect data
11
+ INSTANCE_REDIS_CACHE_PERIOD = 24.hours #Used to determine how long to cached task data will live for
12
+ API_LIMIT_TIMEOUT = 2.minutes #Used to set the default for expiring timeout when api rate limiting is in effect
13
+ BLANK_OBJECT_ID_LOOKUP = 'BlankValueSupplied'
14
+
15
+ def init
16
+ self.connect_user = 'Nobody'
17
+ self.options = Hash.new
18
+ self.logins = Hash.new
19
+ self.api_version = "v2"
20
+ self.attr_builder("timezone", ZuoraConnect.configuration.default_time_zone)
21
+ self.attr_builder("locale", ZuoraConnect.configuration.default_locale)
22
+ PaperTrail.whodunnit = "Backend" if defined?(PaperTrail)
23
+ if INSTANCE_REFRESH_WINDOW > INSTANCE_REDIS_CACHE_PERIOD
24
+ raise "The instance refresh window cannot be greater than the instance cache period"
25
+ end
26
+ self.apartment_switch(nil, true)
27
+ end
28
+
29
+ def apartment_switch(method = nil, migrate = false)
30
+ begin
31
+ Apartment::Tenant.switch!(self.id) if self.persisted?
32
+ rescue Apartment::TenantNotFound => ex
33
+ Apartment::Tenant.create(self.id.to_s)
34
+ retry
35
+ end
36
+ if migrate && ActiveRecord::Migrator.needs_migration?
37
+ Apartment::Migrator.migrate(self.id)
38
+ end
39
+ Thread.current[:appinstance] = self
40
+ end
41
+
42
+ def new_session(session: self.data_lookup, username: self.access_token, password: self.refresh_token, holding_pattern: false, log_level: Logger::DEBUG)
43
+ self.api_version = "v2"
44
+ self.username = username
45
+ self.password = password
46
+ self.last_refresh = session["#{self.id}::last_refresh"]
47
+ self.connect_user = session["#{self.id}::user::email"]
48
+ PaperTrail.whodunnit = self.connect_user if defined?(PaperTrail)
49
+
50
+ ## DEV MODE TASK DATA MOCKUP
51
+ if ZuoraConnect.configuration.mode != "Production"
52
+ mock_task_data = {
53
+ "mode" => ZuoraConnect.configuration.dev_mode_mode
54
+ }
55
+
56
+ case ZuoraConnect.configuration.dev_mode_options.class
57
+ when Hash
58
+ self.options = ZuoraConnect.configuration.dev_mode_options
59
+ when Array
60
+ mock_task_data["options"] = ZuoraConnect.configuration.dev_mode_options
61
+ end
62
+
63
+ ZuoraConnect.configuration.dev_mode_logins.each do |k,v|
64
+ v = v.merge({"entities": [] }) if !v.keys.include?("entities")
65
+ mock_task_data[k] = v
66
+ end
67
+
68
+ self.build_task(task_data: mock_task_data, session: session)
69
+ else
70
+ time_expire = (session["#{self.id}::last_refresh"] || Time.now).to_i - INSTANCE_REFRESH_WINDOW.ago.to_i
71
+
72
+ if session.empty?
73
+ self.new_session_message = "REFRESHING - Session Empty"
74
+ raise ZuoraConnect::Exceptions::HoldingPattern if holding_pattern && !self.mark_for_refresh
75
+ self.refresh(session)
76
+
77
+ elsif (self.id != session["appInstance"].to_i)
78
+ self.new_session_message = "REFRESHING - AppInstance ID(#{self.id}) does not match session id(#{session["appInstance"].to_i})"
79
+ raise ZuoraConnect::Exceptions::HoldingPattern if holding_pattern && !self.mark_for_refresh
80
+ self.refresh(session)
81
+
82
+ elsif session["#{self.id}::task_data"].blank?
83
+ self.new_session_message = "REFRESHING - Task Data Blank"
84
+ raise ZuoraConnect::Exceptions::HoldingPattern if holding_pattern && !self.mark_for_refresh
85
+ self.refresh(session)
86
+
87
+ elsif session["#{self.id}::last_refresh"].blank?
88
+ self.new_session_message = "REFRESHING - No Time on Cookie"
89
+ raise ZuoraConnect::Exceptions::HoldingPattern if holding_pattern && !self.mark_for_refresh
90
+ self.refresh(session)
91
+
92
+ # If the cache is expired and we can aquire a refresh lock
93
+ elsif (session["#{self.id}::last_refresh"].to_i < INSTANCE_REFRESH_WINDOW.ago.to_i) && self.mark_for_refresh
94
+ self.new_session_message = "REFRESHING - Session Old by #{time_expire.abs} second"
95
+ self.refresh(session)
96
+ else
97
+ if time_expire < 0
98
+ self.new_session_message = ["REBUILDING - Expired by #{time_expire} seconds", self.marked_for_refresh? ? " cache updating as of #{self.reset_mark_refreshed_at} seconds ago" : nil].compact.join(',')
99
+ else
100
+ self.new_session_message = "REBUILDING - Expires in #{time_expire} seconds"
101
+ end
102
+ self.build_task(task_data: session["#{self.id}::task_data"], session: session)
103
+ end
104
+ end
105
+ begin
106
+ I18n.locale = self.locale
107
+ rescue I18n::InvalidLocale => ex
108
+ Rails.logger.debug("Invalid Locale: #{ex.message}")
109
+ end
110
+ Time.zone = self.timezone
111
+ return self
112
+ rescue ZuoraConnect::Exceptions::HoldingPattern => ex
113
+ while self.marked_for_refresh?
114
+ Rails.logger.add(log_level, "Holding - Expires in #{self.reset_mark_expires_at}")
115
+ sleep(5)
116
+ end
117
+ self.reload_attributes([:refresh_token, :oauth_expires_at, :access_token])
118
+ session = self.data_lookup(session: session)
119
+ retry
120
+
121
+ ensure
122
+ Rails.logger.add(log_level, self.new_session_message)
123
+ end
124
+
125
+ def refresh(session = nil)
126
+ refresh_count ||= 0
127
+ start = Time.now
128
+ response = HTTParty.get(ZuoraConnect.configuration.url + "/api/#{self.api_version}/tools/tasks/#{self.id}.json",:body => {:access_token => self.access_token})
129
+ response_time = Time.now - start
130
+
131
+ Rails.logger.debug("[#{self.id}] REFRESH TASK - Connect Task Info Request Time #{response_time.round(2).to_s}")
132
+ if response.code == 200
133
+ self.build_task(task_data: JSON.parse(response.body), session: session)
134
+ self.last_refresh = Time.now.to_i
135
+ self.cache_app_instance
136
+ self.reset_mark_for_refresh
137
+ else
138
+ Rails.logger.fatal("[#{self.id}] REFRESH TASK - Failed Code #{response.code}")
139
+ raise ZuoraConnect::Exceptions::ConnectCommunicationError.new("Error Communicating with Connect", response.body, response.code)
140
+ end
141
+ rescue Net::ReadTimeout, Net::OpenTimeout, Errno::EPIPE, Errno::ECONNRESET, Errno::ECONNREFUSED, SocketError => ex
142
+ if (refresh_count += 1) < 3
143
+ Rails.logger.info("[#{self.id}] REFRESH TASK - #{ex.class} Retrying(#{refresh_count})")
144
+ retry
145
+ else
146
+ Rails.logger.fatal("[#{self.id}] REFRESH TASK - #{ex.class} Failed #{refresh_count}x")
147
+ raise
148
+ end
149
+ rescue ZuoraConnect::Exceptions::ConnectCommunicationError => ex
150
+ if (refresh_count += 1) < 3
151
+ Rails.logger.info("[#{self.id}] REFRESH TASK - Failed Retrying(#{refresh_count})")
152
+ if ex.code == 401
153
+ self.refresh_oauth
154
+ end
155
+ retry
156
+ else
157
+ Rails.logger.fatal("[#{self.id}] REFRESH TASK - Failed #{refresh_count}x")
158
+ raise
159
+ end
160
+ end
161
+
162
+ #### START Metrics Mathods ####
163
+ def self.write_to_telegraf(*args)
164
+ if ZuoraConnect.configuration.enable_metrics
165
+ @@telegraf_host = ZuoraConnect::Telegraf.new() if @@telegraf_host == nil
166
+ return @@telegraf_host.write(*args)
167
+ end
168
+ end
169
+
170
+ def self.get_metrics(type)
171
+ namespace = ENV['DEIS_APP'].present? ? "#{ENV['DEIS_APP']}" : "#{Rails.application.class.parent_name}"
172
+
173
+ @data = {}
174
+
175
+ if type == "versions"
176
+ @data = {
177
+ app_name: namespace,
178
+ url: "dummy",
179
+ Version_Gem: ZuoraConnect::VERSION,
180
+ Version_Zuora: ZuoraAPI::VERSION ,
181
+ Version_Ruby: RUBY_VERSION,
182
+ Version_Rails: Rails.version,
183
+ hold: 1
184
+ }
185
+ elsif type == "stats"
186
+ begin
187
+ Resque.redis.ping
188
+ @data = {
189
+ app_name: namespace,
190
+ url: "dummy",
191
+ Resque:{
192
+ Jobs_Finished: Resque.info[:processed] ,
193
+ Jobs_Failed: Resque.info[:failed],
194
+ Jobs_Pending: Resque.info[:pending],
195
+ Workers_Active: Resque.info[:working],
196
+ Workers_Total: Resque.info[:workers]
197
+ }
198
+ }
199
+ rescue
200
+ end
201
+ end
202
+ return @data
203
+ end
204
+ #### END Task Mathods ####
205
+
206
+ #### START Task Mathods ####
207
+ def build_task(task_data: {}, session: {})
208
+ self.task_data = task_data
209
+ self.mode = self.task_data["mode"]
210
+ self.task_data.each do |k,v|
211
+ if k.match(/^(.*)_login$/)
212
+ tmp = ZuoraConnect::Login.new(v)
213
+ if v["tenant_type"] == "Zuora"
214
+ if tmp.entities.size > 0
215
+ tmp.entities.each do |value|
216
+ entity_id = value["id"]
217
+ tmp.client(entity_id).current_session = session["#{self.id}::#{k}::#{entity_id}:current_session"] if session["#{self.id}::#{k}::#{entity_id}:current_session"]
218
+ tmp.client(entity_id).bearer_token = session["#{self.id}::#{k}::#{entity_id}:bearer_token"] if session["#{self.id}::#{k}::#{entity_id}:bearer_token"]
219
+ 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"]
220
+ end
221
+ else
222
+ tmp.client.current_session = session["#{self.id}::#{k}:current_session"] if session["#{self.id}::#{k}:current_session"]
223
+ 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
224
+ 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)
225
+ end
226
+ self.logins[k] = tmp
227
+ self.attr_builder(k, @logins[k])
228
+ end
229
+ elsif k == "options"
230
+ v.each do |opt|
231
+ self.options[opt["config_name"]] = opt
232
+ end
233
+ elsif k == "user_settings"
234
+ self.timezone = v["timezone"]
235
+ self.locale = v["local"]
236
+ end
237
+ end
238
+ rescue => ex
239
+ Rails.logger.error("Task Data: #{task_data}")
240
+ Rails.logger.error("Task Session: #{session}")
241
+ raise
242
+ end
243
+
244
+ def updateOption(optionId, value)
245
+ response = HTTParty.get(ZuoraConnect.configuration.url + "/api/#{self.api_version}/tools/application_options/#{optionId}/edit?value=#{value}",:body => {:access_token => self.username})
246
+ end
247
+
248
+ #This can update an existing login, add a new login, change to another existing login
249
+ #EXAMPLE: {"name": "ftp_login_14","username": "ftplogin7","tenant_type": "Custom","password": "test2","url": "www.ftp.com","custom_data": { "path": "/var/usr/test"}}
250
+ def update_logins(options)
251
+ update_login_count ||= 0
252
+ response = HTTParty.post(ZuoraConnect.configuration.url + "/api/#{self.api_version}/tools/tasks/#{self.id}/logins",:body => {:access_token => self.username}.merge(options))
253
+ parsed_json = JSON.parse(response.body)
254
+ if response.code == 200
255
+ if defined?(Redis.current)
256
+ self.build_task(task_data: parsed_json, session: self.data_lookup)
257
+ self.last_refresh = Time.now.to_i
258
+ self.cache_app_instance
259
+ end
260
+ return parsed_json
261
+ elsif response.code == 400
262
+ raise ZuoraConnect::Exceptions::APIError.new(message: parsed_json['errors'].join(' '), response: response.body, code: response.code)
263
+ else
264
+ raise ZuoraConnect::Exceptions::ConnectCommunicationError.new("Error Communicating with Connect", response.body, response.code)
265
+ end
266
+ rescue Net::ReadTimeout, Net::OpenTimeout, Errno::EPIPE, Errno::ECONNRESET, Errno::ECONNREFUSED, SocketError => ex
267
+ if (update_login_count += 1) < 3
268
+ retry
269
+ else
270
+ raise
271
+ end
272
+ rescue ZuoraConnect::Exceptions::ConnectCommunicationError => ex
273
+ if (update_login_count += 1) < 3
274
+ if ex.code == 401
275
+ self.refresh_oauth
276
+ end
277
+ retry
278
+ else
279
+ raise
280
+ end
281
+ end
282
+ #### END Task Mathods ####
283
+
284
+ #### START Connect OAUTH methods ####
285
+ def check_oauth_state(method)
286
+ #Refresh token if already expired
287
+ if self.oauth_expired?
288
+ Rails.logger.debug("[#{self.id}] Before '#{method}' method, Oauth expired")
289
+ self.refresh_oauth
290
+ end
291
+ end
292
+
293
+ def oauth_expired?
294
+ return self.oauth_expires_at.present? ? (self.oauth_expires_at < Time.now) : true
295
+ end
296
+
297
+ def refresh_oauth
298
+ refresh_oauth_count ||= 0
299
+ start = Time.now
300
+ params = {
301
+ :grant_type => "refresh_token",
302
+ :redirect_uri => ZuoraConnect.configuration.oauth_client_redirect_uri,
303
+ :refresh_token => self.refresh_token
304
+ }
305
+ response = HTTParty.post("#{ZuoraConnect.configuration.url}/oauth/token",:body => params)
306
+ response_time = Time.now - start
307
+ Rails.logger.info("[#{self.id}] REFRESH OAUTH - In #{response_time.round(2).to_s}")
308
+
309
+ if response.code == 200
310
+ response_body = JSON.parse(response.body)
311
+
312
+ self.refresh_token = response_body["refresh_token"]
313
+ self.access_token = response_body["access_token"]
314
+ self.oauth_expires_at = Time.at(response_body["created_at"].to_i) + response_body["expires_in"].seconds
315
+ self.save(:validate => false)
316
+ else
317
+ Rails.logger.fatal("[#{self.id}] REFRESH OAUTH - Failed Code #{response.code}")
318
+ raise ZuoraConnect::Exceptions::ConnectCommunicationError.new("Error Refreshing Access Token", response.body, response.code)
319
+ end
320
+ rescue Net::ReadTimeout, Net::OpenTimeout, Errno::EPIPE, Errno::ECONNRESET, Errno::ECONNREFUSED, SocketError => ex
321
+ if (refresh_oauth_count += 1) < 3
322
+ Rails.logger.info("[#{self.id}] REFRESH OAUTH - #{ex.class} Retrying(#{refresh_oauth_count})")
323
+ retry
324
+ else
325
+ Rails.logger.fatal("[#{self.id}] REFRESH OAUTH - #{ex.class} Failed #{refresh_oauth_count}x")
326
+ raise
327
+ end
328
+ rescue ZuoraConnect::Exceptions::ConnectCommunicationError => ex
329
+ sleep(5)
330
+ self.reload_attributes([:refresh_token, :oauth_expires_at, :access_token]) #Reload only the refresh token for retry
331
+
332
+ #After reload, if nolonger expired return
333
+ return if !self.oauth_expired?
334
+
335
+ if (refresh_oauth_count += 1) < 3
336
+ Rails.logger.info("[#{self.id}] REFRESH OAUTH - Failed Retrying(#{refresh_oauth_count})")
337
+ retry
338
+ else
339
+ Rails.logger.fatal("[#{self.id}] REFRESH OAUTH - Failed #{refresh_oauth_count}x")
340
+ raise
341
+ end
342
+ end
343
+ #### END Connect OAUTH methods ####
344
+
345
+ #### START AppInstance Temporary Persistance Methods ####
346
+ def marked_for_refresh?
347
+ return defined?(Redis.current) ? Redis.current.get("AppInstance:#{self.id}:Refreshing").to_bool : false
348
+ end
349
+
350
+ def reset_mark_for_refresh
351
+ Redis.current.del("AppInstance:#{self.id}:Refreshing") if defined?(Redis.current)
352
+ end
353
+
354
+ def reset_mark_refreshed_at
355
+ return defined?(Redis.current) ? REFRESH_TIMEOUT.to_i - Redis.current.ttl("AppInstance:#{self.id}:Refreshing") : 0
356
+ end
357
+
358
+ def reset_mark_expires_at
359
+ return defined?(Redis.current) ? Redis.current.ttl("AppInstance:#{self.id}:Refreshing") : 0
360
+ end
361
+
362
+ def mark_for_refresh
363
+ return defined?(Redis.current) ? Redis.current.set("AppInstance:#{self.id}:Refreshing", true, {:nx => true, :ex => REFRESH_TIMEOUT.to_i}) : true
364
+ end
365
+
366
+ def data_lookup(session: {})
367
+ if defined?(Redis.current)
368
+ cached_instance = Redis.current.get("AppInstance:#{self.id}")
369
+ if cached_instance.blank?
370
+ Rails.logger.debug("[#{self.id}] Cached AppInstance Missing")
371
+ return session
372
+ else
373
+ Rails.logger.debug("[#{self.id}] Cached AppInstance Found")
374
+ return decrypt_data(data: cached_instance, rescue_return: session).merge(session)
375
+ end
376
+ else
377
+ return session
378
+ end
379
+ end
380
+
381
+ def cache_app_instance
382
+ if defined?(Redis.current)
383
+ #Task data must be present and the last refresh cannot be old. We dont want to overwite new cache data with old
384
+ if self.task_data.present? && (self.last_refresh.to_i > INSTANCE_REFRESH_WINDOW.ago.to_i)
385
+ Rails.logger.debug("[#{self.id}] Caching AppInstance")
386
+ Redis.current.setex("AppInstance:#{self.id}", INSTANCE_REDIS_CACHE_PERIOD.to_i, encrypt_data(data: self.save_data))
387
+ end
388
+ Redis.current.del("Deleted:#{self.id}")
389
+ end
390
+ end
391
+
392
+ def save_data(session = Hash.new)
393
+ self.logins.each do |key, login|
394
+ if login.tenant_type == "Zuora"
395
+ if login.available_entities.size > 1 && Rails.application.config.session_store != ActionDispatch::Session::CookieStore
396
+ login.available_entities.each do |entity_key|
397
+ session["#{self.id}::#{key}::#{entity_key}:current_session"] = login.client(entity_key).current_session if login.client.respond_to?(:current_session)
398
+ session["#{self.id}::#{key}::#{entity_key}:bearer_token"] = login.client(entity_key).bearer_token if login.client.respond_to?(:bearer_token)
399
+ 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)
400
+ end
401
+ else
402
+ session["#{self.id}::#{key}:current_session"] = login.client.current_session if login.client.respond_to?(:current_session)
403
+ session["#{self.id}::#{key}:bearer_token"] = login.client.bearer_token if login.client.respond_to?(:bearer_token)
404
+ session["#{self.id}::#{key}:oauth_session_expires_at"] = login.client.oauth_session_expires_at if login.client.respond_to?(:oauth_session_expires_at)
405
+ end
406
+ end
407
+ end
408
+
409
+ session["#{self.id}::task_data"] = self.task_data
410
+
411
+ #Redis is not defined strip out old data
412
+ if !defined?(Redis.current)
413
+ session["#{self.id}::task_data"].delete('applications')
414
+ session["#{self.id}::task_data"].select {|k,v| k.include?('login') && v['tenant_type'] == 'Zuora'}.each do |login_key, login_data|
415
+ session["#{self.id}::task_data"][login_key]['entities'] = (login_data.dig('entities') || []).map {|entity| entity.slice('id', 'tenantId')}
416
+ end
417
+ end
418
+
419
+ session["#{self.id}::last_refresh"] = self.last_refresh
420
+ session["appInstance"] = self.id
421
+ return session
422
+ end
423
+
424
+ def encryptor
425
+ # Default values for Rails 4 apps
426
+ key_iter_num, key_size, salt, signed_salt = [1000, 64, "encrypted cookie", "signed encrypted cookie"]
427
+ key_generator = ActiveSupport::KeyGenerator.new(Rails.application.secrets.secret_key_base, iterations: key_iter_num)
428
+ secret, sign_secret = [key_generator.generate_key(salt), key_generator.generate_key(signed_salt)]
429
+ return ActiveSupport::MessageEncryptor.new(secret, sign_secret)
430
+ end
431
+
432
+ def decrypt_data(data: nil, rescue_return: nil)
433
+ return data if data.blank?
434
+ begin
435
+ if Rails.env == 'development'
436
+ return JSON.parse(data)
437
+ else
438
+ begin
439
+ return JSON.parse(encryptor.decrypt_and_verify(CGI::unescape(data)))
440
+ rescue ActiveSupport::MessageVerifier::InvalidSignature => ex
441
+ Rails.logger.fatal('Error Decrypting')
442
+ return rescue_return
443
+ end
444
+ end
445
+ rescue JSON::ParserError => ex
446
+ Rails.logger.fatal('Error Parsing')
447
+ return rescue_return
448
+ end
449
+ end
450
+
451
+ def encrypt_data(data: nil)
452
+ return data if data.blank?
453
+ if Rails.env == 'development'
454
+ return data.to_json
455
+ else
456
+ return encryptor.encrypt_and_sign(data.to_json)
457
+ end
458
+ end
459
+ #### END AppInstance Temporary Persistance Methods ####
460
+
461
+ ### START Resque Helping Methods ####
462
+ def api_limit(start: true, time: API_LIMIT_TIMEOUT.to_i)
463
+ if start
464
+ Redis.current.setex("APILimits:#{self.id}", time, true)
465
+ else
466
+ Redis.current.del("APILimits:#{self.id}")
467
+ end
468
+ end
469
+
470
+ def api_limit?
471
+ return Redis.current.get("APILimits:#{self.id}").to_bool
472
+ end
473
+
474
+ def queue_paused?
475
+ return Redis.current.get("resque:PauseQueue:#{self.id}").present?
476
+ end
477
+
478
+ def queue_pause(time: nil, current_user: 'Default')
479
+ if time.present?
480
+ raise "Time must be fixnum of seconds." if time.class != Fixnum
481
+ Redis.current.setex("resque:PauseQueue:#{self.id}", time, current_user)
482
+ else
483
+ Redis.current.set("resque:PauseQueue:#{self.id}", current_user)
484
+ end
485
+ end
486
+
487
+ def queue_start(current_user: 'Default')
488
+ paused_user = Redis.current.get("resque:PauseQueue:#{self.id}")
489
+ if paused_user == current_user || paused_user.blank?
490
+ Redis.current.del("resque:PauseQueue:#{self.id}")
491
+ else
492
+ raise "Can only unpause for user #{paused_user}."
493
+ end
494
+ end
495
+ ### END Resque Helping Methods ####
496
+
497
+ ### START Catalog Helping Methods #####
498
+ def get_catalog(page_size: 5, zuora_login: self.login_lookup(type: "Zuora").first, entity_id: nil)
499
+ self.update_column(:catalog_update_attempt_at, Time.now.utc)
500
+
501
+ entity_reference = entity_id.blank? ? 'Default' : entity_id
502
+ Rails.logger.debug("Fetch Catalog")
503
+ Rails.logger.debug("Zuora Entity: #{entity_id.blank? ? 'default' : entity_id}")
504
+
505
+ login = zuora_login.client(entity_reference)
506
+
507
+ old_logger = ActiveRecord::Base.logger
508
+ ActiveRecord::Base.logger = nil
509
+ 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})
510
+
511
+ response = {'nextPage' => login.rest_endpoint("catalog/products?pageSize=#{page_size}")}
512
+ while !response["nextPage"].blank?
513
+ url = login.rest_endpoint(response["nextPage"].split('/v1/').last)
514
+ Rails.logger.debug("Fetch Catalog URL #{url}")
515
+ output_json, response = login.rest_call(:debug => false, :url => url, :errors => [ZuoraAPI::Exceptions::ZuoraAPISessionError], :timeout_retry => true)
516
+ Rails.logger.debug("Fetch Catalog Response Code #{response.code}")
517
+
518
+ if !output_json['success'] =~ (/(true|t|yes|y|1)$/i) || output_json['success'].class != TrueClass
519
+ Rails.logger.error("Fetch Catalog DATA #{output_json.to_json}")
520
+ raise ZuoraAPI::Exceptions::ZuoraAPIError.new("Error Getting Catalog: #{output_json}")
521
+ end
522
+
523
+ output_json["products"].each do |product|
524
+ 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])
525
+ rateplans = {}
526
+
527
+ product["productRatePlans"].each do |rateplan|
528
+ 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])
529
+ charges = {}
530
+
531
+ rateplan["productRatePlanCharges"].each do |charge|
532
+ 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])
533
+
534
+ charges[charge["id"]] = charge.merge({"productId" => product["id"], "productName" => product["name"], "productRatePlanId" => rateplan["id"], "productRatePlanName" => rateplan["name"] })
535
+ end
536
+
537
+ rateplan["productRatePlanCharges"] = charges
538
+ rateplans[rateplan["id"]] = rateplan.merge({"productId" => product["id"], "productName" => product["name"]})
539
+ end
540
+ product["productRatePlans"] = rateplans
541
+
542
+ 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])
543
+ end
544
+ end
545
+
546
+ # Move from tmp to actual
547
+ 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})
548
+ if defined?(Redis.current)
549
+ Redis.current.keys("Catalog:#{self.id}:*").each do |key|
550
+ Redis.current.del(key.to_s)
551
+ end
552
+ end
553
+ # Clear tmp holder
554
+ 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})
555
+
556
+ ActiveRecord::Base.logger = old_logger
557
+ self.update_column(:catalog_updated_at, Time.now.utc)
558
+ self.touch
559
+
560
+ # DO NOT RETURN CATALOG. THIS IS NOT SCALABLE WITH LARGE CATALOGS. USE THE CATALOG_LOOKUP method provided
561
+ return true
562
+ end
563
+
564
+ def catalog_outdated?(time: Time.now - 12.hours)
565
+ return self.catalog_updated_at.blank? || (self.catalog_updated_at < time)
566
+ end
567
+
568
+ def catalog_loaded?
569
+ return ActiveRecord::Base.connection.execute('SELECT id FROM "public"."zuora_connect_app_instances" WHERE "id" = %s AND catalog = \'{}\' LIMIT 1' % [self.id]).first.nil?
570
+ end
571
+
572
+ # Catalog lookup provides method to lookup zuora catalog efficiently.
573
+ # entity_id: If the using catalog json be field to store multiple entity product catalogs.
574
+ # object: The Object class desired to be returned. Available [:product, :rateplan, :charge]
575
+ # object_id: The id or id's of the object/objects to be returned.
576
+ # child_objects: Whether to include child objects of the object in question.
577
+ # cache: Store individual "1" object lookup in redis for caching.
578
+ def catalog_lookup(entity_id: nil, object: :product, object_id: nil, child_objects: false, cache: false)
579
+ entity_reference = entity_id.blank? ? 'Default' : entity_id
580
+
581
+ if object_id.present? && ![Array, String].include?(object_id.class)
582
+ raise "Object Id can only be a string or an array of strings"
583
+ end
584
+
585
+ if defined?(Redis.current) && object_id.present? && object_id.class == String && object_id.present?
586
+ stub_catalog = decrypt_data(data: Redis.current.get("Catalog:#{self.id}:#{object_id}:Children:#{child_objects}"))
587
+ object_hierarchy = decrypt_data(data: Redis.current.get("Catalog:#{self.id}:#{object_id}:Hierarchy"))
588
+ end
589
+
590
+ if defined?(object_hierarchy)
591
+ 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"})
592
+ end
593
+
594
+ case object
595
+ when :product
596
+ if object_id.nil?
597
+ string =
598
+ "SELECT "\
599
+ "json_object_agg(product_id, product #{child_objects ? '' : '- \'productRatePlans\''}) AS item "\
600
+ "FROM "\
601
+ "\"public\".\"zuora_connect_app_instances\", "\
602
+ "jsonb_each((\"public\".\"zuora_connect_app_instances\".\"catalog\" #> '{%s}' )) AS e(product_id, product) "\
603
+ "WHERE "\
604
+ "\"id\" = %s" % [entity_reference, self.id]
605
+ else
606
+ if object_id.class == String
607
+ string =
608
+ "SELECT "\
609
+ "(catalog #> '{%s, %s}') #{child_objects ? '' : '- \'productRatePlans\''} AS item "\
610
+ "FROM "\
611
+ "\"public\".\"zuora_connect_app_instances\" "\
612
+ "WHERE "\
613
+ "\"id\" = %s" % [entity_reference, object_id.blank? ? BLANK_OBJECT_ID_LOOKUP : object_id, self.id]
614
+ elsif object_id.class == Array
615
+ string =
616
+ "SELECT "\
617
+ "json_object_agg(product_id, product #{child_objects ? '' : '- \'productRatePlans\''}) AS item "\
618
+ "FROM "\
619
+ "\"public\".\"zuora_connect_app_instances\", "\
620
+ "jsonb_each((\"public\".\"zuora_connect_app_instances\".\"catalog\" #> '{%s}' )) AS e(product_id, product) "\
621
+ "WHERE "\
622
+ "\"product_id\" IN (\'%s\') AND "\
623
+ "\"id\" = %s" % [entity_reference, object_id.join("\',\'"), self.id]
624
+ end
625
+ end
626
+
627
+ when :rateplan
628
+ if object_id.nil?
629
+ string =
630
+ "SELECT "\
631
+ "json_object_agg(rateplan_id, rateplan #{child_objects ? '' : '- \'productRatePlanCharges\''}) AS item "\
632
+ "FROM "\
633
+ "\"public\".\"zuora_connect_app_instances\", "\
634
+ "jsonb_each((\"public\".\"zuora_connect_app_instances\".\"catalog\" #> '{%s}' )) AS e(product_id, product), "\
635
+ "jsonb_each(product #> '{productRatePlans}') AS ee(rateplan_id, rateplan) "\
636
+ "WHERE "\
637
+ "\"id\" = %s" % [entity_reference, self.id]
638
+ else
639
+ if object_id.class == String
640
+ string =
641
+ "SELECT "\
642
+ "(catalog #> '{%s, %s, productRatePlans, %s}') #{child_objects ? '' : '- \'productRatePlanCharges\''} AS item "\
643
+ "FROM "\
644
+ "\"public\".\"zuora_connect_app_instances\" "\
645
+ "WHERE "\
646
+ "\"id\" = %s" % [entity_reference, object_hierarchy['productId'], object_id.blank? ? BLANK_OBJECT_ID_LOOKUP : object_id, self.id]
647
+ elsif object_id.class == Array
648
+ string =
649
+ "SELECT "\
650
+ "json_object_agg(rateplan_id, rateplan #{child_objects ? '' : '- \'productRatePlanCharges\''}) AS item "\
651
+ "FROM "\
652
+ "\"public\".\"zuora_connect_app_instances\", "\
653
+ "jsonb_each((\"public\".\"zuora_connect_app_instances\".\"catalog\" #> '{%s}' )) AS e(product_id, product), "\
654
+ "jsonb_each(product #> '{productRatePlans}') AS ee(rateplan_id, rateplan) "\
655
+ "WHERE "\
656
+ "\"rateplan_id\" IN (\'%s\') AND "\
657
+ "\"id\" = %s" % [entity_reference, object_id.join("\',\'"), self.id]
658
+ end
659
+ end
660
+
661
+ when :charge
662
+ if object_id.nil?
663
+ string =
664
+ "SELECT "\
665
+ "json_object_agg(charge_id, charge) as item "\
666
+ "FROM "\
667
+ "\"public\".\"zuora_connect_app_instances\", "\
668
+ "jsonb_each((\"public\".\"zuora_connect_app_instances\".\"catalog\" #> '{%s}' )) AS e(product_id, product), "\
669
+ "jsonb_each(product #> '{productRatePlans}') AS ee(rateplan_id, rateplan), "\
670
+ "jsonb_each(rateplan #> '{productRatePlanCharges}') AS eee(charge_id, charge) "\
671
+ "WHERE "\
672
+ "\"id\" = %s" % [entity_reference, self.id]
673
+ else
674
+ if object_id.class == String
675
+ string =
676
+ "SELECT "\
677
+ "catalog #> '{%s, %s, productRatePlans, %s, productRatePlanCharges, %s}' AS item "\
678
+ "FROM "\
679
+ "\"public\".\"zuora_connect_app_instances\" "\
680
+ "WHERE "\
681
+ "\"id\" = %s" % [entity_reference, object_hierarchy['productId'], object_hierarchy['productRatePlanId'], object_id.blank? ? BLANK_OBJECT_ID_LOOKUP : object_id , self.id]
682
+
683
+ elsif object_id.class == Array
684
+ string =
685
+ "SELECT "\
686
+ "json_object_agg(charge_id, charge) AS item "\
687
+ "FROM "\
688
+ "\"public\".\"zuora_connect_app_instances\", "\
689
+ "jsonb_each((\"public\".\"zuora_connect_app_instances\".\"catalog\" #> '{%s}' )) AS e(product_id, product), "\
690
+ "jsonb_each(product #> '{productRatePlans}') AS ee(rateplan_id, rateplan), "\
691
+ "jsonb_each(rateplan #> '{productRatePlanCharges}') AS eee(charge_id, charge) "\
692
+ "WHERE "\
693
+ "\"charge_id\" IN (\'%s\') AND "\
694
+ "\"id\" = %s" % [entity_reference, object_id.join("\',\'"), self.id]
695
+ end
696
+ end
697
+ else
698
+ raise "Available objects include [:product, :rateplan, :charge]"
699
+ end
700
+
701
+ stub_catalog ||= JSON.parse(ActiveRecord::Base.connection.execute(string).first["item"] || "{}")
702
+
703
+ if defined?(Redis.current) && object_id.present? && object_id.class == String && object_id.present?
704
+ Redis.current.set("Catalog:#{self.id}:#{object_id}:Hierarchy", encrypt_data(data: object_hierarchy))
705
+ Redis.current.set("Catalog:#{self.id}:#{object_id}:Children:#{child_objects}", encrypt_data(data: stub_catalog)) if cache
706
+ end
707
+ return stub_catalog
708
+ end
709
+ ### END Catalog Helping Methods #####
710
+
711
+ ### START S3 Helping Methods #####
712
+ def s3_client
713
+ require 'aws-sdk-s3'
714
+ if ZuoraConnect.configuration.mode == "Development"
715
+ @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)
716
+ else
717
+ @s3_client ||= Aws::S3::Resource.new(region: ZuoraConnect.configuration.aws_region)
718
+ end
719
+ end
720
+
721
+ def upload_to_s3(local_file,s3_path = nil)
722
+ s3_path = local_file.split("/").last if s3_path.nil?
723
+ obj = self.s3_client.bucket(ZuoraConnect.configuration.s3_bucket_name).object("#{ZuoraConnect.configuration.s3_folder_name}/#{self.id.to_s}/#{s3_path}}")
724
+ obj.upload_file(local_file, :server_side_encryption => 'AES256')
725
+ end
726
+
727
+ def get_s3_file_url(key)
728
+ require 'aws-sdk-s3'
729
+ signer = Aws::S3::Presigner.new(client: self.s3_client)
730
+ url = signer.presigned_url(:get_object, bucket: ZuoraConnect.configuration.s3_bucket_name, key: "#{ZuoraConnect.configuration.s3_folder_name}/#{self.id.to_s}/#{key}")
731
+ end
732
+ ### END S3 Helping Methods #####
733
+
734
+ ### START Aggregate Grouping Helping Methods ####
735
+ def self.refresh_aggregate_table(aggregate_name: 'all_tasks_processing', table_name: 'tasks', where_clause: "where status in ('Processing', 'Queued')", index_table: true)
736
+ self.update_functions
737
+ #Broke function into two parts to ensure transaction size was small enough
738
+ ActiveRecord::Base.connection.execute('SELECT "shared_extensions".refresh_aggregate_table(\'%s\', \'%s\', %s, \'Table\');' % [aggregate_name, table_name, ActiveRecord::Base.connection.quote(where_clause)])
739
+ 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
740
+ end
741
+
742
+ def self.update_functions
743
+ ActiveRecord::Base.connection.execute(File.read("#{Gem.loaded_specs["zuora_connect"].gem_dir}/app/views/sql/refresh_aggregate_table.txt"))
744
+ end
745
+ ### END Aggregate Grouping Helping Methods #####
746
+
747
+ # Overide this method to avoid the new session call for api requests that use the before filter authenticate_app_api_request.
748
+ # This can be usefull for apps that dont need connect metadata call, or credentials, to operate for api requests
749
+ def new_session_for_api_requests(params: {})
750
+ return true
751
+ end
752
+
753
+ # Overide this method to avoid the new session call for ui requests that use the before filter authenticate_connect_app_request.
754
+ # This can be usefull for apps that dont need connect metadata call, or credentials, to operate for ui requests
755
+ def new_session_for_ui_requests(params: {})
756
+ return true
757
+ end
758
+
759
+ #Method for overiding droping of an app instance
760
+ def drop_instance
761
+ self.drop_message = 'Ok to drop'
762
+ return true
763
+ end
764
+
765
+ def reload_attributes(selected_attributes)
766
+ raise "Attibutes must be array" if selected_attributes.class != Array
767
+ value_attributes = self.class.unscoped.where(:id=>id).select(selected_attributes).first.attributes
768
+ value_attributes.each do |key, value|
769
+ next if key == "id" && value.blank?
770
+ self.send(:write_attribute, key, value)
771
+ end
772
+ return self
773
+ end
774
+
775
+ def instance_failure(failure)
776
+ raise failure
777
+ end
778
+
779
+ def send_email
780
+ end
781
+
782
+ def login_lookup(type: "Zuora")
783
+ results = []
784
+ self.logins.each do |name, login|
785
+ results << login if login.tenant_type == type
786
+ end
787
+ return results
788
+ end
789
+
790
+ def self.decrypt_response(resp)
791
+ OpenSSL::PKey::RSA.new(ZuoraConnect.configuration.private_key).private_decrypt(resp)
792
+ end
793
+
794
+ def attr_builder(field,val)
795
+ singleton_class.class_eval { attr_accessor "#{field}" }
796
+ send("#{field}=", val)
797
+ end
798
+
799
+ def method_missing(method_sym, *arguments, &block)
800
+ if method_sym.to_s.include?("login")
801
+ Rails.logger.fatal("Method Missing #{method_sym}")
802
+ Rails.logger.fatal("Instance Data: #{self.task_data}")
803
+ Rails.logger.fatal("Instance Logins: #{self.logins}")
804
+ end
805
+ super
806
+ end
807
+
808
+ method_hook :refresh, :updateOption, :update_logins, :before => :check_oauth_state
809
+ method_hook :new_session, :refresh, :build_task, :after => :apartment_switch
810
+ end
811
+ end