followupboss_client 1.0.0 → 1.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f4e09dc895a09a254ed74cc7386a63c7d9e6fd4122c7aebe84a510111c79a44c
4
- data.tar.gz: f2a2891e76c6ae01b836c70a56e0912327512b201639800eb591bfbc3f3552bb
3
+ metadata.gz: 1d1312cbac7957779291cc5318bd1935c88c3b01d3dfa05aab44ceee9fdd6d84
4
+ data.tar.gz: b7970a95962e3be54613ffd330f75201664a26c235815a0795223e004b8ff03d
5
5
  SHA512:
6
- metadata.gz: fd32c62a15dd9de0dc40ebb494f681d8651dfda05aea30f3e3a3ecb96b308b17b1afc85ead1786660deac7d822d24a4e6a5390811a27a9e17b8bf3a0977581df
7
- data.tar.gz: 211789660757346070247a8a2a8618d73f682e3f6ee7f6e8b34c55ac5e53cd4f8bdd62c71c26250ad704cde3f07a7c7b24a9792e9e932176d34cda980a559728
6
+ metadata.gz: b6dad911222d02c831b051d44f1e79c5de579c3bf2d4521b4fd605ff61304dece6fdcf2fbb7f1fabb20f60383917b32b829d4492eea41fba36b0c57c7ffc6714
7
+ data.tar.gz: 12048f77d3a9c3ca7c785b813032b14a14ccbd5ac65611fd0a2395f587947386408be399f81d75d9f18eea05ee8f0a5ff89ac2e22f1e872a0ec163f9bfa8dace
data/CHANGELOG.md CHANGED
@@ -5,6 +5,25 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.1.0] - 2025-07-13
9
+
10
+ ### Fixed
11
+ - **Multi-authentication system improvements**
12
+ - Enhanced client authentication handling
13
+ - Improved cookie-based authentication flow
14
+ - Better authentication middleware integration
15
+ - Fixed authentication configuration issues
16
+
17
+ ### Enhanced
18
+ - **SharedInbox functionality** with improved authentication support
19
+ - **User authentication** with additional auth features
20
+ - **Cookie client** with better multi-auth support
21
+ - **Configuration system** for handling multiple authentication methods
22
+
23
+ ### Updated
24
+ - Documentation with multi-auth usage examples
25
+ - README with enhanced authentication guidance
26
+
8
27
  ## [1.0.0] - 2025-01-07
9
28
 
10
29
  ### Major Release - Enhanced Fork
data/README.md CHANGED
@@ -91,7 +91,15 @@ export FUB_API_KEY=your_api_key
91
91
  **Configuration Block:**
92
92
  ```ruby
93
93
  FubClient.configure do |config|
94
- config.api_key = 'your_api_key'
94
+ # For Her-based resources
95
+ config.api_key = 'your_api_key_here'
96
+
97
+ # Option 1: Direct cookie (use this OR the GIST option below, not both)
98
+ config.cookie = 'cookie string'
99
+
100
+ # Option 2: GIST + Encryption (cookie comes from encrypted GIST)
101
+ config.gist_url = "gist url"
102
+ config.encryption_key = "encryption key"
95
103
  end
96
104
  ```
97
105
 
@@ -19,7 +19,7 @@ Gem::Specification.new do |spec|
19
19
 
20
20
  spec.metadata['allowed_push_host'] = 'https://rubygems.org'
21
21
 
22
- spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
22
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) || f.match(%r{\.gem$}) }
23
23
  spec.bindir = 'exe'
24
24
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
25
25
  spec.require_paths = ['lib']
@@ -9,10 +9,18 @@ module FubClient
9
9
  # Allow explicitly setting the instance (for testing)
10
10
  class << self
11
11
  attr_writer :instance
12
+
13
+ # Convenience method to access configuration
14
+ def config
15
+ FubClient.configuration
16
+ end
17
+
18
+ # Alias for configuration
19
+ def configuration
20
+ FubClient.configuration
21
+ end
12
22
  end
13
23
 
14
- attr_writer :api_key
15
- attr_accessor :cookies, :subdomain
16
24
  attr_reader :her_api
17
25
 
18
26
  def initialize
@@ -20,19 +28,40 @@ module FubClient
20
28
  end
21
29
 
22
30
  def api_key
