intercom 1.0.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.travis.yml +0 -8
- data/Gemfile +3 -0
- data/README.md +208 -52
- data/changes.txt +3 -0
- data/intercom.gemspec +2 -2
- data/lib/ext/hash.rb +18 -0
- data/lib/intercom.rb +38 -43
- data/lib/intercom/api_operations/count.rb +16 -0
- data/lib/intercom/api_operations/delete.rb +15 -0
- data/lib/intercom/api_operations/find.rb +22 -0
- data/lib/intercom/api_operations/find_all.rb +33 -0
- data/lib/intercom/api_operations/list.rb +17 -0
- data/lib/intercom/api_operations/load.rb +15 -0
- data/lib/intercom/api_operations/save.rb +44 -0
- data/lib/intercom/collection_proxy.rb +66 -0
- data/lib/intercom/company.rb +29 -0
- data/lib/intercom/conversation.rb +15 -0
- data/lib/intercom/count.rb +21 -0
- data/lib/intercom/errors.rb +52 -0
- data/lib/intercom/event.rb +4 -101
- data/lib/intercom/extended_api_operations/reply.rb +16 -0
- data/lib/intercom/extended_api_operations/tags.rb +14 -0
- data/lib/intercom/extended_api_operations/users.rb +17 -0
- data/lib/intercom/generic_handlers/base_handler.rb +22 -0
- data/lib/intercom/generic_handlers/count.rb +59 -0
- data/lib/intercom/generic_handlers/tag.rb +71 -0
- data/lib/intercom/generic_handlers/tag_find_all.rb +47 -0
- data/lib/intercom/lib/dynamic_accessors.rb +59 -0
- data/lib/intercom/lib/dynamic_accessors_on_method_missing.rb +53 -0
- data/lib/intercom/lib/flat_store.rb +31 -0
- data/lib/intercom/lib/typed_json_deserializer.rb +52 -0
- data/lib/intercom/message.rb +9 -0
- data/lib/intercom/note.rb +14 -42
- data/lib/intercom/request.rb +40 -4
- data/lib/intercom/segment.rb +14 -0
- data/lib/intercom/tag.rb +19 -78
- data/lib/intercom/traits/api_resource.rb +120 -0
- data/lib/intercom/traits/dirty_tracking.rb +33 -0
- data/lib/intercom/traits/generic_handler_binding.rb +29 -0
- data/lib/intercom/traits/incrementable_attributes.rb +23 -0
- data/lib/intercom/user.rb +25 -361
- data/lib/intercom/utils.rb +50 -0
- data/lib/intercom/version.rb +1 -1
- data/spec/spec_helper.rb +64 -33
- data/spec/unit/intercom/collection_proxy_spec.rb +34 -0
- data/spec/unit/intercom/event_spec.rb +25 -0
- data/spec/unit/intercom/{flat_store_spec.rb → lib/flat_store_spec.rb} +7 -7
- data/spec/unit/intercom/note_spec.rb +5 -4
- data/spec/unit/intercom/tag_spec.rb +3 -3
- data/spec/unit/intercom/traits/api_resource_spec.rb +79 -0
- data/spec/unit/intercom/user_spec.rb +101 -119
- data/spec/unit/intercom_spec.rb +7 -7
- metadata +50 -26
- data/lib/intercom/flat_store.rb +0 -27
- data/lib/intercom/hashable_object.rb +0 -22
- data/lib/intercom/impression.rb +0 -63
- data/lib/intercom/message_thread.rb +0 -189
- data/lib/intercom/requires_parameters.rb +0 -10
- data/lib/intercom/social_profile.rb +0 -24
- data/lib/intercom/unix_timestamp_unwrapper.rb +0 -12
- data/lib/intercom/user_collection_proxy.rb +0 -52
- data/lib/intercom/user_resource.rb +0 -82
- data/spec/integration/fixtures/v1-user.json +0 -45
- data/spec/integration/fixtures/v1-users-impression.json +0 -3
- data/spec/integration/fixtures/v1-users-message_thread.json +0 -44
- data/spec/integration/fixtures/v1-users-message_threads.json +0 -46
- data/spec/integration/fixtures/v1-users-note.json +0 -49
- data/spec/integration/fixtures/v1-users.json +0 -144
- data/spec/integration/intercom_api_integration_spec.rb +0 -134
- data/spec/unit/intercom/impression_spec.rb +0 -18
- data/spec/unit/intercom/message_thread_spec.rb +0 -74
- data/spec/unit/intercom/user_collection_proxy_spec.rb +0 -46
- data/spec/unit/intercom/user_event_spec.rb +0 -83
- data/spec/unit/intercom/user_resource_spec.rb +0 -13
@@ -0,0 +1,23 @@
|
|
1
|
+
module Intercom
|
2
|
+
module Traits
|
3
|
+
module IncrementableAttributes
|
4
|
+
|
5
|
+
def increment(key, value=1)
|
6
|
+
mark_field_as_changed!(:increments)
|
7
|
+
increments[key] ||= 0
|
8
|
+
increments[key] += value
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def increments
|
14
|
+
@increments ||= {}
|
15
|
+
end
|
16
|
+
|
17
|
+
def increments=(hash)
|
18
|
+
mark_field_as_changed!(:increments)
|
19
|
+
@increments = hash
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/intercom/user.rb
CHANGED
@@ -1,366 +1,30 @@
|
|
1
|
-
require 'intercom/
|
2
|
-
require 'intercom/
|
3
|
-
require 'intercom/
|
4
|
-
require 'intercom/
|
1
|
+
require 'intercom/api_operations/count'
|
2
|
+
require 'intercom/api_operations/list'
|
3
|
+
require 'intercom/api_operations/load'
|
4
|
+
require 'intercom/api_operations/find'
|
5
|
+
require 'intercom/api_operations/find_all'
|
6
|
+
require 'intercom/api_operations/save'
|
7
|
+
require 'intercom/api_operations/delete'
|
8
|
+
require 'intercom/extended_api_operations/tags'
|
9
|
+
require 'intercom/traits/incrementable_attributes'
|
10
|
+
require 'intercom/traits/api_resource'
|
5
11
|
|
6
12
|
module Intercom
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
# user.save
|
23
|
-
class User < UserResource
|
13
|
+
class User
|
14
|
+
include ApiOperations::Count
|
15
|
+
include ApiOperations::List
|
16
|
+
include ApiOperations::Load
|
17
|
+
include ApiOperations::Find
|
18
|
+
include ApiOperations::FindAll
|
19
|
+
include ApiOperations::Save
|
20
|
+
include ApiOperations::Delete
|
21
|
+
include ExtendedApiOperations::Tags
|
22
|
+
include Traits::IncrementableAttributes
|
23
|
+
include Traits::ApiResource
|
24
|
+
|
25
|
+
def identity_vars ; [:id, :email, :user_id] ; end
|
26
|
+
def flat_store_attributes ; [:custom_attributes] ; end
|
27
|
+
def update_verb ; 'post' ; end
|
24
28
|
|
25
|
-
|
26
|
-
##
|
27
|
-
# Fetches an Intercom::User from our API.
|
28
|
-
#
|
29
|
-
# Calls GET https://api.intercom.io/v1/users
|
30
|
-
#
|
31
|
-
# returns Intercom::User object representing the state on our servers.
|
32
|
-
#
|
33
|
-
# @return [User]
|
34
|
-
def self.find(params)
|
35
|
-
response = Intercom.get("/v1/users", params)
|
36
|
-
User.from_api(response)
|
37
|
-
end
|
38
|
-
|
39
|
-
# Calls GET https://api.intercom.io/v1/users?email=EMAIL
|
40
|
-
#
|
41
|
-
# returns Intercom::User object representing the state on our servers.
|
42
|
-
#
|
43
|
-
# @param [String] email address of the user
|
44
|
-
# @return [User]
|
45
|
-
def self.find_by_email(email)
|
46
|
-
find({:email => email})
|
47
|
-
end
|
48
|
-
|
49
|
-
# Calls GET https://api.intercom.io/v1/users?user_id=USER-ID
|
50
|
-
#
|
51
|
-
# returns Intercom::User object representing the state on our servers.
|
52
|
-
#
|
53
|
-
# @param [String] user_id user id of the user
|
54
|
-
# @return [User]
|
55
|
-
def self.find_by_user_id(user_id)
|
56
|
-
find({:user_id => user_id})
|
57
|
-
end
|
58
|
-
|
59
|
-
# Creates (or updates when a user already exists for that email/user_id) a user record on your application.
|
60
|
-
#
|
61
|
-
# Calls POST https://api.intercom.io/v1/users
|
62
|
-
#
|
63
|
-
# returns Intercom::User object representing the state on our servers.
|
64
|
-
#
|
65
|
-
# This operation is idempotent.
|
66
|
-
# @return [User]
|
67
|
-
def self.create(params)
|
68
|
-
User.new(params).save
|
69
|
-
end
|
70
|
-
|
71
|
-
# Retrieve all the users
|
72
|
-
# Examples:
|
73
|
-
# Intercom::User.all.each do |user|
|
74
|
-
# puts user.inspect
|
75
|
-
# end
|
76
|
-
# > ["user1@example.com" ,"user2@example.com" ,....]
|
77
|
-
# Intercom::User.all.map(&:email)
|
78
|
-
# > ["user1@example.com" ,"user2@example.com" ,....]
|
79
|
-
#
|
80
|
-
# @return [UserCollectionProxy]
|
81
|
-
def self.all
|
82
|
-
UserCollectionProxy.new
|
83
|
-
end
|
84
|
-
|
85
|
-
# Retrieve all the users that match a query
|
86
|
-
# Examples:
|
87
|
-
# Intercom::User.where(:tag_name => 'Free Trial').each do |user|
|
88
|
-
# puts user.inspect
|
89
|
-
# end
|
90
|
-
# > ["user1@example.com" ,"user2@example.com" ,....]
|
91
|
-
# Intercom::User.where(:tag_name => 'Free Trial').map(&:email)
|
92
|
-
# > ["user1@example.com" ,"user2@example.com" ,....]
|
93
|
-
#
|
94
|
-
# Currently only supports tag_name and tag_id querying
|
95
|
-
#
|
96
|
-
# @return [UserCollectionProxy]
|
97
|
-
def self.where(params)
|
98
|
-
UserCollectionProxy.new(params)
|
99
|
-
end
|
100
|
-
|
101
|
-
# Fetches a count of all Users tracked on Intercom.
|
102
|
-
# Example:
|
103
|
-
# Intercom::User.all.count
|
104
|
-
# > 5346
|
105
|
-
#
|
106
|
-
# @return [Integer]
|
107
|
-
def self.count
|
108
|
-
response = Intercom.get("/v1/users", {:per_page => 1})
|
109
|
-
response["total_count"]
|
110
|
-
end
|
111
|
-
|
112
|
-
# Deletes a user record on your application.
|
113
|
-
#
|
114
|
-
# Calls DELETE https://api.intercom.io/v1/users
|
115
|
-
#
|
116
|
-
# returns Intercom::User object representing the user just before deletion.
|
117
|
-
#
|
118
|
-
# This operation is not idempotent.
|
119
|
-
# @return [User]
|
120
|
-
def self.delete(params)
|
121
|
-
response = Intercom.delete("/v1/users", params)
|
122
|
-
User.from_api(response)
|
123
|
-
end
|
124
|
-
|
125
|
-
# instance method alternative to #create
|
126
|
-
# @return [User]
|
127
|
-
def save
|
128
|
-
response = Intercom.post("/v1/users", to_hash)
|
129
|
-
self.update_from_api_response(response)
|
130
|
-
end
|
131
|
-
|
132
|
-
# Increment a custom data value on a user
|
133
|
-
# @return [User]
|
134
|
-
def increment(key, value=1)
|
135
|
-
increments[key] ||= 0
|
136
|
-
increments[key] += value
|
137
|
-
end
|
138
|
-
|
139
|
-
# @return [String] the {User}'s name
|
140
|
-
def name
|
141
|
-
@attributes["name"]
|
142
|
-
end
|
143
|
-
|
144
|
-
# @param [String] name {User}'s name
|
145
|
-
# @return [void]
|
146
|
-
def name=(name)
|
147
|
-
@attributes["name"]=name
|
148
|
-
end
|
149
|
-
|
150
|
-
# @return [String]
|
151
|
-
def last_seen_ip
|
152
|
-
@attributes["last_seen_ip"]
|
153
|
-
end
|
154
|
-
|
155
|
-
# @return [void]
|
156
|
-
def last_seen_ip=(last_seen_ip)
|
157
|
-
@attributes["last_seen_ip"]=last_seen_ip
|
158
|
-
end
|
159
|
-
|
160
|
-
# @return [String]
|
161
|
-
def last_seen_user_agent
|
162
|
-
@attributes["last_seen_user_agent"]
|
163
|
-
end
|
164
|
-
|
165
|
-
# @return [void]
|
166
|
-
def last_seen_user_agent=(last_seen_user_agent)
|
167
|
-
@attributes["last_seen_user_agent"]=last_seen_user_agent
|
168
|
-
end
|
169
|
-
|
170
|
-
# @return [Integer]
|
171
|
-
def relationship_score
|
172
|
-
@attributes["relationship_score"]
|
173
|
-
end
|
174
|
-
|
175
|
-
# @return [Integer]
|
176
|
-
def session_count
|
177
|
-
@attributes["session_count"]
|
178
|
-
end
|
179
|
-
|
180
|
-
##
|
181
|
-
# Get last time this User interacted with your application
|
182
|
-
# @return [Time]
|
183
|
-
def last_impression_at
|
184
|
-
time_at("last_impression_at")
|
185
|
-
end
|
186
|
-
|
187
|
-
##
|
188
|
-
# Set Time at which this User last made a request your application.
|
189
|
-
# @return [void]
|
190
|
-
def last_impression_at=(time)
|
191
|
-
set_time_at("last_impression_at", time)
|
192
|
-
end
|
193
|
-
|
194
|
-
##
|
195
|
-
# Get last time this User interacted with your application
|
196
|
-
# @return [Time]
|
197
|
-
def last_request_at
|
198
|
-
time_at("last_request_at")
|
199
|
-
end
|
200
|
-
|
201
|
-
##
|
202
|
-
# Set Time at which this User last made a request your application.
|
203
|
-
# @return [void]
|
204
|
-
def last_request_at=(time)
|
205
|
-
set_time_at("last_request_at", time)
|
206
|
-
end
|
207
|
-
|
208
|
-
##
|
209
|
-
# Get Time at which this User started using your application.
|
210
|
-
# @return [Time]
|
211
|
-
def created_at
|
212
|
-
time_at("created_at")
|
213
|
-
end
|
214
|
-
|
215
|
-
##
|
216
|
-
# Set Time at which this User started using your application.
|
217
|
-
# @return [void]
|
218
|
-
def created_at=(time)
|
219
|
-
set_time_at("created_at", time)
|
220
|
-
end
|
221
|
-
|
222
|
-
##
|
223
|
-
# Get whether user has unsubscribed from email
|
224
|
-
# @return [Boolean]
|
225
|
-
def unsubscribed_from_emails
|
226
|
-
@attributes['unsubscribed_from_emails']
|
227
|
-
end
|
228
|
-
|
229
|
-
##
|
230
|
-
# Get url for user's avatar, if present. Otherwise, nil.
|
231
|
-
# @return [String]
|
232
|
-
def avatar_url
|
233
|
-
@attributes["avatar_url"]
|
234
|
-
end
|
235
|
-
|
236
|
-
##
|
237
|
-
# Set whether user has unsubscribed from email
|
238
|
-
# @return [void]
|
239
|
-
def unsubscribed_from_emails=(unsubscribed_from_emails)
|
240
|
-
@attributes['unsubscribed_from_emails'] = unsubscribed_from_emails
|
241
|
-
end
|
242
|
-
|
243
|
-
##
|
244
|
-
# Get array of Intercom::SocialProfile objects attached to this Intercom::User
|
245
|
-
#
|
246
|
-
# See http://docs.intercom.io/#SocialProfiles for more information
|
247
|
-
# @return [Array<SocialProfile>]
|
248
|
-
def social_profiles
|
249
|
-
@social_profiles ||= [].freeze
|
250
|
-
end
|
251
|
-
|
252
|
-
##
|
253
|
-
# Get hash of location attributes associated with this Intercom::User
|
254
|
-
#
|
255
|
-
# Possible entries: city_name, continent_code, country_code, country_name, latitude, longitude, postal_code, region_name, timezone
|
256
|
-
#
|
257
|
-
# e.g.
|
258
|
-
#
|
259
|
-
# {"city_name"=>"Santiago", "continent_code"=>"SA", "country_code"=>"CHL", "country_name"=>"Chile",
|
260
|
-
# "latitude"=>-33.44999999999999, "longitude"=>-70.6667, "postal_code"=>"", "region_name"=>"12",
|
261
|
-
# "timezone"=>"Chile/Continental"}
|
262
|
-
# @return [Hash]
|
263
|
-
def location_data
|
264
|
-
@location_data ||= {}.freeze
|
265
|
-
end
|
266
|
-
|
267
|
-
# Custom attributes stored for this Intercom::User
|
268
|
-
#
|
269
|
-
# See http://docs.intercom.io/#CustomData for more information
|
270
|
-
#
|
271
|
-
# Example: Reading custom_data value for an existing user
|
272
|
-
# user = Intercom::User.find(:email => "someone@example.com")
|
273
|
-
# puts user.custom_data[:plan]
|
274
|
-
#
|
275
|
-
# Example: Setting some custom data for an existing user
|
276
|
-
# user = Intercom::User.find(:email => "someone@example.com")
|
277
|
-
# user.custom_data[:plan] = "pro"
|
278
|
-
# user.save
|
279
|
-
#
|
280
|
-
# @return [FlatStore]
|
281
|
-
def custom_data
|
282
|
-
@attributes["custom_data"] ||= FlatStore.new
|
283
|
-
end
|
284
|
-
|
285
|
-
# Set a {Hash} of custom data attributes to save/update on this user
|
286
|
-
#
|
287
|
-
# @param [Hash] custom_data
|
288
|
-
# @return [FlatStore]
|
289
|
-
def custom_data=(custom_data)
|
290
|
-
@attributes["custom_data"] = FlatStore.new(custom_data)
|
291
|
-
end
|
292
|
-
|
293
|
-
# Company stored for this Intercom::User
|
294
|
-
#
|
295
|
-
# See http://docs.intercom.io/#Companies for more information
|
296
|
-
#
|
297
|
-
# Example: Setting a company for an existing user
|
298
|
-
# user = Intercom::User.find(:email => "someone@example.com")
|
299
|
-
# user.company[:id] = 6
|
300
|
-
# user.company[:name] = "Intercom"
|
301
|
-
# user.save
|
302
|
-
#
|
303
|
-
# @return [FlatStore]
|
304
|
-
def company
|
305
|
-
@attributes["company"] ||= FlatStore.new
|
306
|
-
end
|
307
|
-
|
308
|
-
# Set a {Hash} of company attributes to save/update on this user
|
309
|
-
#
|
310
|
-
# @param [Hash] company
|
311
|
-
# @return [FlatStore]
|
312
|
-
def company=(company)
|
313
|
-
@attributes["company"] = FlatStore.new(company)
|
314
|
-
end
|
315
|
-
|
316
|
-
# Multiple companies for this Intercom::User
|
317
|
-
#
|
318
|
-
# See http://docs.intercom.io/#Companies for more information
|
319
|
-
#
|
320
|
-
# Example: Setting a company for an existing user
|
321
|
-
# user = Intercom::User.find(:email => "someone@example.com")
|
322
|
-
# user.companies = [{:id => 6, :name => "intercom"}, {:id => 9, :name => "Test Company"}]
|
323
|
-
# user.save
|
324
|
-
#
|
325
|
-
# @return [Array]
|
326
|
-
def companies
|
327
|
-
@attributes["companies"] ||= []
|
328
|
-
end
|
329
|
-
|
330
|
-
# Set an {Array} of {Hash} company attributes to save/update on this user
|
331
|
-
#
|
332
|
-
# @param [Array] companies
|
333
|
-
# @return [Array]
|
334
|
-
def companies=(companies)
|
335
|
-
raise ArgumentError.new("Companies requires an array of hashes of companies") unless companies.is_a?(Array) && companies.all? {|company| company.is_a?(Hash)}
|
336
|
-
@attributes["companies"] = companies.collect {|company| FlatStore.new(company) }
|
337
|
-
end
|
338
|
-
|
339
|
-
##
|
340
|
-
# Creates an Event for the given User
|
341
|
-
# @param {Hash} options, keys for :created_at (Unix timestamp) and metadata
|
342
|
-
def track_event(event_name, options={})
|
343
|
-
attributes = {:event_name => event_name, :user => self}
|
344
|
-
attributes[:created_at] = options[:created_at] unless options[:created_at].nil?
|
345
|
-
attributes[:metadata] = options[:metadata] unless options[:metadata].nil?
|
346
|
-
Event.create(attributes)
|
347
|
-
end
|
348
|
-
|
349
|
-
protected
|
350
|
-
def social_profiles=(social_profiles) #:nodoc:
|
351
|
-
@social_profiles = social_profiles.map { |account| SocialProfile.new(account) }.freeze
|
352
|
-
end
|
353
|
-
|
354
|
-
def location_data=(hash) #:nodoc:
|
355
|
-
@location_data = hash.freeze
|
356
|
-
end
|
357
|
-
|
358
|
-
def increments #:nodoc:
|
359
|
-
@attributes["increments"] ||= {}
|
360
|
-
end
|
361
|
-
|
362
|
-
def increments=(hash) #:nodoc:
|
363
|
-
@attributes["increments"] = hash
|
364
|
-
end
|
365
29
|
end
|
366
30
|
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Intercom
|
2
|
+
module Utils
|
3
|
+
class << self
|
4
|
+
def singularize(str)
|
5
|
+
str.gsub(/ies$/, 'y').gsub(/s$/, '')
|
6
|
+
end
|
7
|
+
|
8
|
+
def pluralize(str)
|
9
|
+
return str.gsub(/y$/, 'ies') if str =~ /y$/
|
10
|
+
"#{str}s"
|
11
|
+
end
|
12
|
+
|
13
|
+
def resource_class_to_singular_name(resource_class)
|
14
|
+
resource_class.to_s.split('::')[-1].downcase
|
15
|
+
end
|
16
|
+
|
17
|
+
def resource_class_to_collection_name(resource_class)
|
18
|
+
Utils.pluralize(resource_class_to_singular_name(resource_class))
|
19
|
+
end
|
20
|
+
|
21
|
+
def constantize_resource_name(resource_name)
|
22
|
+
class_name = Utils.singularize(resource_name.capitalize)
|
23
|
+
define_lightweight_class(class_name) unless Intercom.const_defined?(class_name)
|
24
|
+
namespaced_class_name = "Intercom::#{class_name}"
|
25
|
+
Object.const_get(namespaced_class_name)
|
26
|
+
end
|
27
|
+
|
28
|
+
def constantize_singular_resource_name(resource_name)
|
29
|
+
class_name = resource_name.split('_').map(&:capitalize).join
|
30
|
+
define_lightweight_class(class_name) unless Intercom.const_defined?(class_name)
|
31
|
+
namespaced_class_name = "Intercom::#{class_name}"
|
32
|
+
Object.const_get(namespaced_class_name )
|
33
|
+
end
|
34
|
+
|
35
|
+
def define_lightweight_class(class_name)
|
36
|
+
#File.open('./intercom_ruby_dynamically_defined_classes.log', 'a') {|f| f.puts("Dynamically defining the class Intercom::#{class_name}") } #HACK
|
37
|
+
new_class_definition = Class.new(Object) do
|
38
|
+
include Traits::ApiResource
|
39
|
+
end
|
40
|
+
Intercom.const_set(class_name, new_class_definition)
|
41
|
+
end
|
42
|
+
|
43
|
+
def entity_key_from_type(type)
|
44
|
+
is_list = type.split('.')[1] == 'list'
|
45
|
+
entity_name = type.split('.')[0]
|
46
|
+
is_list ? Utils.pluralize(entity_name) : entity_name
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|