pylon-api 1.0.0 → 1.1.1

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: 417baee1f917bf76f2bebeafe9ac0e740ca238649225e26ad75092d368d542ef
4
- data.tar.gz: '018b75c89e76821075e2c6a9a369ce2630627d82c15a441c162aa16de0d78a2e'
3
+ metadata.gz: 4f54f7ed75e823866e06256d1a598bb3158e686131156932b7aad3d9a90da9c7
4
+ data.tar.gz: fe68e2c1be8128acadd4179b2baa2751fd9223fdbe1a5c66cb35d2d41c13fae0
5
5
  SHA512:
6
- metadata.gz: 658536c7d456207f7ff5eab110aa1f991b658753a603301512896ed22cb7434881c6800dad2593c39f457238fdcf252a98d2dc18a16d0d541fb9e8660a581e08
7
- data.tar.gz: 9de4cd5667d7dfc9a7eb890e067d3d3dc756f5f31a4a2b35ef9e49aa9790dcb31f1efd8ad7b6c421a0fd76aaf2e2de8aa38c50f4da8418efb059c30fec3f5d27
6
+ metadata.gz: 6282c28251d01ed1f7c196fbb421c3e895d37681da8e71e5941ed74db3ff88aff39e8e9c8604fe899d555e61aec5bc7b8824fcdf30e6e789c3b74086d25ae7ee
7
+ data.tar.gz: bf00a26818a96154c91a2a1f281d219372f0fd97b8b0075ddbb70fcebaa675c28b31e55a10bf218303fcc4e2b0ad15229a30950e2f2a5e41cd174995940c0a2a
data/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.1.1] - 2025-04-18
4
+
5
+ ### Fixed
6
+ - Fixed URL-based file attachments by ensuring they use proper multipart form encoding
7
+
8
+ ## [1.1.0] - 2025-04-17
9
+
10
+ ### Added
11
+ - Support for URL-based file attachments
12
+ - Enhanced attachment handling for various file types
13
+ - Added MIME type detection for file uploads
14
+
3
15
  ## [1.0.0] - 2024-03-21
4
16
 
5
17
  ### Changed
data/lib/pylon/client.rb CHANGED
@@ -6,7 +6,23 @@ module Pylon
6
6
  # @example
7
7
  # client = Pylon::Client.new(api_key: 'your_api_key')
8
8
  # issues = client.list_issues(start_time: '2024-03-01T00:00:00Z', end_time: '2024-03-31T23:59:59Z')
9
+ # issues.each { |issue| puts issue.title }
10
+ # rubocop:disable Metrics/ClassLength
9
11
  class Client
12
+ # Common MIME types for file uploads
13
+ MIME_TYPES = {
14
+ ".jpg" => "image/jpeg",
15
+ ".jpeg" => "image/jpeg",
16
+ ".png" => "image/png",
17
+ ".gif" => "image/gif",
18
+ ".pdf" => "application/pdf",
19
+ ".doc" => "application/msword",
20
+ ".docx" => "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
21
+ ".xls" => "application/vnd.ms-excel",
22
+ ".xlsx" => "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
23
+ ".csv" => "text/csv",
24
+ ".txt" => "text/plain"
25
+ }
10
26
  # Base URL for the Pylon API
11
27
  BASE_URL = "https://api.usepylon.com"
12
28
 
@@ -28,76 +44,104 @@ module Pylon
28
44
  #
29
45
  # @param page [Integer] Page number for pagination
30
46
  # @param per_page [Integer] Number of items per page
31
- # @return [Array<Hash>] List of accounts
47
+ # @return [Models::Collection<Models::Account>] Collection of account objects
32
48
  def list_accounts(page: 1, per_page: 20)
33
- get("/accounts", query: { page: page, per_page: per_page })
49
+ get("/accounts", query: { page: page, per_page: per_page },
50
+ model_class: Models::Account, collection: true)
34
51
  end
35
52
 
36
53
  # Get details for a specific account
37
54
  #
38
55
  # @param account_id [String] The ID of the account to retrieve
39
- # @return [Hash] Account details
56
+ # @return [Models::Account] Account object
40
57
  def get_account(account_id)
41
- get("/accounts/#{account_id}")
58
+ get("/accounts/#{account_id}", model_class: Models::Account)
42
59
  end
43
60
 
44
61
  # Create a new attachment
45
62
  #