23
- @api_key ||= ENV['FUB_API_KEY']
31
+ return @api_key if defined?(@api_key) && @api_key
32
+ FubClient.configuration.api_key || ENV['FUB_API_KEY']
33
+ end
34
+
35
+ def api_key=(value)
36
+ @api_key = value
37
+ end
38
+
39
+ def subdomain
40
+ return @subdomain if defined?(@subdomain) && @subdomain
41
+ FubClient.configuration.subdomain || ENV['FUB_SUBDOMAIN']
42
+ end
43
+
44
+ def subdomain=(value)
45
+ @subdomain = value
46
+ end
47
+
48
+ def cookies
49
+ return @cookies if defined?(@cookies) && @cookies
50
+ FubClient.configuration.cookie || ENV['FUB_COOKIE']
51
+ end
52
+
53
+ def cookies=(value)
54
+ @cookies = value
24
55
  end
25
56
 
26
57
  def api_uri
27
- return @api_uri if @api_uri
28
-
29
- @api_uri = if subdomain
30
- # Use subdomain-specific URL for cookie-based auth
31
- URI::HTTPS.build(host: "#{subdomain}.followupboss.com", path: "/api/#{API_VERSION}")
32
- else
33
- # Use default API URL for API key auth
34
- URI::HTTPS.build(host: API_URL, path: "/#{API_VERSION}")
35
- end
58
+ if use_cookies? && subdomain
59
+ # Use subdomain-specific URL for cookie-based auth
60
+ URI::HTTPS.build(host: "#{subdomain}.followupboss.com", path: "/api/#{API_VERSION}")
61
+ else
62
+ # Use default API URL for API key auth
63
+ URI::HTTPS.build(host: API_URL, path: "/#{API_VERSION}")
64
+ end
36
65
  end
37
66
 
38
67
  # Login to obtain cookies
@@ -164,7 +193,8 @@ module FubClient
164
193
 
165
194
  # Use cookie authentication?
166
195
  def use_cookies?
167
- !@cookies.nil? && !@cookies.empty?
196
+ current_cookies = cookies
197
+ !current_cookies.nil? && !current_cookies.empty?
168
198
  end
169
199
 
170
200
  # Reset the HER API connection with current settings
@@ -3,11 +3,11 @@ module FubClient
3
3
  attr_accessor :api_key, :subdomain, :gist_url, :encryption_key, :cookie
4
4
 
5
5
  def initialize
6
- @api_key = ENV['FUB_API_KEY']
7
- @subdomain = ENV['FUB_SUBDOMAIN']
8
- @gist_url = ENV['FUB_GIST_URL']
9
- @encryption_key = ENV['FUB_ENCRYPTION_KEY']
10
- @cookie = ENV['FUB_COOKIE']
6
+ @api_key = ENV['FUB_API_KEY'] if ENV['FUB_API_KEY']
7
+ @subdomain = ENV['FUB_SUBDOMAIN'] if ENV['FUB_SUBDOMAIN']
8
+ @gist_url = ENV['FUB_GIST_URL'] if ENV['FUB_GIST_URL']
9
+ @encryption_key = ENV['FUB_ENCRYPTION_KEY'] if ENV['FUB_ENCRYPTION_KEY']
10
+ @cookie = ENV['FUB_COOKIE'] if ENV['FUB_COOKIE']
11
11
  end
12
12
 
13
13
  def valid?
@@ -46,9 +46,17 @@ module FubClient
46
46
 
47
47
  def self.configure
48
48
  yield(configuration) if block_given?
49
+ # Reset the client's Her API to pick up new configuration
50
+ if defined?(FubClient::Client) && FubClient::Client.instance
51
+ FubClient::Client.instance.reset_her_api
52
+ end
49
53
  end
50
54
 
51
55
  def self.reset_configuration!
52
56
  @configuration = Configuration.new
57
+ # Also reset the client's Her API if it exists
58
+ if defined?(FubClient::Client) && FubClient::Client.instance
59
+ FubClient::Client.instance.reset_her_api
60
+ end
53
61
  end
54
62
  end
@@ -6,9 +6,10 @@ module FubClient
6
6
  def initialize(subdomain: nil, gist_url: nil, encryption_key: nil, cookie: nil)
7
7
  config = FubClient.configuration
8
8
 
