patch_retention 0.2.1 → 0.3.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: 9a2a099b064c0ef56c80743759676b3dc37d4de5ee636e0f2cf271ec5e7726c4
4
- data.tar.gz: fa4eacdf94682e3156faa8a0580a0afcb803604f13187954bc87de0298726077
3
+ metadata.gz: 9642c7116bc29f3cc1e9368c861ac11d13b2357ab778b4f9395fa2bb14c3edb3
4
+ data.tar.gz: 7dc040049c15f9b32eb83d06a986daa0da1c4c881d1dfd2553dc764d9fbba494
5
5
  SHA512:
6
- metadata.gz: 1eeac799c480635c1120170d0d2bf5c4d44acf34b744e86e652fc4c0dd664d73b6c7523278b338e12aaed419647b6d8c14873269225347cf8b51cd7428020659
7
- data.tar.gz: c948d3b077bd7e84c9753a60f1e962d736c5332a2122817caefef16c6be56d499d5037e5de4571f48378c07ee3e4ba4bf65f3a3d47993a940b0718dfe9b43b7d
6
+ metadata.gz: cd6b62c587f1ad1ed68b579bfcd70403a916de81593248d4850664b4495dc2ff316d279b338952dfb332f038f1c3b63240907345544298becbf14fe749345806
7
+ data.tar.gz: 82fe4b41064b55979b281ad05e48459ae3230e04d91f32065ecab1bf030e337bc56b791a6786ba09fb6bee7b7ec75a808485b8f986713cb888543248278e43f0
data/CHANGELOG.md CHANGED
@@ -1,5 +1,26 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.3.1] - 2025-09-04
4
+
5
+ ### Added
6
+ - Added `tags` parameter support to `CalendarItems.create` method for categorizing calendar items
7
+ - Added `email` parameter to `Contacts.all` method for filtering contacts by email address
8
+
9
+ ### Changed
10
+ - **BREAKING**: Updated `Contacts.all` method to use keyword arguments (limit:, offset:, email:, config:) instead of positional arguments
11
+
12
+ ## [0.3.0] - 2025-07-27
13
+
14
+ ### Added
15
+ - Calendar Items API support with full CRUD operations:
16
+ - `create` - Create calendar events for contacts
17
+ - `update` - Update existing calendar items
18
+ - `find` - Retrieve a specific calendar item
19
+ - `delete` - Remove calendar items
20
+ - `all` - List calendar items with filtering and pagination support
21
+ - Support for calendar item metadata including description, location, external_id, external_data, and tags
22
+ - Comprehensive test coverage for Calendar Items functionality
23
+
3
24
  ## [0.2.0] - 2025-05-26
4
25
 
5
26
  ### Added
data/CLAUDE.md CHANGED
@@ -171,6 +171,11 @@ The gem uses dotenv for credential management:
171
171
  2. **VCR Integration**: All API tests are recorded/replayed using VCR for consistent testing
172
172
  3. **Flexible Assertions**: Don't rely on exact field presence, use conditional checks
173
173
  4. **Custom Config Testing**: Always test that methods accept custom configuration
174
+ 5. **Time/Date Testing with VCR**:
175
+ - Avoid strict time comparisons with `Time.now` as VCR cassettes may be recorded at different times
176
+ - Instead of `expect(Time.parse(result["date"])).to(be_within(24 * 60 * 60).of(Time.now))`
177
+ - Use format validation: `expect(result["date"]).to match(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/)`
178
+ - Or simply verify presence: `expect(result["date"]).not_to be_nil`
174
179
 
175
180
  ### Code Style
176
181
  - Using Shopify's Rubocop style guide (`rubocop-shopify` gem)
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- patch_retention (0.2.1)
4
+ patch_retention (0.3.1)
5
5
  faraday (~> 1.10)
6
6
  zeitwerk (~> 2.6)
7
7
 
@@ -94,7 +94,7 @@ GEM
94
94
  rubocop (~> 1.51)
95
95
  ruby-progressbar (1.13.0)
96
96
  ruby2_keywords (0.0.5)
97
- thor (1.3.2)
97
+ thor (1.4.0)
98
98
  unicode-display_width (2.5.0)
99
99
  vcr (6.3.1)
100
100
  base64
@@ -119,6 +119,7 @@ DEPENDENCIES
119
119
  rspec (~> 3.0)
120
120
  rubocop (~> 1.21)
121
121
  rubocop-shopify (~> 2.15)
122
+ thor (>= 1.4.0)
122
123
  vcr (~> 6.0)
123
124
  webmock (~> 3.0)
124
125
 
data/README.md CHANGED
@@ -118,7 +118,21 @@ Then, you can use the gem's methods to interact with the API.
118
118
  To retrieve all contacts:
119
119
 
