zuora_connect 1.5.30 → 1.5.32

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