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 +4 -4
- data/CHANGELOG.md +21 -0
- data/CLAUDE.md +5 -0
- data/Gemfile.lock +3 -2
- data/README.md +102 -1
- data/lib/patch_retention/calendar_items.rb +86 -0
- data/lib/patch_retention/contacts/find.rb +4 -1
- data/lib/patch_retention/contacts.rb +2 -2
- data/lib/patch_retention/memberships.rb +3 -3
- data/lib/patch_retention/version.rb +1 -1
- data/patch_retention.gemspec +1 -0
- metadata +17 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9642c7116bc29f3cc1e9368c861ac11d13b2357ab778b4f9395fa2bb14c3edb3
|
4
|
+
data.tar.gz: 7dc040049c15f9b32eb83d06a986daa0da1c4c881d1dfd2553dc764d9fbba494
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
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.
|
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
|
-
|
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
|
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
|
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,
|
7
|
-
|
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
|
data/patch_retention.gemspec
CHANGED
@@ -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.
|
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-
|
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
|