46
- # @param file [String] The file content to upload
47
- # @return [Hash] Created attachment details
48
- def create_attachment(file)
49
- post("/attachments", body: { file: file })
63
+ # @param file [File, String, nil] The file to upload (File object or file content)
64
+ # @param description [String, nil] A text description of the file
65
+ # @param file_url [String, nil] A URL to fetch the file from if file is not provided
66
+ # @return [Models::Attachment] Created attachment object
67
+ # @raise [ArgumentError] If neither file nor file_url is provided
68
+ def create_attachment(file = nil, description: nil, file_url: nil)
69
+ if file.nil? && file_url.nil?
70
+ raise ArgumentError, "Either file or file_url must be provided"
71
+ end
72
+
73
+ params = {}
74
+ params[:description] = description if description
75
+ params[:file_url] = file_url if file_url
76
+
77
+ if file
78
+ if file.is_a?(::File) || (file.respond_to?(:path) && file.respond_to?(:read))
79
+ # For File objects or IO-like objects
80
+ params[:file] = Faraday::UploadIO.new(file, mime_type_for_file(file), File.basename(file.path))
81
+ else
82
+ # For raw content as string, create a temp file
83
+ temp_file = Tempfile.new(["upload", ".bin"])
84
+ temp_file.binmode
85
+ temp_file.write(file)
86
+ temp_file.rewind
87
+ params[:file] = Faraday::UploadIO.new(temp_file, "application/octet-stream")
88
+ end
89
+ end
90
+
91
+ # Always use multipart for attachments, whether file or file_url
92
+ post_multipart("/attachments", params, model_class: Models::Attachment)
50
93
  end
51
94
 
52
95
  # Get details for a specific attachment
53
96
  #
54
97
  # @param attachment_id [String] The ID of the attachment to retrieve
55
- # @return [Hash] Attachment details
98
+ # @return [Models::Attachment] Attachment object
56
99
  def get_attachment(attachment_id)
57
- get("/attachments/#{attachment_id}")
100
+ get("/attachments/#{attachment_id}", model_class: Models::Attachment)
58
101
  end
59
102
 
60
103
  # List all contacts with pagination
61
104
  #
62
105
  # @param page [Integer] Page number for pagination
63
106
  # @param per_page [Integer] Number of items per page
64
- # @return [Array<Hash>] List of contacts
107
+ # @return [Models::Collection<Models::Contact>] Collection of contact objects
65
108
  def list_contacts(page: 1, per_page: 20)
66
- get("/contacts", query: { page: page, per_page: per_page })
109
+ get("/contacts", query: { page: page, per_page: per_page },
110
+ model_class: Models::Contact, collection: true)
67
111
  end
68
112
 
69
113
  # Create a new contact
70
114
  #
71
115
  # @param params [Hash] Contact parameters
72
- # @return [Hash] Created contact details
116
+ # @return [Models::Contact] Created contact object
73
117
  def create_contact(params)
74
- post("/contacts", body: params)
118
+ post("/contacts", body: params, model_class: Models::Contact)
75
119
  end
76
120
 
77
121
  # Get details for a specific contact
78
122
  #
79
123
  # @param contact_id [String] The ID of the contact to retrieve
80
- # @return [Hash] Contact details
124
+ # @return [Models::Contact] Contact object
81
125
  def get_contact(contact_id)
82
- get("/contacts/#{contact_id}")
126
+ get("/contacts/#{contact_id}", model_class: Models::Contact)
83
127
  end
84
128
 
85
129
  # Update an existing contact
86
130
  #
87
131
  # @param contact_id [String] The ID of the contact to update
88
132
  # @param params [Hash] Updated contact parameters
89
- # @return [Hash] Updated contact details
133
+ # @return [Models::Contact] Updated contact object
90
134
  def update_contact(contact_id, params)
91
- patch("/contacts/#{contact_id}", body: params)
135
+ patch("/contacts/#{contact_id}", body: params, model_class: Models::Contact)
92
136
  end
93
137
 
94
138
  # List all custom fields with pagination
95
139
  #
96
140
  # @param page [Integer] Page number for pagination
97
141
  # @param per_page [Integer] Number of items per page
98
- # @return [Array<Hash>] List of custom fields
142
+ # @return [Models::Collection] Collection of custom fields
99
143
  def list_custom_fields(page: 1, per_page: 20)
100
- get("/custom_fields", query: { page: page, per_page: per_page })
144
+ get("/custom_fields", query: { page: page, per_page: per_page }, collection: true)
101
145
  end