9
- @subdomain = subdomain || config.subdomain
10
- @gist_url = gist_url || config.gist_url
11
- @encryption_key = encryption_key || config.encryption_key
9
+ @subdomain = subdomain || config.subdomain || ENV['FUB_SUBDOMAIN']
10
+ @gist_url = gist_url || config.gist_url || ENV['FUB_GIST_URL']
11
+ @encryption_key = encryption_key || config.encryption_key || ENV['FUB_ENCRYPTION_KEY']
12
+ cookie ||= config.cookie || ENV['FUB_COOKIE']
12
13
 
13
14
  raise ArgumentError, 'Subdomain is required for cookie authentication' unless @subdomain
14
15
 
@@ -26,7 +27,7 @@ module FubClient
26
27
 
27
28
  def cookies=(value)
28
29
  @cookies = value
29
- client.cookies = value if @cookies
30
+ # Don't modify the global client anymore
30
31
  end
31
32
 
32
33
  def client
@@ -34,13 +35,33 @@ module FubClient
34
35
  end
35
36
 
36
37
  def configure_client
37
- client.subdomain = @subdomain
38
- client.cookies = @cookies
39
- client.reset_her_api
38
+ # Don't modify the global client - create our own Her API instance
39
+ @her_api = create_cookie_her_api
40
40
  end
41
41
 
42
42
  def reset_her_api
43
- client.reset_her_api
43
+ @her_api = create_cookie_her_api
44
+ end
45
+
46
+ def her_api
47
+ @her_api ||= create_cookie_her_api
48
+ end
49
+
50
+ private
51
+
52
+ def create_cookie_her_api
53
+ api_uri = URI::HTTPS.build(host: "#{@subdomain}.followupboss.com", path: "/api/#{FubClient::Client::API_VERSION}")
54
+
55
+ her_api = Her::API.new
56
+ her_api.setup url: api_uri.to_s do |c|
57
+ # Use cookie authentication middleware with our cookies
58
+ c.use FubClient::Middleware::CookieAuthentication, cookies: @cookies
59
+ c.request :url_encoded
60
+ c.use FubClient::Middleware::Parser
61
+ c.adapter :net_http
62
+ end
63
+
64
+ her_api
44
65
  end
45
66
 
46
67
  def fetch_cookie_from_gist
@@ -1,9 +1,23 @@
1
1
  module FubClient
2
2
  module Middleware
3
3
  class CookieAuthentication < Faraday::Middleware
4
+ def initialize(app, cookies: nil)
5
+ super(app)
6
+ @cookies = cookies
7
+ end
8
+
4
9
  def call(env)
5
- # Get the cookies from the client
6
- cookies = FubClient::Client.instance.cookies
10
+ # Get the cookies from the parameter, client, or Her API instance
11
+ cookies = @cookies || FubClient::Client.instance.cookies
12
+
13
+ # If still no cookies, try to get from the Her API instance
14
+ if (!cookies || cookies.empty?) && env[:request] && env[:request].respond_to?(:connection)
15
+ connection = env[:request].connection
16
+ if connection.respond_to?(:instance_variable_get)
17
+ api_cookies = connection.instance_variable_get(:@cookies)
18
+ cookies = api_cookies if api_cookies && !api_cookies.empty?
19
+ end
20
+ end
7
21
 
8
22
  if cookies && !cookies.empty?
9
23
  # CRITICAL: Must set a request header to enable cookie-based auth
@@ -103,45 +103,18 @@ module FubClient
103
103
  root_element :shared_inbox
104
104
  include_root_in_json true
105
105
 
106
- def self.all_inboxes
107
- puts 'Calling SharedInbox.all_inboxes using direct cookie authentication' if ENV['DEBUG']
106
+ def self.all_inboxes(cookie_client: nil)
107
+ puts 'Calling SharedInbox.all_inboxes using cookie authentication' if ENV['DEBUG']
108
108
 
109
- client = FubClient::Client.instance
110
- cookies = client.cookies
111
- subdomain = client.subdomain
109
+ client = resolve_cookie_client(cookie_client)
110
+ return [] unless client
112
111
 
113
- if !cookies || cookies.empty?
114
- puts 'Error: No cookies available for authentication' if ENV['DEBUG']
115
- return []
116
- end
112
+ conn = create_faraday_connection_from_client(client)
113
+ return [] unless conn
117
114
 