120
120
  ```ruby
121
+ # Basic usage
121
122
  contacts = PatchRetention::Contacts.all
123
+
124
+ # With pagination (using keyword arguments)
125
+ contacts = PatchRetention::Contacts.all(limit: 10, offset: 0)
126
+
127
+ # With email filter
128
+ contacts = PatchRetention::Contacts.all(email: 'john.doe@example.com')
129
+
130
+ # With pagination and email filter
131
+ contacts = PatchRetention::Contacts.all(
132
+ limit: 50,
133
+ offset: 0,
134
+ email: 'john@example.com'
135
+ )
122
136
  ```
123
137
 
124
138
  To retrieve a single contact:
@@ -265,7 +279,10 @@ membership = PatchRetention::Memberships.create(
265
279
  start_at: Time.now.xmlschema, # Optional, ISO8601 timestamp
266
280
  end_at: (Time.now + 30*24*60*60).xmlschema, # Optional, ISO8601 timestamp (e.g., 30 days from now)
267
281
  external_id: "MEM123", # Optional
268
- external_data: { "notes": "Trial membership" }, # Optional, hash
282
+ data: {
283
+ "is_trial" => true,
284
+ "trial_ends_at" => (Time.now + 15*24*60*60).xmlschema
285
+ }, # Optional, hash for custom data
269
286
  tags: ["trial", "api_created"] # Optional, array of strings
270
287
  )
271
288
  # => {"id"=>"mem_xxxxxxxxxxxxxx", "contact_id"=>"ct_xxxxxxxxxxxxxx", ...}
@@ -287,6 +304,90 @@ To find a membership:
287
304
  membership = PatchRetention::Memberships.find(membership_id: "mem_xxxxxxxxxxxxxx")