102
146
 
103
147
  # Create a new custom field
@@ -115,7 +159,7 @@ module Pylon
115
159
  # @param page [Integer] Page number for pagination
116
160
  # @param per_page [Integer] Number of items per page
117
161
  # @param filters [Hash] Additional filters to apply
118
- # @return [Array<Hash>] List of issues
162
+ # @return [Models::Collection<Models::Issue>] Collection of issue objects
119
163
  # @raise [ArgumentError] If start_time or end_time is missing
120
164
  def list_issues(start_time:, end_time:, page: 1, per_page: 20, **filters)
121
165
  raise ArgumentError, "start_time is required" unless start_time
@@ -126,135 +170,140 @@ module Pylon
126
170
  end_time: end_time,
127
171
  page: page,
128
172
  per_page: per_page
129
- ))
173
+ ), model_class: Models::Issue, collection: true)
130
174
  end
131
175
 
132
176
  # Create a new issue
133
177
  #
134
178
  # @param params [Hash] Issue parameters
135
- # @return [Hash] Created issue details
179
+ # @return [Models::Issue] Created issue object
136
180
  def create_issue(params)
137
- post("/issues", body: params)
181
+ post("/issues", body: params, model_class: Models::Issue)
138
182
  end
139
183
 
140
184
  # Get details for a specific issue
141
185
  #
142
186
  # @param issue_id [String] The ID of the issue to retrieve
143
- # @return [Hash] Issue details
187
+ # @return [Models::Issue] Issue object
144
188
  def get_issue(issue_id)
145
- get("/issues/#{issue_id}")
189
+ get("/issues/#{issue_id}", model_class: Models::Issue)
146
190
  end
147
191
 
148
192
  # Update an existing issue
149
193
  #
150
194
  # @param issue_id [String] The ID of the issue to update
151
195
  # @param params [Hash] Updated issue parameters
152
- # @return [Hash] Updated issue details
196
+ # @return [Models::Issue] Updated issue object
153
197
  def update_issue(issue_id, params)
154
- patch("/issues/#{issue_id}", body: params)
198
+ patch("/issues/#{issue_id}", body: params, model_class: Models::Issue)
155
199
  end
156
200
 
157
201
  # Snooze an issue until a specified time
158
202
  #
159
203
  # @param issue_id [String] The ID or number of the issue to snooze
160
204
  # @param snooze_until [String] The date and time to snooze the issue until (RFC3339 format)
161
- # @return [Hash] Updated issue details
205
+ # @return [Models::Issue] Updated issue object
162
206
  def snooze_issue(issue_id, snooze_until:)
163
- post("/issues/#{issue_id}/snooze", body: { snooze_until: snooze_until })
207
+ post("/issues/#{issue_id}/snooze", body: { snooze_until: snooze_until },
208
+ model_class: Models::Issue)
164
209
  end
165
210
 
166
211
  # List all knowledge base articles with pagination
167
212
  #
168
213
  # @param page [Integer] Page number for pagination
169
214
  # @param per_page [Integer] Number of items per page
170
- # @return [Array<Hash>] List of articles
215
+ # @return [Models::Collection<Models::Article>] Collection of article objects
171
216
  def list_articles(page: 1, per_page: 20)
172
- get("/knowledge_base/articles", query: { page: page, per_page: per_page })
217
+ get("/knowledge_base/articles", query: { page: page, per_page: per_page },
218
+ model_class: Models::Article, collection: true)
173
219
  end
174
220
 
175
221
  # Get details for a specific article
176
222
  #
177
223
  # @param article_id [String] The ID of the article to retrieve
178
- # @return [Hash] Article details
224
+ # @return [Models::Article] Article object
179
225
  def get_article(article_id)
180
- get("/knowledge_base/articles/#{article_id}")
226
+ get("/knowledge_base/articles/#{article_id}", model_class: Models::Article)
181
227
  end
182
228
 
183
229
  # Get details for the current user
184
230
  #
185
- # @return [Hash] Current user details
231
+ # @return [Models::User] Current user object
186
232
  def get_current_user
187
- get("/me")
233
+ get("/me", model_class: Models::User)
188
234
  end
189
235
 
190
236
  # List all tags with pagination
191
237
  #
192
238
  # @param page [Integer] Page number for pagination
193
239
  # @param per_page [Integer] Number of items per page