118
- if !subdomain || subdomain.empty?
119
- puts 'Error: No subdomain set for authentication' if ENV['DEBUG']
120
- return []
121
- end
122
-
123
- conn = Faraday.new(url: "https://#{subdomain}.followupboss.com") do |f|
124
- f.headers['Cookie'] = cookies
125
- f.headers['Accept'] = 'application/json, text/javascript, */*; q=0.01'
126
- f.headers['Accept-Language'] = 'en-US,en;q=0.9'
127
- f.headers['X-Requested-With'] = 'XMLHttpRequest'
128
- f.headers['X-System'] = 'fub-spa'
129
- f.headers['User-Agent'] =
130
- 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36'
131
- f.adapter :net_http
132
- end
133
-
134
- response = conn.get('/api/v1/sharedInboxes?showAllBypass=true&limit=20&offset=0')
115
+ response = conn.get('/api/v1/sharedInboxes?showAllBypass=true&limit=200&offset=0')
135
116
 
136
117
  if ENV['DEBUG']
137
- puts 'Request headers:'
138
- conn.headers.each do |k, v|
139
- if k.downcase == 'cookie' && v.length > 50
140
- puts " #{k}: #{v[0..50]}..."
141
- else
142
- puts " #{k}: #{v}"
143
- end
144
- end
145
118
  puts "Response status: #{response.status}"
146
119
  puts "Response body: #{response.body[0..100]}..." if response.body && response.body.length > 100
147
120
  end
@@ -149,7 +122,7 @@ module FubClient
149
122
  if response.status == 200
150
123
  data = JSON.parse(response.body, symbolize_names: true)
151
124
  inboxes = data[:sharedInboxes] || []
152
- puts "Found #{inboxes.count} shared inboxes via direct request" if ENV['DEBUG']
125
+ puts "Found #{inboxes.count} shared inboxes via cookie client" if ENV['DEBUG']
153
126
  inboxes
154
127
  else
155
128
  puts "Error: HTTP #{response.status} - #{response.body}" if ENV['DEBUG']
@@ -161,33 +134,14 @@ module FubClient
161
134
  []
162
135
  end
163
136
 
164
- def self.get_inbox(id)
165
- puts "Calling SharedInbox.get_inbox(#{id}) using direct cookie authentication" if ENV['DEBUG']
137
+ def self.get_inbox(id, cookie_client: nil)
138
+ puts "Calling SharedInbox.get_inbox(#{id}) using cookie authentication" if ENV['DEBUG']
166
139
 
167
- client = FubClient::Client.instance
168
- cookies = client.cookies
169
- subdomain = client.subdomain
140
+ client = resolve_cookie_client(cookie_client)
141
+ return nil unless client
170
142
 
171
- if !cookies || cookies.empty?
172
- puts 'Error: No cookies available for authentication' if ENV['DEBUG']
173
- return nil
174
- end
175
-
176
- if !subdomain || subdomain.empty?
177
- puts 'Error: No subdomain set for authentication' if ENV['DEBUG']
178
- return nil
179
- end
180
-
181
- conn = Faraday.new(url: "https://#{subdomain}.followupboss.com") do |f|
182
- f.headers['Cookie'] = cookies
183
- f.headers['Accept'] = 'application/json, text/javascript, */*; q=0.01'
184
- f.headers['Accept-Language'] = 'en-US,en;q=0.9'
185
- f.headers['X-Requested-With'] = 'XMLHttpRequest'
186
- f.headers['X-System'] = 'fub-spa'
187
- f.headers['User-Agent'] =
188
- 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36'
189
- f.adapter :net_http
190
- end
143
+ conn = create_faraday_connection_from_client(client)
144
+ return nil unless conn
191
145
 
192
146
  response = conn.get("/api/v1/sharedInboxes/#{id}")
193
147
 
@@ -212,7 +166,7 @@ module FubClient
212
166
  end
213
167
 
214
168
  if data.is_a?(Hash) && !data.empty?
215
- puts "Found inbox with ID #{id} via direct request" if ENV['DEBUG']
169
+ puts "Found inbox with ID #{id} via cookie client" if ENV['DEBUG']
216
170
  data.extend(SharedInboxMethods)
217
171
  data
218
172
  else
@@ -229,6 +183,170 @@ module FubClient
229
183
  nil
230
184
  end
231
185
 
