zuora_connect 0

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