194
- # @return [Array<Hash>] List of tags
240
+ # @return [Models::Collection<Models::Tag>] Collection of tag objects
195
241
  def list_tags(page: 1, per_page: 20)
196
- get("/tags", query: { page: page, per_page: per_page })
242
+ get("/tags", query: { page: page, per_page: per_page },
243
+ model_class: Models::Tag, collection: true)
197
244
  end
198
245
 
199
246
  # Create a new tag
200
247
  #
201
248
  # @param name [String] The name of the tag
202
249
  # @param color [String] Optional hex color code for the tag
203
- # @return [Hash] Created tag details
250
+ # @return [Models::Tag] Created tag object
204
251
  def create_tag(name:, color: nil)
205
- post("/tags", body: { name: name, color: color }.compact)
252
+ post("/tags", body: { name: name, color: color }.compact, model_class: Models::Tag)
206
253
  end
207
254
 
208
255
  # List all teams with pagination
209
256
  #
210
257
  # @param page [Integer] Page number for pagination
211
258
  # @param per_page [Integer] Number of items per page
212
- # @return [Array<Hash>] List of teams
259
+ # @return [Models::Collection<Models::Team>] Collection of team objects
213
260
  def list_teams(page: 1, per_page: 20)
214
- get("/teams", query: { page: page, per_page: per_page })
261
+ get("/teams", query: { page: page, per_page: per_page },
262
+ model_class: Models::Team, collection: true)
215
263
  end
216
264
 
217
265
  # Create a new team
218
266
  #
219
267
  # @param params [Hash] Team parameters
220
- # @return [Hash] Created team details
268
+ # @return [Models::Team] Created team object
221
269
  def create_team(params)
222
- post("/teams", body: params)
270
+ post("/teams", body: params, model_class: Models::Team)
223
271
  end
224
272
 
225
273
  # Get details for a specific team
226
274
  #
227
275
  # @param team_id [String] The ID of the team to retrieve
228
- # @return [Hash] Team details
276
+ # @return [Models::Team] Team object
229
277
  def get_team(team_id)
230
- get("/teams/#{team_id}")
278
+ get("/teams/#{team_id}", model_class: Models::Team)
231
279
  end
232
280
 
233
281
  # List all ticket forms with pagination
234
282
  #
235
283
  # @param page [Integer] Page number for pagination
236
284
  # @param per_page [Integer] Number of items per page
237
- # @return [Array<Hash>] List of ticket forms
285
+ # @return [Models::Collection<Models::TicketForm>] Collection of ticket form objects
238
286
  def list_ticket_forms(page: 1, per_page: 20)
239
- get("/ticket-forms", query: { page: page, per_page: per_page })
287
+ get("/ticket-forms", query: { page: page, per_page: per_page },
288
+ model_class: Models::TicketForm, collection: true)
240
289
  end
241
290
 
242
291
  # Create a new ticket form
243
292
  #
244
293
  # @param name [String] The name of the form
245
294
  # @param fields [Array<Hash>] Array of form field definitions
246
- # @return [Hash] Created ticket form details
295
+ # @return [Models::TicketForm] Created ticket form object
247
296
  def create_ticket_form(name:, fields: [])
248
- post("/ticket-forms", body: { name: name, fields: fields })
297
+ post("/ticket-forms", body: { name: name, fields: fields }, model_class: Models::TicketForm)
249
298
  end
250
299
 
251
300
  # List all user roles with pagination
252
301
  #
253
302
  # @param page [Integer] Page number for pagination
254
303
  # @param per_page [Integer] Number of items per page
255
- # @return [Array<Hash>] List of user roles
304
+ # @return [Models::Collection] Collection of user role objects
256
305
  def list_user_roles(page: 1, per_page: 20)
257
- get("/user_roles", query: { page: page, per_page: per_page })
306
+ get("/user_roles", query: { page: page, per_page: per_page }, collection: true)
258
307
  end
259
308
 
260
309
  # Get details for a specific user role
@@ -269,34 +318,35 @@ module Pylon
269
318
  #
270
319
  # @param page [Integer] Page number for pagination
271
320
  # @param per_page [Integer] Number of items per page
272
- # @return [Array<Hash>] List of users
321
+ # @return [Models::Collection<Models::User>] Collection of user objects
273
322
  def list_users(page: 1, per_page: 20)