186
+ def self.update_inbox(id, attributes, cookie_client: nil, merge_with_existing: true)
187
+ puts "Calling SharedInbox.update_inbox(#{id}) using cookie authentication" if ENV['DEBUG']
188
+
189
+ client = resolve_cookie_client(cookie_client)
190
+ return nil unless client
191
+
192
+ # If merge_with_existing is true, get current inbox data and merge
193
+ if merge_with_existing
194
+ current_inbox = get_inbox(id, cookie_client: client)
195
+ unless current_inbox
196
+ puts 'Error: Could not retrieve current inbox data for merging' if ENV['DEBUG']
197
+ return nil
198
+ end
199
+
200
+ # Create full payload by merging existing data with new attributes
201
+ full_payload = {
202
+ name: current_inbox[:name],
203
+ phones: current_inbox[:phones] || [],
204
+ users: current_inbox[:users] || [],
205
+ replyFrom: current_inbox[:replyFrom] || "",
206
+ replyFromPersonalized: current_inbox[:replyFromPersonalized] || false,
207
+ officeHoursBehaviorType: current_inbox[:officeHoursBehaviorType] || "Voicemail",
208
+ officeHoursForwardNumber: current_inbox[:officeHoursForwardNumber] || [],
209
+ incomingForwardTeam: current_inbox[:incomingForwardTeam] || [],
210
+ agentViewAll: current_inbox[:agentViewAll] || 0,
211
+ incomingForwardNumber: current_inbox[:incomingForwardNumber] || [],
212
+ incomingBehaviorType: current_inbox[:incomingBehaviorType] || "Voicemail",
213
+ unansweredForwardNumber: current_inbox[:unansweredForwardNumber] || []
214
+ }.merge(attributes)
215
+
216
+ puts "Merged payload with existing data" if ENV['DEBUG']
217
+ else
218
+ full_payload = attributes
219
+ puts "Using provided attributes without merging" if ENV['DEBUG']
220
+ end
221
+
222
+ conn = create_faraday_connection_from_client(client)
223
+ return nil unless conn
224
+
225
+ response = conn.put do |req|
226
+ req.url "/api/v1/sharedInboxes/#{id}"
227
+ req.headers['Content-Type'] = 'application/json; charset=UTF-8'
228
+ req.headers['X-FUB-JS-Version'] = '198593'
229
+ req.body = JSON.generate(full_payload)
230
+ end
231
+
232
+ if ENV['DEBUG']
233
+ puts "Update response status: #{response.status}"
234
+ puts "Update response body: #{response.body[0..200]}..." if response.body && response.body.length > 200
235
+ end
236
+
237
+ if [200, 204].include?(response.status)
238
+ if response.status == 200 && !response.body.empty?
239
+ data = JSON.parse(response.body, symbolize_names: true)
240
+ puts "Updated inbox with ID #{id} via cookie client" if ENV['DEBUG']
241
+ data.extend(SharedInboxMethods) if data.is_a?(Hash)
242
+ data
243
+ else
244
+ puts "Updated inbox with ID #{id} (no response body)" if ENV['DEBUG']
245
+ true
246
+ end
247
+ else
248
+ puts "Error updating inbox: HTTP #{response.status} - #{response.body}" if ENV['DEBUG']
249
+ nil
250
+ end
251
+ rescue StandardError => e
252
+ puts "Error in update_inbox: #{e.message}" if ENV['DEBUG']
253
+ puts e.backtrace.join("\n") if ENV['DEBUG']
254
+ nil
255
+ end
256
+
257
+ def self.find_by_phone(phone_number, cookie_client: nil)
258
+ puts "Calling SharedInbox.find_by_phone(#{phone_number}) using cookie authentication" if ENV['DEBUG']
259
+
260
+ client = resolve_cookie_client(cookie_client)
261
+ return nil unless client
262
+
263
+ # Get all inboxes (now with limit=200, should cover all 142 inboxes)
264
+ inboxes = all_inboxes(cookie_client: client)
265
+ return nil if inboxes.empty?
266
+
267
+ # Normalize the phone number for comparison (remove formatting)
268
+ normalized_search = normalize_phone(phone_number)
269
+
270
+ puts "Searching #{inboxes.length} inboxes for phone: #{phone_number} (normalized: #{normalized_search})" if ENV['DEBUG']
271
+
272
+ # Search through all inboxes for matching phone number
273
+ matching_inbox = inboxes.find do |inbox|
274
+ phones = inbox[:phones] || []
275
+ phones.any? do |phone_obj|
276
+ phone = phone_obj[:phone] || phone_obj['phone']
277
+ next false unless phone
278
+
279
+ normalized_inbox_phone = normalize_phone(phone)
280
+ match = normalized_inbox_phone == normalized_search
281
+
282
+ if ENV['DEBUG']
283
+ puts " Checking inbox '#{inbox[:name]}' phone '#{phone}' (normalized: #{normalized_inbox_phone}) - Match: #{match}"
284
+ end
285
+
286
+ match
287
+ end
288
+ end
289
+
290
+ if matching_inbox
291
+ puts "Found matching inbox: '#{matching_inbox[:name]}' (ID: #{matching_inbox[:id]})" if ENV['DEBUG']
292
+ matching_inbox.extend(SharedInboxMethods)
293
+ matching_inbox
294
+ else
295
+ puts "No inbox found with phone number: #{phone_number}" if ENV['DEBUG']
296
+ nil
297
+ end
298
+ rescue StandardError => e
299
+ puts "Error in find_by_phone: #{e.message}" if ENV['DEBUG']
300
+ puts e.backtrace.join("\n") if ENV['DEBUG']
301
+ nil
302
+ end
303
+
304
+
305
+ private_class_method def self.resolve_cookie_client(cookie_client)
306
+ # Use provided cookie_client or try to create one from configuration
307
+ client = cookie_client || create_cookie_client
308
+
309
+ unless client
310
+ puts 'Error: No cookie client available for authentication' if ENV['DEBUG']
311
+ return nil
312
+ end
313
+
314
+ client
315
+ end
316
+
317
+ private_class_method def self.normalize_phone(phone_number)
318
+ # Remove all non-digit characters for comparison
319
+ return '' unless phone_number
320
+ phone_number.to_s.gsub(/\D/, '')
321
+ end
322
+
323
+ def self.create_cookie_client
324
+ config = FubClient.configuration
325
+ return nil unless config.has_cookie_auth?
326
+
327
+ begin
328
+ FubClient::CookieClient.new
329
+ rescue => e
330
+ puts "Error creating cookie client: #{e.message}" if ENV['DEBUG']
331
+ nil
332
+ end
333
+ end
334
+
335
+ def self.create_faraday_connection_from_client(cookie_client)
336
+ return nil unless cookie_client && cookie_client.cookies && cookie_client.subdomain
337
+
338
+ Faraday.new(url: "https://#{cookie_client.subdomain}.followupboss.com") do |f|
339
+ f.headers['Cookie'] = cookie_client.cookies
340
+ f.headers['Accept'] = 'application/json, text/javascript, */*; q=0.01'
341
+ f.headers['Accept-Language'] = 'en-US,en;q=0.9'
342
+ f.headers['X-Requested-With'] = 'XMLHttpRequest'
343
+ f.headers['X-System'] = 'fub-spa'
344
+ f.headers['User-Agent'] =
345
+ 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36'
346
+ f.adapter :net_http
347
+ end
348
+ end
349
+
232
350
  def self.create_faraday_connection
233
351
  client = FubClient::Client.instance
234
352
  cookies = client.cookies
@@ -1,4 +1,25 @@
1
1
  module FubClient
2
2
  class User < Resource
3
+ # Add a method to get the relation if needed
4
+ def self.relation
5
+ Her::Model::Relation.new(self)
6
+ end
7
+
8
+ # Add convenience methods
9
+ def self.active
10
+ where(status: 'Active').to_a
11
+ end
12
+
13
+ def self.agents
14
+ where(role: 'Agent').to_a
15
+ end
16
+
17
+ def self.by_email(email)
18
+ where(email: email).first
19
+ end
20
+
21
+ def self.by_name(name)
22
+ where(name: name).first
23
+ end
3
24
  end
4
25
  end
@@ -1,3 +1,3 @@
1
1
  module FubClient
2
- VERSION = '1.0.0'
2
+ VERSION = '1.1.0'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: followupboss_client
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Connor Gallopo
8
8
  - Kyoto Kopz
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-07-03 00:00:00.000000000 Z
11
+ date: 2025-07-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activemodel