zotero-rb 0.1.4 → 0.1.5

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: fec7634f993dfdf13ab89d23638bb4a1754a386749fc375eee188c0731be65cc
4
- data.tar.gz: af1f817fe3c696dc7f480100db3a6bc6d78a8ed902dfe4624840af931c11e97d
3
+ metadata.gz: 6480cd849dc46519c7a122f454e93f34befae86b340703dedc69781c59cb9a37
4
+ data.tar.gz: c9890904401b2589eef07693b5e42fb798bbf81779fc2449e941cd6a1a33110b
5
5
  SHA512:
6
- metadata.gz: c7ee12554c2e9aff9a6e05e6929da003ff65fa09ebd5301e8eb56e44cfbf3c9048ad95578ede3945f09445a00175d38680dc95654f9408f067ca6e98dcf62a8f
7
- data.tar.gz: c7351998cf6efff225f4b19dfdc6a8d14a5414fed82f1e81e8b68f9d8b20ead9f1a675fef2dfdd7902a97e38f540e6a849e9c31a8cc6846514c832a75494119e
6
+ metadata.gz: 5c15301b1d99903ee690fca6dc77de020122fdf82ea46fe156238e16ef792b44e0ab6847548ff6bf9a563264a255719fb1caada748adbf9d8c0e1b0f35aaa4a7
7
+ data.tar.gz: a25a0ad4d3e46e8577380c653fc25f472232c5d631451135f5c3acba468d8970f9cf21f4789b9ad691a1f2b2a5e4ca8651d06cef1f7e7081d42f619da404b2f5
data/CHANGELOG.md CHANGED
@@ -5,6 +5,14 @@ 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
+ ## [0.1.5](https://github.com/andrewhwaller/zotero-rb/compare/v0.1.4...v0.1.5) (2025-09-11)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * correct test expectations for refactored parameter signature ([eaad0bb](https://github.com/andrewhwaller/zotero-rb/commit/eaad0bbd0ce87cbe640b4ca7190759170ac11494))
14
+ * Correct test method calls to match API signatures ([76dd63e](https://github.com/andrewhwaller/zotero-rb/commit/76dd63e4d6e36c18629c377d7084efc8c6159560))
15
+
8
16
  ## [1.0.0](https://github.com/andrewhwaller/zotero-rb/compare/v0.1.2...v1.0.0) (2025-09-04)
9
17
 
10
18
 
data/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
 
6
6
  A comprehensive Ruby client for the [Zotero Web API v3](https://www.zotero.org/support/dev/web_api/v3/start).
7
7
 
8
- NOTE: This gem is experimental and has not been fully tested with real data. So far, the gem has been set up to cover Zotero's web API documentation as much as possible, but testing is still ongoing. Do not use this gem for production applications without exercising due caution.
8
+ NOTE: This gem is experimental and has not been fully tested with real data. So far, the gem has been set up to cover Zotero's web API documentation as much as possible, but testing is still ongoing. Do not use this gem for production applications without exercising due caution. Having said that, if you come across something that doesn't work, open up an issue or even a PR and I'd be happy to get a fix going.
9
9
 
10
10
  ## Installation
11
11
 
data/lib/zotero/client.rb CHANGED
@@ -35,52 +35,11 @@ module Zotero
35
35
  # Initialize a new Zotero API client.
36
36
  #
37
37
  # @param api_key [String] Your Zotero API key from https://www.zotero.org/settings/keys
38
+ # @raise [ArgumentError] if api_key is nil or empty
38
39
  def initialize(api_key:)
39
40
  @api_key = api_key
40
41
  end
41
42
 
42
- def get(path, params: {})
43
- response = http_request(:get, path,
44
- headers: auth_headers.merge(default_headers),
45
- params: params)
46
- handle_response(response, params[:format])
47
- end
48
-
49
- def post(path, data:, version: nil, write_token: nil, params: {})
50
- headers = build_write_headers(version: version, write_token: write_token)
51
- response = http_request(:post, path,
52
- headers: headers,
53
- body: data,
54
- params: params)
55
- handle_write_response(response)
56
- end
57
-
58
- def patch(path, data:, version: nil, params: {})
59
- headers = build_write_headers(version: version)
60
- response = http_request(:patch, path,
61
- headers: headers,
62
- body: data,
63
- params: params)
64
- handle_write_response(response)
65
- end
66
-
67
- def put(path, data:, version: nil, params: {})
68
- headers = build_write_headers(version: version)
69
- response = http_request(:put, path,
70
- headers: headers,
71
- body: data,
72
- params: params)
73
- handle_write_response(response)
74
- end
75
-
76
- def delete(path, version: nil, params: {})
77
- headers = build_write_headers(version: version)
78
- response = http_request(:delete, path,
79
- headers: headers,
80
- params: params)
81
- handle_write_response(response)
82
- end
83
-
84
43
  # Get a Library instance for a specific user.
85
44
  #
86
45
  # @param user_id [Integer, String] The Zotero user ID
@@ -97,6 +56,33 @@ module Zotero
97
56
  Library.new(client: self, type: :group, id: group_id)
98
57
  end
99
58
 
59
+ # Make a GET request to the Zotero API.
60
+ # This is the main public interface for read operations.
61
+ #
62
+ # @param path [String] The API endpoint path
63
+ # @param params [Hash] Query parameters for the request
64
+ # @return [Array, Hash] The parsed response data
65
+ def make_get_request(path, params: {})
66
+ headers = auth_headers.merge(default_headers)
67
+ response = http_request(:get, path, headers: headers, params: params)
68
+ handle_response(response, params[:format])
69
+ end
70
+
71
+ # Make a write request (POST, PATCH, PUT, DELETE) to the Zotero API.
72
+ # This is the main public interface for write operations.
73
+ #
74
+ # @param method [Symbol] The HTTP method (:post, :patch, :put, :delete)
75
+ # @param path [String] The API endpoint path
76
+ # @param data [Hash, Array] Optional request body data
77
+ # @param options [Hash] Write options (version: Integer, write_token: String)
78
+ # @param params [Hash] Query parameters for the request
79
+ # @return [Hash, Boolean] The parsed response data or success status
80
+ def make_write_request(method, path, data: nil, options: {}, params: {})
81
+ headers = build_write_headers(version: options[:version], write_token: options[:write_token])
82
+ response = http_request(method, path, headers: headers, body: data, params: params)
83
+ handle_write_response(response)
84
+ end
85
+
100
86
  protected
101
87
 
102
88
  def http_request(method, path, **options)
@@ -107,18 +93,54 @@ module Zotero
107
93
  connection = HTTPConnection.new(uri)
108
94
  request = build_request(method, uri, request_options[:headers], request_options[:body], request_options)
109
95
 
110
- net_response = connection.request(request)
111
- ResponseAdapter.new(net_response, uri)
96
+ connection.request(request)
112
97
  end
113
98
  end
114
99
 
100
+ def auth_headers
101
+ { "Zotero-API-Key" => api_key }
102
+ end
103
+
104
+ def default_headers
105
+ { "Zotero-API-Version" => "3" }
106
+ end
107
+
108
+ def build_write_headers(version: nil, write_token: nil)
109
+ headers = auth_headers.merge(default_headers)
110
+ headers["Content-Type"] = "application/json"
111
+ headers["If-Unmodified-Since-Version"] = version.to_s if version
112
+ headers["Zotero-Write-Token"] = write_token if write_token
113
+ headers
114
+ end
115
+
116
+ def handle_response(response, format = nil)
117
+ return parse_response_body(response, format) if response.code.to_i.between?(200, 299)
118
+
119
+ raise_error_for_status(response)
120
+ end
121
+
122
+ def handle_write_response(response)
123
+ case response.code.to_i
124
+ when 200
125
+ parse_json_response(response)
126
+ when 204
127
+ true
128
+ else
129
+ raise_error_for_status(response)
130
+ end
131
+ end
132
+
133
+ private
134
+
135
+ attr_reader :api_key
136
+
115
137
  def build_request_options(options)
116
138
  {
117
139
  headers: options[:headers] || {},
118
140
  body: options[:body],
119
141
  params: options[:params] || {},
120
- multipart: options.dig(:options, :multipart),
121
- format: options.dig(:options, :format)
142
+ multipart: options[:multipart],
143
+ format: options[:format]
122
144
  }
123
145
  end
124
146
 
@@ -171,109 +193,22 @@ module Zotero
171
193
  end
172
194
  end
173
195
 
174
- private
175
-
176
- attr_reader :api_key
177
-
178
- def auth_headers
179
- { "Zotero-API-Key" => api_key }
180
- end
181
-
182
- def default_headers
183
- { "Zotero-API-Version" => "3" }
184
- end
185
-
186
- def build_write_headers(version: nil, write_token: nil)
187
- headers = auth_headers.merge(default_headers)
188
- headers["Content-Type"] = "application/json"
189
- headers["If-Unmodified-Since-Version"] = version.to_s if version
190
- headers["Zotero-Write-Token"] = write_token if write_token
191
- headers
192
- end
193
-
194
- def handle_response(response, format = nil)
195
- return parse_response_body(response, format) if response.code.between?(200, 299)
196
-
197
- raise_error_for_status(response)
198
- end
199
-
200
- def handle_write_response(response)
201
- case response.code
202
- when 200
203
- response.parsed_response
204
- when 204
205
- true
206
- else
207
- raise_error_for_status(response)
208
- end
209
- end
210
-
211
196
  def parse_response_body(response, format)
212
197
  case format&.to_s
213
198
  when "json", nil
214
- response.parsed_response
199
+ parse_json_response(response)
215
200
  else
216
201
  response.body
217
202
  end
218
203
  end
219
- end
220
- # rubocop:enable Metrics/ClassLength
221
-
222
- # Adapter to provide HTTParty-compatible interface for Net::HTTP responses
223
- class ResponseAdapter
224
- attr_reader :net_response, :uri
225
-
226
- def initialize(net_response, uri)
227
- @net_response = net_response
228
- @uri = uri
229
- end
230
204
 
231
- def code
232
- @net_response.code.to_i
233
- end
234
-
235
- def parsed_response
236
- @parsed_response ||= parse_body
237
- end
238
-
239
- def body
240
- @net_response.body
241
- end
242
-
243
- def headers
244
- @headers ||= @net_response.to_hash.transform_values(&:first)
245
- end
205
+ def parse_json_response(response)
206
+ return nil if response.body.nil? || response.body.empty?
246
207
 
247
- def message
248
- @net_response.message
249
- end
250
-
251
- def request
252
- @request ||= RequestAdapter.new(@uri)
253
- end
254
-
255
- private
256
-
257
- def parse_body
258
- content_type = @net_response.content_type
259
- return nil if @net_response.body.nil? || @net_response.body.empty?
260
-
261
- if content_type&.include?("application/json")
262
- JSON.parse(@net_response.body)
263
- else
264
- @net_response.body
265
- end
208
+ JSON.parse(response.body)
266
209
  rescue JSON::ParserError
267
- @net_response.body
268
- end
269
- end
270
-
271
- # Adapter to provide request.path access for error handling
272
- class RequestAdapter
273
- attr_reader :path
274
-
275
- def initialize(uri)
276
- @path = uri.path
210
+ response.body
277
211
  end
278
212
  end
213
+ # rubocop:enable Metrics/ClassLength
279
214
  end
data/lib/zotero/fields.rb CHANGED
@@ -1,14 +1,24 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Zotero
4
- # Field discovery methods
4
+ # Field discovery methods for Zotero items and creators
5
5
  module Fields
6
+ # Get all available item fields.
7
+ #
8
+ # @param locale [String] Optional locale for localized field names (e.g. 'en-US', 'fr-FR')
9
+ # @return [Array<Hash>] Array of field definitions with field names and localized labels
6
10
  def item_fields(locale: nil)
7
- get("/itemFields", params: build_locale_params(locale))
11
+ params = build_locale_params(locale)
12
+ make_get_request("/itemFields", params: params)
8
13
  end
9
14
 
15
+ # Get all available creator fields.
16
+ #
17
+ # @param locale [String] Optional locale for localized field names (e.g. 'en-US', 'fr-FR')
18
+ # @return [Array<Hash>] Array of creator field definitions with field names and localized labels
10
19
  def creator_fields(locale: nil)
11
- get("/creatorFields", params: build_locale_params(locale))
20
+ params = build_locale_params(locale)
21
+ make_get_request("/creatorFields", params: params)
12
22
  end
13
23
 
14
24
  private
@@ -3,20 +3,40 @@
3
3
  require "digest"
4
4
 
5
5
  module Zotero
6
- # File upload operations for library items
7
- module LibraryFileOperations
6
+ # File attachment operations for library items
7
+ module FileAttachments
8
+ # Create a new attachment item in the library.
9
+ #
10
+ # @param attachment_data [Hash] The attachment data including itemType, contentType, etc.
11
+ # @param version [Integer] Optional version for conditional requests
12
+ # @param write_token [String] Optional write token for batch operations
13
+ # @return [Hash] The API response with created attachment
8
14
  def create_attachment(attachment_data, version: nil, write_token: nil)
9
15
  create_single("items", attachment_data, version: version, write_token: write_token)
10
16
  end
11
17
 
18
+ # Get file information for an attachment item.
19
+ #
20
+ # @param item_key [String] The attachment item key
21
+ # @return [Hash] File information including filename, md5, mtime
12
22
  def get_file_info(item_key)
13
- @client.get("#{@base_path}/items/#{item_key}/file")
23
+ @client.make_get_request("#{@base_path}/items/#{item_key}/file")
14
24
  end
15
25
 
26
+ # Upload a file to an attachment item.
27
+ #
28
+ # @param item_key [String] The attachment item key
29
+ # @param file_path [String] Local path to the file to upload
30
+ # @return [Boolean] Success status
16
31
  def upload_file(item_key, file_path)
17
32
  perform_file_upload(item_key, file_path, existing_file: false)
18
33
  end
19
34
 
35
+ # Update the file content of an existing attachment.
36
+ #
37
+ # @param item_key [String] The attachment item key
38
+ # @param file_path [String] Local path to the new file
39
+ # @return [Boolean] Success status
20
40
  def update_file(item_key, file_path)
21
41
  perform_file_upload(item_key, file_path, existing_file: true)
22
42
  end
@@ -14,13 +14,14 @@ module Zotero
14
14
  end
15
15
 
16
16
  def external_post(url, multipart_data:)
17
- response = http_request(:post, url, body: multipart_data, options: { multipart: true, format: :plain })
17
+ response = http_request(:post, url, body: multipart_data, multipart: true, format: :plain)
18
18
 
19
- case response.code
19
+ code = response.code.to_i
20
+ case code
20
21
  when 200..299
21
22
  response.body
22
23
  else
23
- raise Error, "External upload failed: HTTP #{response.code} - #{response.message}"
24
+ raise Error, "External upload failed: HTTP #{code} - #{response.message}"
24
25
  end
25
26
  end
26
27
 
@@ -1,17 +1,34 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Zotero
4
+ # Fulltext search and content methods
4
5
  module Fulltext
6
+ # Get fulltext content that has been modified since a given version.
7
+ #
8
+ # @param since [Integer] Version number to get changes since
9
+ # @return [Hash] Object mapping item keys to version numbers
5
10
  def fulltext_since(since:)
6
- @client.get("#{@base_path}/fulltext", params: { since: since })
11
+ params = { since: since }
12
+ @client.make_get_request("#{@base_path}/fulltext", params: params)
7
13
  end
8
14
 
15
+ # Get the fulltext content for a specific item.
16
+ #
17
+ # @param item_key [String] The item key to get fulltext for
18
+ # @return [Hash] Fulltext content data including content, indexedChars, and totalChars
9
19
  def item_fulltext(item_key)
10
- @client.get("#{@base_path}/items/#{item_key}/fulltext")
20
+ @client.make_get_request("#{@base_path}/items/#{item_key}/fulltext")
11
21
  end
12
22
 
23
+ # Set the fulltext content for a specific item.
24
+ #
25
+ # @param item_key [String] The item key to set fulltext for
26
+ # @param content_data [Hash] Fulltext content data with content, indexedChars, totalChars
27
+ # @param version [Integer] Optional version for optimistic concurrency control
28
+ # @return [Boolean] Success status
13
29
  def set_item_fulltext(item_key, content_data, version: nil)
14
- @client.put("#{@base_path}/items/#{item_key}/fulltext", data: content_data, version: version)
30
+ @client.make_write_request(:put, "#{@base_path}/items/#{item_key}/fulltext", data: content_data,
31
+ options: { version: version })
15
32
  end
16
33
  end
17
34
  end
@@ -4,7 +4,8 @@ module Zotero
4
4
  # HTTP error handling methods
5
5
  module HTTPErrors
6
6
  def raise_error_for_status(response)
7
- case response.code
7
+ code = response.code.to_i
8
+ case code
8
9
  when 400..428 then raise_client_error(response)
9
10
  when 429 then raise_rate_limit_error(response)
10
11
  else raise_server_or_unknown_error(response)
@@ -12,10 +13,11 @@ module Zotero
12
13
  end
13
14
 
14
15
  def raise_client_error(response)
15
- case response.code
16
+ code = response.code.to_i
17
+ case code
16
18
  when 400, 413 then raise BadRequestError, "Bad request: #{response.body}"
17
19
  when 401, 403 then raise AuthenticationError, "Authentication failed - check your API key"
18
- when 404 then raise NotFoundError, "Resource not found: #{response.request.path}"
20
+ when 404 then raise NotFoundError, "Resource not found"
19
21
  when 409 then raise ConflictError, "Conflict: #{response.body}"
20
22
  when 412 then raise PreconditionFailedError, "Precondition failed: #{response.body}"
21
23
  when 428 then raise PreconditionRequiredError, "Precondition required: #{response.body}"
@@ -23,8 +25,9 @@ module Zotero
23
25
  end
24
26
 
25
27
  def raise_rate_limit_error(response)
26
- backoff = response.headers["backoff"]&.to_i
27
- retry_after = response.headers["retry-after"]&.to_i
28
+ headers = response.to_hash.transform_values(&:first)
29
+ backoff = headers["backoff"]&.to_i
30
+ retry_after = headers["retry-after"]&.to_i
28
31
  message = "Rate limited."
29
32
  message += " Backoff: #{backoff}s" if backoff
30
33
  message += " Retry after: #{retry_after}s" if retry_after
@@ -32,11 +35,12 @@ module Zotero
32
35
  end
33
36
 
34
37
  def raise_server_or_unknown_error(response)
35
- case response.code
38
+ code = response.code.to_i
39
+ case code
36
40
  when 500..599
37
- raise ServerError, "Server error: HTTP #{response.code} - #{response.message}"
41
+ raise ServerError, "Server error: HTTP #{code} - #{response.message}"
38
42
  else
39
- raise Error, "Unexpected response: HTTP #{response.code} - #{response.message}"
43
+ raise Error, "Unexpected response: HTTP #{code} - #{response.message}"
40
44
  end
41
45
  end
42
46
  end
@@ -3,22 +3,42 @@
3
3
  module Zotero
4
4
  # Item type discovery and template methods
5
5
  module ItemTypes
6
+ # Get all available item types.
7
+ #
8
+ # @param locale [String] Optional locale for localized type names (e.g. 'en-US', 'fr-FR')
9
+ # @return [Array<Hash>] Array of item type definitions
6
10
  def item_types(locale: nil)
7
- get("/itemTypes", params: build_locale_params(locale))
11
+ params = build_locale_params(locale)
12
+ make_get_request("/itemTypes", params: params)
8
13
  end
9
14
 
15
+ # Get all fields available for a specific item type.
16
+ #
17
+ # @param item_type [String] The item type name (e.g. 'book', 'journalArticle')
18
+ # @param locale [String] Optional locale for localized field names
19
+ # @return [Array<Hash>] Array of field definitions for the item type
10
20
  def item_type_fields(item_type, locale: nil)
11
21
  params = { itemType: item_type }
12
22
  params.merge!(build_locale_params(locale))
13
- get("/itemTypeFields", params: params)
23
+ make_get_request("/itemTypeFields", params: params)
14
24
  end
15
25
 
26
+ # Get all creator types available for a specific item type.
27
+ #
28
+ # @param item_type [String] The item type name (e.g. 'book', 'journalArticle')
29
+ # @return [Array<Hash>] Array of creator type definitions for the item type
16
30
  def item_type_creator_types(item_type)
17
- get("/itemTypeCreatorTypes", params: { itemType: item_type })
31
+ params = { itemType: item_type }
32
+ make_get_request("/itemTypeCreatorTypes", params: params)
18
33
  end
19
34
 
35
+ # Get a new item template for a specific item type.
36
+ #
37
+ # @param item_type [String] The item type name (e.g. 'book', 'journalArticle')
38
+ # @return [Hash] Template object with empty fields for the item type
20
39
  def new_item_template(item_type)
21
- get("/items/new", params: { itemType: item_type })
40
+ params = { itemType: item_type }
41
+ make_get_request("/items/new", params: params)
22
42
  end
23
43
 
24
44
  private
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "library_file_operations"
3
+ require_relative "file_attachments"
4
4
  require_relative "fulltext"
5
5
  require_relative "syncing"
6
6
 
@@ -15,8 +15,7 @@ module Zotero
15
15
  # collections = library.collections
16
16
  #
17
17
  class Library
18
- # TODO: rename this module, LibraryFileOperations sounds weird
19
- include LibraryFileOperations
18
+ include FileAttachments
20
19
  include Fulltext
21
20
  include Syncing
22
21
 
@@ -39,7 +38,7 @@ module Zotero
39
38
  # @param params [Hash] Query parameters for the request
40
39
  # @return [Array, Hash] Collections data from the API
41
40
  def collections(**params)
42
- @client.get("#{@base_path}/collections", params: params)
41
+ @client.make_get_request("#{@base_path}/collections", params: params)
43
42
  end
44
43
 
45
44
  # Get items in this library.
@@ -47,15 +46,23 @@ module Zotero
47
46
  # @param params [Hash] Query parameters for the request
48
47
  # @return [Array, Hash] Items data from the API
49
48
  def items(**params)
50
- @client.get("#{@base_path}/items", params: params)
49
+ @client.make_get_request("#{@base_path}/items", params: params)
51
50
  end
52
51
 
52
+ # Get saved searches in this library.
53
+ #
54
+ # @param params [Hash] Query parameters for the request
55
+ # @return [Array, Hash] Saved searches data from the API
53
56
  def searches(**params)
54
- @client.get("#{@base_path}/searches", params: params)
57
+ @client.make_get_request("#{@base_path}/searches", params: params)
55
58
  end
56
59
 
60
+ # Get tags in this library.
61
+ #
62
+ # @param params [Hash] Query parameters for the request
63
+ # @return [Array, Hash] Tags data from the API
57
64
  def tags(**params)
58
- @client.get("#{@base_path}/tags", params: params)
65
+ @client.make_get_request("#{@base_path}/tags", params: params)
59
66
  end
60
67
 
61
68
  # Create a new item in this library.
@@ -68,41 +75,95 @@ module Zotero
68
75
  create_single("items", item_data, version: version, write_token: write_token)
69
76
  end
70
77
 
78
+ # Create multiple items in this library.
79
+ #
80
+ # @param items_array [Array<Hash>] Array of item data objects
81
+ # @param version [Integer] Optional version for conditional requests
82
+ # @param write_token [String] Optional write token for batch operations
83
+ # @return [Hash] The API response with created items
71
84
  def create_items(items_array, version: nil, write_token: nil)
72
85
  create_multiple("items", items_array, version: version, write_token: write_token)
73
86
  end
74
87
 
88
+ # Update an existing item in this library.
89
+ #
90
+ # @param item_key [String] The item key to update
91
+ # @param item_data [Hash] The updated item data
92
+ # @param version [Integer] Version for optimistic concurrency control
93
+ # @return [Hash] The API response
75
94
  def update_item(item_key, item_data, version: nil)
76
- @client.patch("#{@base_path}/items/#{item_key}", data: item_data, version: version)
95
+ @client.make_write_request(:patch, "#{@base_path}/items/#{item_key}", data: item_data,
96
+ options: { version: version })
77
97
  end
78
98
 
99
+ # Delete an item from this library.
100
+ #
101
+ # @param item_key [String] The item key to delete
102
+ # @param version [Integer] Version for optimistic concurrency control
103
+ # @return [Boolean] Success status
79
104
  def delete_item(item_key, version: nil)
80
- @client.delete("#{@base_path}/items/#{item_key}", version: version)
105
+ @client.make_write_request(:delete, "#{@base_path}/items/#{item_key}", options: { version: version })
81
106
  end
82
107
 
108
+ # Delete multiple items from this library.
109
+ #
110
+ # @param item_keys [Array<String>] Array of item keys to delete
111
+ # @param version [Integer] Version for optimistic concurrency control
112
+ # @return [Boolean] Success status
83
113
  def delete_items(item_keys, version: nil)
84
- @client.delete("#{@base_path}/items", version: version, params: { itemKey: item_keys.join(",") })
114
+ @client.make_write_request(:delete, "#{@base_path}/items", options: { version: version },
115
+ params: { itemKey: item_keys.join(",") })
85
116
  end
86
117
 
118
+ # Create a new collection in this library.
119
+ #
120
+ # @param collection_data [Hash] The collection data
121
+ # @param version [Integer] Optional version for conditional requests
122
+ # @param write_token [String] Optional write token for batch operations
123
+ # @return [Hash] The API response
87
124
  def create_collection(collection_data, version: nil, write_token: nil)
88
125
  create_single("collections", collection_data, version: version, write_token: write_token)
89
126
  end
90
127
 
128
+ # Create multiple collections in this library.
129
+ #
130
+ # @param collections_array [Array<Hash>] Array of collection data objects
131
+ # @param version [Integer] Optional version for conditional requests
132
+ # @param write_token [String] Optional write token for batch operations
133
+ # @return [Hash] The API response with created collections
91
134
  def create_collections(collections_array, version: nil, write_token: nil)
92
135
  create_multiple("collections", collections_array, version: version, write_token: write_token)
93
136
  end
94
137
 
138
+ # Update an existing collection in this library.
139
+ #
140
+ # @param collection_key [String] The collection key to update
141
+ # @param collection_data [Hash] The updated collection data
142
+ # @param version [Integer] Version for optimistic concurrency control
143
+ # @return [Hash] The API response
95
144
  def update_collection(collection_key, collection_data, version: nil)
96
- @client.patch("#{@base_path}/collections/#{collection_key}", data: collection_data, version: version)
145
+ @client.make_write_request(:patch, "#{@base_path}/collections/#{collection_key}", data: collection_data,
146
+ options: { version: version })
97
147
  end
98
148
 
149
+ # Delete a collection from this library.
150
+ #
151
+ # @param collection_key [String] The collection key to delete
152
+ # @param version [Integer] Version for optimistic concurrency control
153
+ # @return [Boolean] Success status
99
154
  def delete_collection(collection_key, version: nil)
100
- @client.delete("#{@base_path}/collections/#{collection_key}", version: version)
155
+ @client.make_write_request(:delete, "#{@base_path}/collections/#{collection_key}", options: { version: version })
101
156
  end
102
157
 
158
+ # Delete multiple collections from this library.
159
+ #
160
+ # @param collection_keys [Array<String>] Array of collection keys to delete
161
+ # @param version [Integer] Version for optimistic concurrency control
162
+ # @return [Boolean] Success status
103
163
  def delete_collections(collection_keys, version: nil)
104
- @client.delete("#{@base_path}/collections", version: version,
105
- params: { collectionKey: collection_keys.join(",") })
164
+ @client.make_write_request(:delete, "#{@base_path}/collections",
165
+ options: { version: version },
166
+ params: { collectionKey: collection_keys.join(",") })
106
167
  end
107
168
 
108
169
  private
@@ -110,11 +171,15 @@ module Zotero
110
171
  attr_reader :client, :type, :id, :base_path
111
172
 
112
173
  def create_single(resource, data, version: nil, write_token: nil)
113
- @client.post("#{@base_path}/#{resource}", data: [data], version: version, write_token: write_token)
174
+ @client.make_write_request(:post, "#{@base_path}/#{resource}",
175
+ data: [data],
176
+ options: { version: version, write_token: write_token })
114
177
  end
115
178
 
116
179
  def create_multiple(resource, data_array, version: nil, write_token: nil)
117
- @client.post("#{@base_path}/#{resource}", data: data_array, version: version, write_token: write_token)
180
+ @client.make_write_request(:post, "#{@base_path}/#{resource}",
181
+ data: data_array,
182
+ options: { version: version, write_token: write_token })
118
183
  end
119
184
 
120
185
  def validate_type(type)
@@ -1,19 +1,40 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Zotero
4
+ # Syncing and API key verification methods
4
5
  module Syncing
6
+ # Verify that the current API key is valid.
7
+ #
8
+ # @return [Hash] API key information including userID and username
5
9
  def verify_api_key
6
- @client ? @client.get("/keys/current") : get("/keys/current")
10
+ if @client
11
+ @client.make_get_request("/keys/current")
12
+ else
13
+ make_get_request("/keys/current")
14
+ end
7
15
  end
8
16
 
17
+ # Get groups for a specific user.
18
+ #
19
+ # @param user_id [Integer, String] The user ID to get groups for
20
+ # @param format [String] Response format ('versions' or 'json')
21
+ # @return [Hash, Array] Groups data in the requested format
9
22
  def user_groups(user_id, format: "versions")
10
- client = @client || self
11
- client.get("/users/#{user_id}/groups", params: { format: format })
23
+ params = { format: format }
24
+ if @client
25
+ @client.make_get_request("/users/#{user_id}/groups", params: params)
26
+ else
27
+ make_get_request("/users/#{user_id}/groups", params: params)
28
+ end
12
29
  end
13
30
 
31
+ # Get items that have been deleted from this library.
32
+ #
33
+ # @param since [Integer] Optional version to get deletions since
34
+ # @return [Hash] Object with deleted collections and items arrays
14
35
  def deleted_items(since: nil)
15
36
  params = since ? { since: since } : {}
16
- @client.get("#{@base_path}/deleted", params: params)
37
+ @client.make_get_request("#{@base_path}/deleted", params: params)
17
38
  end
18
39
  end
19
40
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Zotero
4
- VERSION = "0.1.4"
4
+ VERSION = "0.1.5"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: zotero-rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Waller
@@ -32,6 +32,7 @@ files:
32
32
  - lib/zotero/client.rb
33
33
  - lib/zotero/error.rb
34
34
  - lib/zotero/fields.rb
35
+ - lib/zotero/file_attachments.rb
35
36
  - lib/zotero/file_upload.rb
36
37
  - lib/zotero/fulltext.rb
37
38
  - lib/zotero/http_config.rb
@@ -39,7 +40,6 @@ files:
39
40
  - lib/zotero/http_errors.rb
40
41
  - lib/zotero/item_types.rb
41
42
  - lib/zotero/library.rb
42
- - lib/zotero/library_file_operations.rb
43
43
  - lib/zotero/network_errors.rb
44
44
  - lib/zotero/syncing.rb
45
45
  - lib/zotero/version.rb
@@ -70,7 +70,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
70
70
  - !ruby/object:Gem::Version
71
71
  version: '0'
72
72
  requirements: []
73
- rubygems_version: 3.6.9
73
+ rubygems_version: 3.7.1
74
74
  specification_version: 4
75
75
  summary: A comprehensive Ruby client for the Zotero Web API v3
76
76
  test_files: []