zuora_connect 1.5.30 → 1.5.32

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