274
- get("/users", query: { page: page, per_page: per_page })
323
+ get("/users", query: { page: page, per_page: per_page },
324
+ model_class: Models::User, collection: true)
275
325
  end
276
326
 
277
327
  # Create a new user
278
328
  #
279
329
  # @param params [Hash] User parameters
280
- # @return [Hash] Created user details
330
+ # @return [Models::User] Created user object
281
331
  def create_user(params)
282
- post("/users", body: params)
332
+ post("/users", body: params, model_class: Models::User)
283
333
  end
284
334
 
285
335
  # Get details for a specific user
286
336
  #
287
337
  # @param user_id [String] The ID of the user to retrieve
288
- # @return [Hash] User details
338
+ # @return [Models::User] User object
289
339
  def get_user(user_id)
290
- get("/users/#{user_id}")
340
+ get("/users/#{user_id}", model_class: Models::User)
291
341
  end
292
342
 
293
343
  # Update an existing user
294
344
  #
295
345
  # @param user_id [String] The ID of the user to update
296
346
  # @param params [Hash] Updated user parameters
297
- # @return [Hash] Updated user details
347
+ # @return [Models::User] Updated user object
298
348
  def update_user(user_id, params)
299
- patch("/users/#{user_id}", body: params)
349
+ patch("/users/#{user_id}", body: params, model_class: Models::User)
300
350
  end
301
351
 
302
352
  private
@@ -315,26 +365,71 @@ module Pylon
315
365
  end
316
366
  end
317
367
 
368
+ # Determine MIME type for file
369
+ #
370
+ # @param file [File] The file to determine MIME type for
371
+ # @return [String] The MIME type
372
+ def mime_type_for_file(file)
373
+ return "application/octet-stream" unless file.respond_to?(:path)
374
+
375
+ ext = File.extname(file.path).downcase
376
+ MIME_TYPES[ext] || "application/octet-stream"
377
+ end
378
+
318
379
  # Handle API response and raise appropriate errors
319
380
  #
320
381
  # @param response [Faraday::Response] The API response
321
- # @return [Array] Array containing response data and response object
382
+ # @param model_class [Class] The model class to use for wrapping the response
383
+ # @param collection [Boolean] Whether the response is a collection of items
384
+ # @return [Models::Base, Models::Collection, Array] Model, Collection, or response data array
322
385
  # @raise [AuthenticationError] If authentication fails
323
386
  # @raise [ResourceNotFoundError] If resource is not found
324
387
  # @raise [ValidationError] If request parameters are invalid
325
388
  # @raise [ApiError] For other API errors
326
- def handle_response(response)
389
+ def handle_response(response, model_class = nil, collection = false)
327
390
  if @debug
328
391
  puts "Request URL: #{response.env.url}"
329
392
  puts "Response status: #{response.status}"
330
393
  puts "Response body: #{response.body.inspect}"
331
394
  end
332
395
 
333
- case response.status
334
- when 200..299
335
- data = response.body
336
- data = data["data"] if data.is_a?(Hash) && data.key?("data")
396
+ if response.status.between?(200, 299)
397
+ return handle_successful_response(response, model_class, collection)
398
+ else
399
+ handle_error_response(response)
400
+ end
401
+ end
402
+
403
+ # Handle successful API response
404
+ #
405
+ # @param response [Faraday::Response] The API response
406
+ # @param model_class [Class] The model class to use for wrapping the response
407
+ # @param collection [Boolean] Whether the response is a collection of items
408
+ # @return [Models::Base, Models::Collection, Array] Wrapped response data
409
+ def handle_successful_response(response, model_class, collection)
410
+ data = response.body
411
+ data = data["data"] if data.is_a?(Hash) && data.key?("data")
412
+
413
+ if model_class
414
+ if collection
415
+ Models::Collection.new(data, model_class, response)
416
+ else
417
+ model_class.new(data, response)
418
+ end
419
+ else
337
420
  [data, response]
421
+ end
422
+ end
423
+
424
+ # Handle error API response
425
+ #
426
+ # @param response [Faraday::Response] The API response
427
+ # @raise [AuthenticationError] If authentication fails
428
+ # @raise [ResourceNotFoundError] If resource is not found
429
+ # @raise [ValidationError] If request parameters are invalid
430
+ # @raise [ApiError] For other API errors
431
+ def handle_error_response(response)
432
+ case response.status
338
433
  when 401
339
434
  raise AuthenticationError, parse_error_message(response) || "Invalid API key"