288
305
  ```
289
306
 
307
+ **Calendar Items**
308
+
309
+ To create a calendar item:
310
+
311
+ ```ruby
312
+ calendar_item = PatchRetention::CalendarItems.create(
313
+ contact_id: "ct_xxxxxxxxxxxxxx", # Required, ID of the contact
314
+ title: "Tennis Court Reservation", # Required, title of the event
315
+ start_at: "2025-02-01T10:00:00Z", # Required, ISO8601 timestamp - when the event starts
316
+ end_at: "2025-02-01T11:00:00Z", # Required, ISO8601 timestamp - when the event ends
317
+ run_start_at: "2025-02-01T09:30:00Z", # Optional, ISO8601 - when to run the event for this item (defaults to start_at if not set)
318
+ run_end_at: "2025-02-01T11:30:00Z", # Optional, ISO8601 - when to run the event for this item (defaults to end_at if not set)
319
+ data: { # Optional, additional metadata that will be included in the calendar item
320
+ external_id: "res_123456", # Your system's ID
321
+ facility_id: "fac_789",
322
+ court_name: "Court 1",
323
+ reservation_type: "match"
324
+ },
325
+ tags: ["tennis", "premium", "court1"], # Optional, array of tags to categorize the calendar item
326
+ time_occurred: "2025-01-28T09:00:00Z", # Optional, ISO8601 - past or current time for the event (will default to now)
327
+ skip_triggers: false # Optional, if set to false or empty, any automation tied to the calendar item will not run
328
+ )
329
+ # => {"id"=>"cal_xxxxxxxxxxxxxx", "contact_id"=>"ct_xxxxxxxxxxxxxx", ...}
330
+ ```
331
+
332
+ To update a calendar item:
333
+
334
+ ```ruby
335
+ calendar_item = PatchRetention::CalendarItems.update(
336
+ calendar_item_id: "cal_xxxxxxxxxxxxxx",
337
+ title: "Updated Tennis Match",
338
+ end_at: "2025-02-01T12:00:00Z", # Extend by 1 hour
339
+ data: {
340
+ description: "Match extended due to tie-break"
341
+ },
342
+ tags: ["Tennis", "Extended", "Tie-break"] # Tags as separate parameter
343
+ )
344
+ ```
345
+
346
+ To find a calendar item:
347
+
348
+ ```ruby
349
+ calendar_item = PatchRetention::CalendarItems.find(calendar_item_id: "cal_xxxxxxxxxxxxxx")
350
+ ```
351
+
352
+ To delete a calendar item:
353
+
354
+ ```ruby
355
+ result = PatchRetention::CalendarItems.delete(calendar_item_id: "cal_xxxxxxxxxxxxxx")
356
+ # => { success: true }
357
+ ```
358
+
359
+ To list calendar items:
360
+
361
+ ```ruby
362
+ # List all calendar items
363
+ calendar_items = PatchRetention::CalendarItems.all()
364
+
365
+ # List calendar items for a specific contact
366
+ calendar_items = PatchRetention::CalendarItems.all(contact_id: "ct_xxxxxxxxxxxxxx")
367
+
368
+ # List calendar items within ID range (MongoDB IDs)
369
+ calendar_items = PatchRetention::CalendarItems.all(
370
+ min_id: "0123456789abcdefghijklmn", # Get all items on or after this ID
371
+ max_id: "0123456789abcdefghijklmn" # Get all items on or before this ID
372
+ )
373
+
374
+ # List specific calendar items by IDs (comma-separated)
375
+ calendar_items = PatchRetention::CalendarItems.all(
376
+ id: "0123456789abcdefghijklmn,123456789abcdefghijklmn"
377
+ )
378
+
379
+ # Filter by date range (predefined options)
380
+ calendar_items = PatchRetention::CalendarItems.all(
381
+ date_range: "Today" # Options: "Today", "Yesterday", "Last 7 Days", "This Week", "Last Week", "Last 30 Days"
382
+ )
383
+
384
+ # With pagination
385
+ calendar_items = PatchRetention::CalendarItems.all(
386
+ limit: 50, # Default is 50, max is 100
387
+ offset: 25 # Skip first 25 results
388
+ )
389
+ ```
390
+
290
391
  ## Development
291
392
 
292
393
  After checking out the repo, run `bin/setup` to install dependencies.
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PatchRetention
4
+ class CalendarItems
5
+ class << self
6
+ def create(contact_id:, title:, start_at:, end_at:, run_start_at: nil, run_end_at: nil,
7
+ data: nil, time_occurred: nil, skip_triggers: false, tags: nil, config: nil)
8
+ payload = {
9
+ contact_id: contact_id,
10
+ title: title,
11
+ start_at: start_at,
12
+ end_at: end_at,
13
+ }
14
+ payload[:run_start_at] = run_start_at if run_start_at
15
+ payload[:run_end_at] = run_end_at if run_end_at
16
+ payload[:data] = data if data
17
+ payload[:time_occurred] = time_occurred if time_occurred
18
+ payload[:skip_triggers] = skip_triggers unless skip_triggers.nil?
19
+ payload[:tags] = tags if tags
20
+
21
+ response = PatchRetention.connection(config).post("/v2/calendar_items") do |req|
22
+ req.body = payload.to_json
23
+ req.headers["Content-Type"] = "application/json"
24
+ end
25
+
26
+ JSON.parse(response.body)
27
+ rescue Faraday::Error => e
28
+ raise Error, "Failed to create calendar item: #{e.message}"
29
+ end
30
+
31
+ def update(calendar_item_id:, title: nil, start_at: nil, end_at: nil, run_start_at: nil,
32
+ run_end_at: nil, data: nil, tags: nil, config: nil)
33
+ payload = {}
34
+ payload[:title] = title if title
35
+ payload[:start_at] = start_at if start_at
36
+ payload[:end_at] = end_at if end_at
37
+ payload[:run_start_at] = run_start_at if run_start_at
38
+ payload[:run_end_at] = run_end_at if run_end_at
39
+ payload[:data] = data if data
40
+ payload[:tags] = tags if tags
41
+
42
+ response = PatchRetention.connection(config).patch("/v2/calendar_items/#{calendar_item_id}") do |req|
43
+ req.body = payload.to_json
44
+ req.headers["Content-Type"] = "application/json"
45
+ end
46
+
47
+ JSON.parse(response.body)
48
+ rescue Faraday::Error => e
49
+ raise Error, "Failed to update calendar item: #{e.message}"
50
+ end
51
+
52
+ def find(calendar_item_id:, config: nil)
53
+ response = PatchRetention.connection(config).get("/v2/calendar_items/#{calendar_item_id}")
54
+
55
+ JSON.parse(response.body)
56
+ rescue Faraday::Error => e
57
+ raise Error, "Failed to find calendar item: #{e.message}"
58
+ end
59
+
60
+ def delete(calendar_item_id:, config: nil)
61
+ response = PatchRetention.connection(config).delete("/v2/calendar_items/#{calendar_item_id}")
62
+
63
+ { success: response.status == 204 }
64
+ rescue Faraday::Error => e
65
+ raise Error, "Failed to delete calendar item: #{e.message}"
66
+ end
67
+
68
+ def all(contact_id: nil, min_id: nil, max_id: nil, id: nil, date_range: nil, limit: nil, offset: nil, config: nil)
69
+ params = {}
70
+ params[:contact_id] = contact_id if contact_id
71
+ params[:min_id] = min_id if min_id
72
+ params[:max_id] = max_id if max_id
73
+ params[:id] = id if id
74
+ params[:date_range] = date_range if date_range
75
+ params[:limit] = limit if limit
76
+ params[:offset] = offset if offset
77
+
78
+ response = PatchRetention.connection(config).get("/v2/calendar_items", params)
79
+
80
+ JSON.parse(response.body)
81
+ rescue Faraday::Error => e
82
+ raise Error, "Failed to retrieve calendar items: #{e.message}"
83
+ end
84
+ end
85
+ end
86
+ end
@@ -29,13 +29,16 @@ module PatchRetention::Contacts::Find
29
29
  #
30
30
  # @param limit [Integer] The number of contacts to retrieve per page.
31
31
  # @param offset [Integer] The number of contacts to skip before starting to collect the result set.
32
+ # @param email [String] Optional email to filter contacts by.
33
+ # @param config [Configuration] Optional configuration object.
32
34
  # @return [Object] The response from the PatchRetention API.
33
35
  # @raise [PatchRetention::Error] If the API returns a status other than 200.
34
- def all(limit, offset, config = nil)
36
+ def all(limit:, offset:, email: nil, config: nil)
35
37
  raise_error_if_present do
36
38
  PatchRetention.connection(config).get(PatchRetention::Contacts::API_PATH) do |req|
37
39
  req.params["limit"] = limit
38
40
  req.params["offset"] = offset
41
+ req.params["email"] = email if email
39
42
  end
40
43
  end
41
44
  end
@@ -8,8 +8,8 @@ class PatchRetention::Contacts
8
8
  Find.by_id(id, config)
9
9
  end
10
10
 
11
- def all(limit = 30, offset = 0, config = nil)
12
- Find.all(limit, offset, config)
11
+ def all(limit: 30, offset: 0, email: nil, config: nil)
12
+ Find.all(limit: limit, offset: offset, email: email, config: config)
13
13
  end
14
14
 
15
15
  def find_or_create_by(contact_params:, query_params: {}, config: nil)
@@ -3,8 +3,8 @@
3
3
  module PatchRetention
4
4
  class Memberships
5
5
  class << self
6
- def create(contact_id:, product_id:, start_at: nil, end_at: nil, external_id: nil, external_data: nil,
7
- tags: nil, config: nil)
6
+ def create(contact_id:, product_id:, start_at: nil, end_at: nil, external_id: nil, tags: nil, data: nil,
7
+ config: nil)
8
8
  payload = {
9
9
  contact_id: contact_id,
10
10
  product_id: product_id,
@@ -12,8 +12,8 @@ module PatchRetention
12
12
  payload[:start_at] = start_at if start_at
13
13
  payload[:end_at] = end_at if end_at
14
14
  payload[:external_id] = external_id if external_id
15
- payload[:external_data] = external_data if external_data
16
15
  payload[:tags] = tags if tags
16
+ payload[:data] = data if data
17
17
 
18
18
  response = PatchRetention.connection(config).post("/v2/memberships") do |req|
19
19
  req.body = payload.to_json
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PatchRetention
4
- VERSION = "0.2.1"
4
+ VERSION = "0.3.1"
5
5
  end
@@ -45,6 +45,7 @@ Gem::Specification.new do |spec|
45
45
  spec.add_development_dependency("rspec", "~> 3.0")
46
46
  spec.add_development_dependency("rubocop", "~> 1.21")
47
47
  spec.add_development_dependency("rubocop-shopify", "~> 2.15")
48
+ spec.add_development_dependency("thor", ">= 1.4.0")
48
49
  spec.add_development_dependency("vcr", "~> 6.0")
49
50
  spec.add_development_dependency("webmock", "~> 3.0")
50
51
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: patch_retention
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Playbypoint
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2025-07-21 00:00:00.000000000 Z
12
+ date: 2025-09-04 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: faraday
@@ -165,6 +165,20 @@ dependencies:
165
165
  - - "~>"
166
166
  - !ruby/object:Gem::Version
167
167
  version: '2.15'
168
+ - !ruby/object:Gem::Dependency
169
+ name: thor
170
+ requirement: !ruby/object:Gem::Requirement
171
+ requirements:
172
+ - - ">="
173
+ - !ruby/object:Gem::Version
174
+ version: 1.4.0
175
+ type: :development
176
+ prerelease: false
177
+ version_requirements: !ruby/object:Gem::Requirement
178
+ requirements:
179
+ - - ">="
180
+ - !ruby/object:Gem::Version
181
+ version: 1.4.0
168
182
  - !ruby/object:Gem::Dependency
169
183
  name: vcr
170
184
  requirement: !ruby/object:Gem::Requirement
@@ -237,6 +251,7 @@ files:
237
251
  - docs/references/troubleshooting.md
238
252
  - docs/wip/session-2025-05-26.md
239
253
  - lib/patch_retention.rb
254
+ - lib/patch_retention/calendar_items.rb
240
255
  - lib/patch_retention/configuration.rb
241
256
  - lib/patch_retention/contacts.rb
242
257
  - lib/patch_retention/contacts/delete.rb