zuora_connectD 1.7.09

Sign up to get free protection for your applications and to get access to all the features.
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