340
435
  when 404
@@ -366,35 +461,71 @@ module Pylon
366
461
  #
367
462
  # @param path [String] The API endpoint path
368
463
  # @param query [Hash] Query parameters
369
- # @return [Array] Array containing response data and response object
370
- def get(path, query: {})
371
- handle_response(connection.get(path, query))
464
+ # @param model_class [Class] The model class to use for wrapping the response
465
+ # @param collection [Boolean] Whether the response is a collection of items
466
+ # @return [Models::Base, Models::Collection, Array] Model, Collection, or response data array
467
+ def get(path, query: {}, model_class: nil, collection: false)
468
+ handle_response(connection.get(path, query), model_class, collection)
372
469
  end
373
470
 
374
471
  # Make a POST request
375
472
  #
376
473
  # @param path [String] The API endpoint path
377
474
  # @param body [Hash] Request body
378
- # @return [Array] Array containing response data and response object
379
- def post(path, body: {})
380
- handle_response(connection.post(path, body.to_json))
475
+ # @param model_class [Class] The model class to use for wrapping the response
476
+ # @param collection [Boolean] Whether the response is a collection of items
477
+ # @return [Models::Base, Models::Collection, Array] Model, Collection, or response data array
478
+ def post(path, body: {}, model_class: nil, collection: false)
479
+ handle_response(connection.post(path, body.to_json), model_class, collection)
480
+ end
481
+
482
+ # Make a multipart POST request for file uploads
483
+ #
484
+ # @param path [String] The API endpoint path
485
+ # @param params [Hash] Request parameters including file uploads
486
+ # @param model_class [Class] The model class to use for wrapping the response
487
+ # @param collection [Boolean] Whether the response is a collection of items
488
+ # @return [Models::Base, Models::Collection, Array] Model, Collection, or response data array
489
+ def post_multipart(path, params = {}, model_class: nil, collection: false)
490
+ # Create a connection without the default JSON content type for multipart uploads
491
+ multipart_conn = Faraday.new(@base_url) do |f|
492
+ f.request :multipart
493
+ # Only force multipart for file_url uploads, omit url_encoded middleware
494
+ f.response :json, content_type: /\bjson$/
495
+ f.response :logger if @debug
496
+ f.adapter Faraday.default_adapter
497
+ f.headers["Authorization"] = "Bearer #{api_key}"
498
+ f.headers["Accept"] = "application/json"
499
+ end
500
+
501
+ # If we have a file_url but no actual file, create a dummy file part to force multipart encoding
502
+ if params[:file_url] && !params[:file]
503
+ # Create an empty file part to force multipart encoding
504
+ params[:_dummy] = Faraday::FilePart.new(StringIO.new(""), "application/octet-stream", "dummy")
505
+ end
506
+
507
+ handle_response(multipart_conn.post(path, params), model_class, collection)
381
508
  end
382
509
 
383
510
  # Make a PATCH request
384
511
  #
385
512
  # @param path [String] The API endpoint path
386
513
  # @param body [Hash] Request body
387
- # @return [Array] Array containing response data and response object
388
- def patch(path, body: {})
389
- handle_response(connection.patch(path, body.to_json))
514
+ # @param model_class [Class] The model class to use for wrapping the response
515
+ # @param collection [Boolean] Whether the response is a collection of items
516
+ # @return [Models::Base, Models::Collection, Array] Model, Collection, or response data array
517
+ def patch(path, body: {}, model_class: nil, collection: false)
518
+ handle_response(connection.patch(path, body.to_json), model_class, collection)
390
519
  end
391
520
 
392
521
  # Make a DELETE request
393
522
  #
394
523
  # @param path [String] The API endpoint path
395
- # @return [Array] Array containing response data and response object
396
- def delete(path)
397
- handle_response(connection.delete(path))
524
+ # @param model_class [Class] The model class to use for wrapping the response
525
+ # @param collection [Boolean] Whether the response is a collection of items
526
+ # @return [Models::Base, Models::Collection, Array] Model, Collection, or response data array
527
+ def delete(path, model_class: nil, collection: false)
528
+ handle_response(connection.delete(path), model_class, collection)
398
529
  end
399
530
  end
400
- end
531
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pylon
4
+ module Models
5
+ class Account < Base
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pylon
4
+ module Models
5
+ class Article < Base
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pylon
4
+ module Models
5
+ class Attachment < Base
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pylon
4
+ module Models
5
+ class Base
6
+ attr_reader :attributes, :_response
7
+
8
+ def initialize(attributes = {}, response = nil)
9
+ @attributes = attributes || {}
10
+ @_response = response
11
+ end
12
+
13
+ def method_missing(method_name, *args, &block)
14
+ key = method_name.to_s
15
+ if @attributes.key?(key)
16
+ @attributes[key]
17
+ else
18
+ super
19
+ end
20
+ end
21
+
22
+ def respond_to_missing?(method_name, include_private = false)
23
+ @attributes.key?(method_name.to_s) || super
24
+ end
25
+
26
+ def [](key)
27
+ @attributes[key.to_s]
28
+ end
29
+
30
+ def to_h
31
+ @attributes
32
+ end
33
+ alias to_hash to_h
34
+
35
+ def inspect
36
+ "#<#{self.class.name} #{@attributes.inspect}>"
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pylon
4
+ module Models
5
+ class Collection
6
+ include Enumerable
7
+
8
+ attr_reader :items, :_response
9
+
10
+ def initialize(items = [], model_class = nil, response = nil)
11
+ @items = items.map do |item|
12
+ model_class ? model_class.new(item) : item
13
+ end
14
+ @_response = response
15
+ end
16
+
17
+ def each(&block)
18
+ @items.each(&block)
19
+ end
20
+
21
+ def [](index)
22
+ @items[index]
23
+ end
24
+
25
+ def size
26
+ @items.size
27
+ end
28
+ alias length size
29
+
30
+ def to_a
31
+ @items
32
+ end
33
+
34
+ def inspect
35
+ "#<#{self.class.name} items=#{@items.size}>"
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pylon
4
+ module Models
5
+ class Contact < Base
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pylon
4
+ module Models
5
+ class Issue < Base
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pylon
4
+ module Models
5
+ class Tag < Base
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pylon
4
+ module Models
5
+ class Team < Base
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pylon
4
+ module Models
5
+ class TicketForm < Base
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pylon
4
+ module Models
5
+ class User < Base
6
+ end
7
+ end
8
+ end
data/lib/pylon/version.rb CHANGED
@@ -4,9 +4,9 @@ module Pylon
4
4
  # Major version for breaking changes
5
5
  MAJOR = 1
6
6
  # Minor version for new features
7
- MINOR = 0
7
+ MINOR = 1
8
8
  # Patch version for bug fixes
9
- PATCH = 0
9
+ PATCH = 1
10
10
  # Pre-release version (optional)
11
11
  PRE = nil
12
12
 
data/lib/pylon.rb CHANGED
@@ -3,8 +3,20 @@
3
3
  require "faraday"
4
4
  require "faraday/multipart"
5
5
  require "json"
6
+ require "tempfile"
6
7
 
7
8
  require_relative "pylon/version"
9
+ require_relative "pylon/models/base"
10
+ require_relative "pylon/models/collection"
11
+ require_relative "pylon/models/user"
12
+ require_relative "pylon/models/account"
13
+ require_relative "pylon/models/issue"
14
+ require_relative "pylon/models/team"
15
+ require_relative "pylon/models/tag"
16
+ require_relative "pylon/models/attachment"
17
+ require_relative "pylon/models/contact"
18
+ require_relative "pylon/models/ticket_form"
19
+ require_relative "pylon/models/article"
8
20
  require_relative "pylon/client"
9
21
 
10
22
  module Pylon
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pylon-api
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ben Odom
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-04-15 00:00:00.000000000 Z
11
+ date: 2025-04-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -134,6 +134,17 @@ files:
134
134
  - README.md
135
135
  - lib/pylon.rb
136
136
  - lib/pylon/client.rb
137
+ - lib/pylon/models/account.rb
138
+ - lib/pylon/models/article.rb
139
+ - lib/pylon/models/attachment.rb
140
+ - lib/pylon/models/base.rb
141
+ - lib/pylon/models/collection.rb
142
+ - lib/pylon/models/contact.rb
143
+ - lib/pylon/models/issue.rb
144
+ - lib/pylon/models/tag.rb
145
+ - lib/pylon/models/team.rb
146
+ - lib/pylon/models/ticket_form.rb
147
+ - lib/pylon/models/user.rb
137
148
  - lib/pylon/version.rb
138
149
  - lib/tasks/version.rake
139
150
  homepage: https://github.com/benjodo/pylon-api