calendlyr 0.7.5 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +9 -5
  3. data/.gitignore +3 -1
  4. data/CHANGELOG.md +98 -0
  5. data/README.md +209 -73
  6. data/calendlyr.gemspec +8 -6
  7. data/docs/resources/activity_log/list_activity_log_entries.md +2 -1
  8. data/docs/resources/availabilities/user_availability_schedule.md +3 -2
  9. data/docs/resources/availabilities/user_busy_time.md +3 -2
  10. data/docs/resources/data_compliance.md +1 -1
  11. data/docs/resources/event_types/availability_schedule.md +28 -0
  12. data/docs/resources/event_types/available_time.md +15 -11
  13. data/docs/resources/event_types/event_type.md +21 -9
  14. data/docs/resources/event_types/membership.md +15 -16
  15. data/docs/resources/events/cancellation.md +1 -1
  16. data/docs/resources/events/event.md +6 -5
  17. data/docs/resources/events/invitee.md +18 -0
  18. data/docs/resources/events/invitee_no_show.md +2 -1
  19. data/docs/resources/groups/group.md +2 -1
  20. data/docs/resources/locations/location.md +16 -0
  21. data/docs/resources/organizations/membership.md +3 -2
  22. data/docs/resources/organizations/organization.md +13 -9
  23. data/docs/resources/outgoing_communications/outgoing_communication.md +28 -0
  24. data/docs/resources/routing_forms/routing_form.md +2 -1
  25. data/docs/resources/routing_forms/submission.md +2 -1
  26. data/docs/resources/scheduling_links/scheduling_link.md +26 -0
  27. data/docs/resources/share.md +4 -10
  28. data/docs/resources/webhooks/invitee_payload.md +10 -16
  29. data/docs/resources/webhooks/payload.md +32 -3
  30. data/docs/resources/webhooks/sample.md +23 -0
  31. data/docs/resources/webhooks/subscription.md +10 -6
  32. data/lib/calendlyr/client.rb +7 -2
  33. data/lib/calendlyr/collection.rb +42 -9
  34. data/lib/calendlyr/configuration.rb +24 -0
  35. data/lib/calendlyr/error.rb +65 -27
  36. data/lib/calendlyr/object.rb +85 -13
  37. data/lib/calendlyr/objects/event_type.rb +0 -4
  38. data/lib/calendlyr/objects/event_types/availability_schedule.rb +8 -0
  39. data/lib/calendlyr/objects/event_types/available_time.rb +5 -1
  40. data/lib/calendlyr/objects/event_types/membership.rb +4 -7
  41. data/lib/calendlyr/objects/events/invitee.rb +1 -1
  42. data/lib/calendlyr/objects/location.rb +4 -0
  43. data/lib/calendlyr/objects/organization.rb +0 -4
  44. data/lib/calendlyr/objects/outgoing_communication.rb +6 -0
  45. data/lib/calendlyr/objects/scheduling_link.rb +2 -5
  46. data/lib/calendlyr/objects/share.rb +0 -5
  47. data/lib/calendlyr/objects/webhooks/payload.rb +30 -0
  48. data/lib/calendlyr/resource.rb +84 -12
  49. data/lib/calendlyr/resources/availability.rb +14 -2
  50. data/lib/calendlyr/resources/event_types.rb +45 -5
  51. data/lib/calendlyr/resources/events.rb +20 -2
  52. data/lib/calendlyr/resources/groups.rb +14 -2
  53. data/lib/calendlyr/resources/locations.rb +13 -0
  54. data/lib/calendlyr/resources/organizations.rb +25 -3
  55. data/lib/calendlyr/resources/outgoing_communications.rb +11 -3
  56. data/lib/calendlyr/resources/routing_forms.rb +14 -2
  57. data/lib/calendlyr/resources/scheduling_links.rb +5 -2
  58. data/lib/calendlyr/resources/shares.rb +1 -0
  59. data/lib/calendlyr/resources/webhooks.rb +11 -3
  60. data/lib/calendlyr/version.rb +1 -1
  61. data/lib/calendlyr/webhook.rb +105 -0
  62. data/lib/calendlyr.rb +50 -0
  63. data/logos/calendlyr.png +0 -0
  64. data/logos/calendlyr_bg_white.png +0 -0
  65. data/test/calendlyr/client_test.rb +29 -0
  66. data/test/calendlyr/collection_test.rb +168 -0
  67. data/test/calendlyr/configuration_test.rb +157 -0
  68. data/test/calendlyr/object_test.rb +82 -1
  69. data/test/calendlyr/objects/event_type_test.rb +0 -15
  70. data/test/calendlyr/objects/event_types/availability_schedule_test.rb +20 -0
  71. data/test/calendlyr/objects/events/cancellation_test.rb +1 -1
  72. data/test/calendlyr/objects/events/guest_test.rb +1 -1
  73. data/test/calendlyr/objects/events/invitee_no_show_test.rb +1 -1
  74. data/test/calendlyr/objects/events/invitee_test.rb +10 -3
  75. data/test/calendlyr/objects/location_test.rb +22 -0
  76. data/test/calendlyr/objects/organization_test.rb +0 -8
  77. data/test/calendlyr/objects/organizations/invitation_test.rb +1 -1
  78. data/test/calendlyr/objects/share_test.rb +3 -9
  79. data/test/calendlyr/objects/webhooks/payload_test.rb +15 -0
  80. data/test/calendlyr/resource_test.rb +456 -2
  81. data/test/calendlyr/resources/availabilities/user_busy_times_test.rb +26 -0
  82. data/test/calendlyr/resources/availabilities/user_schedules_test.rb +25 -0
  83. data/test/calendlyr/resources/data_compliance_test.rb +1 -4
  84. data/test/calendlyr/resources/event_types_test.rb +132 -0
  85. data/test/calendlyr/resources/events_test.rb +87 -0
  86. data/test/calendlyr/resources/groups_test.rb +54 -0
  87. data/test/calendlyr/resources/locations_test.rb +30 -0
  88. data/test/calendlyr/resources/organizations_test.rb +96 -2
  89. data/test/calendlyr/resources/outgoing_communications_test.rb +34 -8
  90. data/test/calendlyr/resources/routing_forms_test.rb +57 -0
  91. data/test/calendlyr/resources/scheduling_links_test.rb +31 -6
  92. data/test/calendlyr/resources/shares_test.rb +15 -0
  93. data/test/calendlyr/resources/webhooks_test.rb +63 -5
  94. data/test/calendlyr/webhook_test.rb +292 -0
  95. data/test/fixtures/activity_log/list_page2.json +30 -0
  96. data/test/fixtures/event_invitees/list_page2.json +35 -0
  97. data/test/fixtures/event_invitees/retrieve.json +11 -1
  98. data/test/fixtures/event_type_availability_schedules/list.json +17 -0
  99. data/test/fixtures/event_type_availability_schedules/update.json +3 -0
  100. data/test/fixtures/event_type_available_times/list.json +0 -12
  101. data/test/fixtures/event_type_memberships/list.json +43 -0
  102. data/test/fixtures/event_type_memberships/list_page2.json +33 -0
  103. data/test/fixtures/event_types/create.json +30 -0
  104. data/test/fixtures/event_types/list_page2.json +37 -0
  105. data/test/fixtures/event_types/update.json +30 -0
  106. data/test/fixtures/events/create_invitee.json +37 -0
  107. data/test/fixtures/events/list_page2.json +29 -0
  108. data/test/fixtures/events/retrieve.json +12 -2
  109. data/test/fixtures/group_relationships/list_page2.json +35 -0
  110. data/test/fixtures/groups/list_page2.json +16 -0
  111. data/test/fixtures/locations/list.json +16 -0
  112. data/test/fixtures/locations/list_page2.json +16 -0
  113. data/test/fixtures/objects/event.json +10 -2
  114. data/test/fixtures/objects/event_types/availability_schedule.json +6 -0
  115. data/test/fixtures/objects/location.json +5 -0
  116. data/test/fixtures/organizations/list_invitations_page2.json +18 -0
  117. data/test/fixtures/organizations/list_memberships_page2.json +26 -0
  118. data/test/fixtures/organizations/retrieve.json +11 -0
  119. data/test/fixtures/outgoing_communications/list.json +4 -6
  120. data/test/fixtures/outgoing_communications/list_page2.json +21 -0
  121. data/test/fixtures/routing_forms/list_page2.json +17 -0
  122. data/test/fixtures/routing_forms/list_routing_form_submission_page2.json +29 -0
  123. data/test/fixtures/user_availability_schedules/list_page1.json +16 -0
  124. data/test/fixtures/user_availability_schedules/list_page2.json +16 -0
  125. data/test/fixtures/user_busy_times/list_page1.json +18 -0
  126. data/test/fixtures/user_busy_times/list_page2.json +13 -0
  127. data/test/fixtures/webhooks/list_page2.json +23 -0
  128. data/test/fixtures/webhooks/sample.json +55 -94
  129. data/test/test_helper.rb +19 -7
  130. metadata +70 -27
  131. data/docs/resources/scheduling_link.md +0 -26
  132. data/test/calendlyr/objects/event_types/available_time_test.rb +0 -20
  133. data/test/calendlyr/objects/event_types/membership_test.rb +0 -32
  134. data/test/calendlyr/objects/scheduling_link_test.rb +0 -17
  135. data/test/calendlyr/resources/event_types/membership_test.rb +0 -22
  136. data/test/fixtures/objects/event_types/available_time.json +0 -6
  137. data/test/fixtures/objects/event_types/membership.json +0 -65
  138. data/test/fixtures/objects/scheduling_links/event_type.json +0 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d69cdfb2373e6ecbed4936f15670331a83b20816bd77e9aec6c166e43d47cde3
4
- data.tar.gz: ebe3df79b6d23e1e6539d63c0bf447396aa80037304f765b04414cf4e862a745
3
+ metadata.gz: ca7216a6bc050ebad873636af0bb988c8841d7828fe6cebc0a0497947c0a9dc7
4
+ data.tar.gz: e36f58a64ad9277fdd38bb44fe69aeb4e7d84fe10fb6b9274a474bb609406340
5
5
  SHA512:
6
- metadata.gz: a3c733ada27c62a49fd5f2644ae2d0e6861c4c5e50b287b0e4173d7873a5e7f2cb83c37c3cde5509551bdea6c13b2d1c6469922c5a632076423c6f5aa8d26199
7
- data.tar.gz: b6554a78bffea8609e5871f1c16e617997bcba34e2c348b285e1b5d27140a22009d91674dece47e37513fe6e7155c035654a49abd7713e211c0a64881e29b44c
6
+ metadata.gz: 36c382f6725f7fdc22acfd11bee564dbcd5c448df265f49d07c450e074ab23c3a1f165970e4f5f925feebbcfbd7f3dae827ecc144a5ca0fb0c0524f9dcaa2d3c
7
+ data.tar.gz: 2e60e92afb872bb9590cda26e3208ed1bed7f069ea644a55738c5c6284447241c0c90358ff2a19d1e3c1149a790db2e69648c8ed945cd9ed6e4a9ed36526d575
@@ -11,7 +11,7 @@ jobs:
11
11
  runs-on: ubuntu-latest
12
12
  strategy:
13
13
  matrix:
14
- ruby: ["2.7", "3.0", "3.2", "3.3"]
14
+ ruby: ["3.2", "3.3", "3.4", "4.0"]
15
15
 
16
16
  steps:
17
17
  - uses: actions/checkout@v4
@@ -27,7 +27,11 @@ jobs:
27
27
  run: bundle exec standardrb
28
28
 
29
29
  - name: Run tests
30
- run: |
31
- bundle exec rake test
32
- env:
33
- CODECOV_TOKEN: ${{secrets.CODECOV_TOKEN}}
30
+ run: bundle exec rake test
31
+
32
+ - name: Upload coverage
33
+ if: matrix.ruby == '3.4'
34
+ uses: codecov/codecov-action@v4
35
+ with:
36
+ token: ${{ secrets.CODECOV_TOKEN }}
37
+ files: coverage/.resultset.json
data/.gitignore CHANGED
@@ -22,4 +22,6 @@ tmp
22
22
  mkmf.log
23
23
  vendor
24
24
  .idea/
25
- bin/my_console
25
+ bin/my_console
26
+ .atl/*
27
+ .DS_Store
data/CHANGELOG.md CHANGED
@@ -2,6 +2,104 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [1.0.0]
6
+
7
+ ### Added
8
+ * Auto-pagination via `Enumerator::Lazy` — `collection.auto_paginate` returns a lazy enumerator that traverses all pages on demand without pre-fetching. Compose with `.take(n)`, `.select { }`, `.map { }`, etc.
9
+ * `#next_page` method on `Collection` — returns the next page as a new `Collection`, or `nil` when there are no more pages.
10
+ * `#next_page_url` attr_reader on `Collection` — exposes the raw next-page URL string.
11
+ * `list_all` convenience methods on every resource with list endpoints:
12
+ - `client.events.list_all`, `client.events.list_all_invitees`
13
+ - `client.event_types.list_all`, `client.event_types.list_all_availability_schedules`, `client.event_types.list_all_memberships`
14
+ - `client.organizations.list_all_memberships`, `client.organizations.list_all_invitations`, `client.organizations.list_all_activity_log`
15
+ - `client.groups.list_all`, `client.groups.list_all_relationships`
16
+ - `client.availability.list_all_user_busy_times`, `client.availability.list_all_user_schedules`
17
+ - `client.routing_forms.list_all`, `client.routing_forms.list_all_submissions`
18
+ - `client.webhooks.list_all`
19
+ - `client.locations.list_all`
20
+ - `client.outgoing_communications.list_all`
21
+ * `GET /event_type_available_times` — `client.event_types.list_available_times(event_type:, start_time:, end_time:)` returns a `Collection` of `EventTypes::AvailableTime` objects. No pagination (not supported by this endpoint).
22
+ * `GET /event_type_memberships` — `client.event_types.list_memberships(event_type:)` and `list_all_memberships`. Returns `EventTypes::Membership` objects.
23
+ * `POST /scheduling_links` — `client.scheduling_links.create(owner:, owner_type:, max_event_count:)` returns a `SchedulingLink` object. Bare UUID expansion for `owner`.
24
+ * `GET /organizations/{uuid}` — `client.organizations.retrieve(uuid:)` returns an `Organization` object.
25
+ * `GET /outgoing_communications` — `client.outgoing_communications.list(organization:)` and `list_all`. Returns `OutgoingCommunication` objects.
26
+ * `GET /sample_webhook_data` — `client.webhooks.sample(event:, organization:, scope:)` returns the raw response hash (shape varies by event type).
27
+
28
+ ### Changed — Breaking
29
+ * **`Collection#next_page`** (attr_reader returning the raw URL string) is now **`Collection#next_page_url`**. If you were reading the raw next-page URL via `collection.next_page`, change to `collection.next_page_url`.
30
+ * **`Collection#next_page`** is now a **method** that returns the next `Collection` object (or `nil`), not the raw URL string.
31
+
32
+ ### Notes
33
+ * Version 1.0.0 signals complete Calendly API v2 coverage. All documented endpoints are now implemented.
34
+ * Changes originally prepared for an unreleased `0.11.0` were shipped as part of `1.0.0`.
35
+
36
+ [1.0.0]: https://github.com/araluce/calendlyr/compare/v0.10.0...v1.0.0
37
+
38
+ ## [0.10.0]
39
+
40
+ ### Added
41
+ * `Calendlyr::Webhook.verify!`, `valid?`, and `parse` — verify signed webhook payloads with HMAC-SHA256, optional timestamp tolerance, and typed payload parsing
42
+ * `Calendlyr.configure`, `Calendlyr.configuration`, `Calendlyr.client`, and `Calendlyr.reset!` — module-level global configuration and default client support with token/timeout settings
43
+ * Optional request/response logging via `Client.new(logger:)` or `Calendlyr.configure { |c| c.logger = ... }` — INFO for method/URL/status/duration, DEBUG for response body (truncated), WARN for retries, ERROR for API errors. Authorization header is never logged.
44
+ * `Object#to_json` — Serialize any API object to JSON. Works with `JSON.generate`, nested objects, and arrays. The internal `client` reference is automatically excluded from serialization.
45
+ * `client.data_compliance.delete_scheduled_event_data` — Remove scheduled events data within a time range (`POST /data_compliance/deletion/events`)
46
+ * `put_request` support in `Resource` base class for PUT HTTP verb
47
+ * `Collection` now includes `Enumerable` — use `each`, `map`, `select` directly on collections
48
+ * Configurable HTTP timeouts via `Client.new(token:, open_timeout:, read_timeout:)` (default 30s)
49
+ * Automatic retry with exponential backoff for 429 Too Many Requests (max 3 retries, respects `Retry-After` header)
50
+
51
+ ### Changed — Breaking
52
+ * **`Calendlyr::Object`** no longer inherits from `OpenStruct`. Replaced with a zero-dependency hash-backed class. Dynamic dot-access for all API fields is preserved. If you were relying on `OpenStruct`-specific methods (e.g., `marshal_dump`), this is a breaking change.
53
+ * Empty API responses (e.g., DELETE) now return `{}` instead of `true`
54
+ * Requires Ruby >= 3.2.0 (dropped support for Ruby 2.4–3.1)
55
+
56
+ ### Fixed
57
+ * Error messages now include request context (`GET /path`) and expose structured attributes on `Calendlyr::Error` (`status`, `http_method`, `path`, `response_body`) for easier debugging
58
+ * **Security:** Removed `OpenSSL::SSL::VERIFY_NONE` — SSL connections now properly verify certificates
59
+ * **Security:** Bare `rescue` replaced with `rescue JSON::ParserError` — non-JSON errors are no longer silently swallowed
60
+ * `Invitee#cancel` now correctly uses the event UUID instead of the invitee UUID
61
+ * `CGI.parse` replaced with `URI.decode_www_form` for Ruby 4.0 compatibility
62
+ * Tautological test assertions (`assert` with string literal) replaced with `assert_equal`
63
+
64
+ ### Removed
65
+ * `codecov` gem dependency (deprecated since Feb 2022, incompatible with Ruby 4.0). Coverage uploads now use `codecov-action` in CI.
66
+
67
+ [0.10.0]: https://github.com/araluce/calendlyr/compare/v0.9.0...v0.10.0
68
+
69
+ ## [0.9.0]
70
+
71
+ ### Added
72
+ * `client.event_types.create` — Create standard event types (`POST /event_types`)
73
+ * `client.event_types.update` — Update existing event types (`PATCH /event_types/:uuid`)
74
+ * `client.event_types.list_availability_schedules` — List availability schedules for an event type (`GET /event_type_availability_schedules`)
75
+ * `client.event_types.update_availability_schedule` — Update availability schedules (`PATCH /event_type_availability_schedules`)
76
+ * `client.locations.list` — List host's connected locations: Zoom, Google Meet, etc. (`GET /locations`)
77
+ * `client.events.create_invitee` — Programmatically book events via the Scheduling API (`POST /invitees`)
78
+ * `patch_request` support in `Resource` base class for PATCH HTTP verb
79
+
80
+ ### Changed
81
+ * Updated `Event` fixtures with `buffered_start_time`, `buffered_end_time`, `meeting_notes_plain`, `meeting_notes_html`, and `cancellation` fields
82
+ * Updated `Invitee` fixtures with `no_show`, `reconfirmation`, `scheduling_method`, and `invitee_scheduled_by` fields
83
+
84
+ ### Removed — Breaking
85
+ * **`client.outgoing_communications`** — `OutgoingCommunicationsResource` removed (endpoint no longer in Calendly API docs)
86
+ * **`client.scheduling_links`** — `SchedulingLinksResource` and `SchedulingLink` object removed (endpoint no longer in Calendly API docs)
87
+ * **`client.webhooks.sample_webhook_data`** — Method removed (endpoint no longer in Calendly API docs)
88
+ * **`client.event_types.list_available_times`** — Method and `EventTypes::AvailableTime` object removed (endpoint no longer in Calendly API docs)
89
+ * **`client.event_types.list_memberships`** — Method and `EventTypes::Membership` object removed (endpoint no longer in Calendly API docs)
90
+ * **`EventType#available_times`** — Convenience method removed (delegates to removed `list_available_times`)
91
+ * **`Share#associated_scheduling_links`** — Method removed (depends on removed `SchedulingLink`)
92
+ * **`Organization#sample_webhook_data`** — Convenience method removed (delegates to removed `webhooks.sample_webhook_data`)
93
+
94
+ [0.9.0]: https://github.com/araluce/calendlyr/compare/v0.8.0...v0.9.0
95
+
96
+ ## [0.8.0]
97
+ * **Breaking:** All error classes (`BadRequest`, `NotFound`, `Unauthenticated`, `PermissionDenied`, `ExternalCalendarError`, `TooManyRequests`, `InternalServerError`) now inherit from `Calendlyr::Error` instead of `StandardError`. Code using `rescue Calendlyr::Error` will now catch all API errors.
98
+ * Fix: Response handling with empty body
99
+ * Fix: Some doc typos
100
+
101
+ [0.8.0]: https://github.com/araluce/calendlyr/compare/v0.7.5...v0.8.0
102
+
5
103
  ## [0.7.5]
6
104
  * Fix: Calendlyr::TooManyRequests was not included in autoloading list
7
105
  * Fix: Calendlyr::ExternalCalendarError typo in class name
data/README.md CHANGED
@@ -1,103 +1,239 @@
1
- [![](https://img.shields.io/github/license/araluce/calendlyr?kill_cache=1)](https://github.com/araluce/calendlyr/blob/master/LICENSE.txt)
2
- [![](https://github.com/araluce/calendlyr/actions/workflows/ci.yml/badge.svg?kill_cache=1)](https://github.com/araluce/calendlyr/actions)
3
- [![codecov](https://codecov.io/gh/araluce/calendlyr/branch/master/graph/badge.svg?token=YSUU4PHM6Y&kill_cache=1)](https://codecov.io/gh/araluce/calendlyr)
4
- ![Gem Downloads (for specified version)](https://img.shields.io/gem/dt/calendlyr)
1
+ [![](https://img.shields.io/github/license/araluce/calendlyr)](https://github.com/araluce/calendlyr/blob/master/LICENSE.txt)
2
+ [![](https://github.com/araluce/calendlyr/actions/workflows/ci.yml/badge.svg)](https://github.com/araluce/calendlyr/actions)
3
+ [![codecov](https://codecov.io/gh/araluce/calendlyr/branch/master/graph/badge.svg?token=YSUU4PHM6Y)](https://codecov.io/gh/araluce/calendlyr)
4
+ ![Gem Downloads](https://img.shields.io/gem/dt/calendlyr)
5
5
 
6
- # Calendly API Rubygem
6
+ # Calendlyr
7
7
 
8
- Easy and comprehensive rubygem for [Calendly](https://calendly.com/). Currently supports [API v2](https://calendly.stoplight.io/docs/api-docs).
8
+ ![Calendlyr logo](logos/calendlyr_bg_white.png)
9
9
 
10
- You just need a Personal Access Token.
10
+ The simplest way to interact with [Calendly's API v2](https://developer.calendly.com/api-docs) in Ruby. No runtime dependencies, no ceremony — just a Personal Access Token and you're good to go.
11
11
 
12
- ## Dependencies
12
+ ## Installation
13
13
 
14
- No dependencies :tada:
14
+ Calendlyr requires **Ruby >= 3.2.0**.
15
15
 
16
- We understand the importance of not adding unwanted dependencies.
16
+ Add to your Gemfile:
17
17
 
18
- ## 📚 Docs
18
+ ```ruby
19
+ gem "calendlyr"
20
+ ```
19
21
 
20
- ### Installation
22
+ Then run `bundle install`. That's it.
21
23
 
22
- Add this line to your application's Gemfile:
24
+ ## Quick Start
23
25
 
24
26
  ```ruby
25
- gem 'calendlyr', '0.7.5'
27
+ client = Calendlyr::Client.new(token: ENV["CALENDLY_TOKEN"])
28
+
29
+ # List your scheduled events — just pass the UUID, no full URI needed
30
+ events = client.events.list(user: "YOUR_USER_UUID")
31
+ events.data
32
+ #=> [#<Calendlyr::Event>, #<Calendlyr::Event>, ...]
33
+
34
+ # Access event details naturally
35
+ event = events.data.first
36
+ event.name #=> "30 Minute Meeting"
37
+ event.status #=> "active"
38
+ event.start_time #=> "2024-01-15T10:00:00.000000Z"
39
+
40
+ # List invitees for an event
41
+ invitees = client.events.list_invitees(uuid: event.uuid)
42
+ invitees.data.first.email #=> "john@example.com"
26
43
  ```
27
44
 
28
- And then execute:
45
+ ## Global configuration
29
46
 
30
- $ bundle
47
+ For single-tenant apps, you can configure `Calendlyr` once and reuse a default client:
31
48
 
32
- Or install it yourself as:
49
+ ```ruby
50
+ Calendlyr.configure do |config|
51
+ config.token = ENV.fetch("CALENDLY_TOKEN")
52
+ config.open_timeout = 5
53
+ config.read_timeout = 15
54
+ end
55
+
56
+ client = Calendlyr.client
57
+ events = client.events.list(user: "YOUR_USER_UUID")
58
+ ```
33
59
 
34
- $ gem install calendlyr
60
+ `Calendlyr.client` memoizes a client instance and rebuilds it if token or timeout values change.
35
61
 
36
- ### Usage
62
+ ### Optional request/response logging
37
63
 
38
- To access the API, you'll need to create a `Calendlyr::Client` and provide your token. You can generate your Personal Access Token at [https://calendly.com/integrations/api_webhooks](https://calendly.com/integrations/api_webhooks)
64
+ Calendlyr can emit request lifecycle logs with any logger-like object that responds to `info`, `debug`, `warn`, and `error`. Logging is opt-in, and the gem does not ship a logger implementation for you.
39
65
 
40
66
  ```ruby
41
- client = Calendlyr::Client.new(token: ENV["CALENDLY_TOKEN"])
67
+ require "logger"
68
+
69
+ client = Calendlyr::Client.new(token: ENV["CALENDLY_TOKEN"], logger: Logger.new($stdout))
42
70
  ```
43
71
 
44
- The client then provides access to all of the resources.
45
-
46
- ### Resources
47
-
48
- The gem is designed to closely mirror the Calendly API, making it easy to convert API examples into gem code
49
-
50
- Responses are created as objects, like `Calendlyr::Event`. Having types like `Calendlyr::User` is useful for understanding the type of object you're working with. These objects are built using `OpenStruct`, allowing you to access data in a Ruby-like way.
51
-
52
- * [Pagination](docs/resources/pagination.md)
53
- * **Activity Log**
54
- * [Calendlyr::ActivityLog](docs/resources/activity_log/list_activity_log_entries.md)
55
- * **Availabilities**
56
- * [Calendlyr::Availabilities::Rule](docs/resources/availabilities/availability_rule.md)
57
- * [Calendlyr::Availabilities::UserSchedule](docs/resources/availabilities/user_availability_schedule.md)
58
- * [Calendlyr::Availabilities::UserBusyTime](docs/resources/availabilities/user_busy_time.md)
59
- * [Data Compliance](docs/resources/data_compliance.md)
60
- * **Event Types**
61
- * [Calendlyr::EventType](docs/resources/event_types/event_type.md)
62
- * [Calendlyr::EventTypes::AvailableTime](docs/resources/event_types/available_time.md)
63
- * [Calendlyr::EventTypes::Membership](docs/resources/event_types/membership.md)
64
- * [Calendlyr::EventTypes::Profile](docs/resources/event_types/profile.md)
65
- * **Groups**
66
- * [Calendlyr::Group](docs/resources/groups/group.md)
67
- * [Calendlyr::Groups::Relationship](docs/resources/groups/relationship.md)
68
- * **Organizations**
69
- * [Calendlyr::Organization](docs/resources/organizations/organization.md)
70
- * [Calendlyr::Organizations::Invitation](docs/resources/organizations/invitation.md)
71
- * [Calendlyr::Organizations::Membership](docs/resources/organizations/membership.md)
72
- * **Routing Forms**
73
- * [Calendlyr::RoutingForm](docs/resources/routing_forms/routing_form.md)
74
- * [Calendlyr::RoutingForms::Submission](docs/resources/routing_forms/submission.md)
75
- * **Schedule Events**
76
- * [Calendlyr::Event](docs/resources/events/event.md)
77
- * [Calendlyr::Events::Cancellation](docs/resources/events/cancellation.md)
78
- * [Calendlyr::Events::Guest](docs/resources/events/guest.md)
79
- * [Calendlyr::Events::Invitee](docs/resources/events/invitee.md)
80
- * [Calendlyr::Events::InviteeNoShow](docs/resources/events/invitee_no_show.md)
81
- * [Scheduled Links](docs/resources/scheduling_link.md)
82
- * [Shares](docs/resources/share.md)
83
- * [Users](docs/resources/user.md)
84
- * **Webhooks**
85
- * [Calendlyr::Webhooks::Subscription](docs/resources/webhooks/subscription.md)
86
- * [Calendlyr::Webhooks::Payload](docs/resources/webhooks/payload.md)
87
- * [Calendlyr::Webhooks::InviteePayload](docs/resources/webhooks/invitee_payload.md)
72
+ `Logger` is just an example. You can pass any object that responds to `info`, `debug`, `warn`, and `error`.
73
+
74
+ If you're on Ruby 4 and want to use Ruby's `Logger`, make sure your application includes the `logger` gem.
75
+
76
+ Or configure it globally:
77
+
78
+ ```ruby
79
+ Calendlyr.configure do |config|
80
+ config.token = ENV.fetch("CALENDLY_TOKEN")
81
+ config.logger = Logger.new($stdout)
82
+ end
83
+ ```
84
+
85
+ In Rails, this is typically configured in an initializer:
86
+
87
+ ```ruby
88
+ # config/initializers/calendlyr.rb
89
+ Calendlyr.configure do |config|
90
+ config.token = ENV.fetch("CALENDLY_TOKEN")
91
+ end
92
+ ```
93
+
94
+ > [!IMPORTANT]
95
+ > `Calendlyr.client` is module-global and **not thread-safe for multi-tenant usage**.
96
+ > If your app serves multiple tenants or uses per-request credentials, use `Calendlyr::Client.new(token:)` per request.
97
+
98
+ ### Bare UUIDs
99
+
100
+ Most methods that take a Calendly resource reference (like `user:`, `organization:`, `event_type:`, or `owner:`) accept both bare UUIDs and full URIs. When supported, the gem expands bare UUIDs automatically:
101
+
102
+ ```ruby
103
+ # Both are equivalent:
104
+ client.events.list(user: "YOUR_USER_UUID")
105
+ client.events.list(user: "https://api.calendly.com/users/YOUR_USER_UUID")
106
+ ```
107
+
108
+ The gem mirrors the Calendly API closely, so converting API examples into gem code is straightforward. Responses are wrapped in Ruby objects with dot-access for every field.
109
+
110
+ > **Note:** A few endpoints still expect a full Calendly URI for specific parameters. When that matters, the resource docs call it out explicitly.
111
+
112
+ ### Webhook signature verification
113
+
114
+ `Calendlyr::Webhook` (singular) verifies signed webhook payloads. This is separate from `client.webhooks` / `Calendlyr::Webhooks` (plural), which are API resources for managing webhook subscriptions.
115
+
116
+ - Calendly sends the signature in the `Calendly-Webhook-Signature` HTTP header.
117
+ - In Rack/Rails, that header is available as `HTTP_CALENDLY_WEBHOOK_SIGNATURE`.
118
+ - `verify!` raises on invalid signature/timestamp; `valid?` returns `true`/`false`; `parse` verifies first, then JSON-parses and wraps the payload.
119
+
120
+ ```ruby
121
+ payload = request.body.read
122
+ signature_header = request.get_header("HTTP_CALENDLY_WEBHOOK_SIGNATURE")
123
+ signing_key = ENV.fetch("CALENDLY_WEBHOOK_SIGNING_KEY")
124
+
125
+ if Calendlyr::Webhook.valid?(payload: payload, signature_header: signature_header, signing_key: signing_key)
126
+ webhook = Calendlyr::Webhook.parse(
127
+ payload: payload,
128
+ signature_header: signature_header,
129
+ signing_key: signing_key
130
+ )
131
+
132
+ webhook.event #=> "invitee.created"
133
+ webhook.payload #=> Calendlyr::Webhooks::InviteePayload (for invitee.* events)
134
+ #=> Calendlyr::Object (for other/unknown events)
135
+ end
136
+ ```
137
+
138
+ ### JSON serialization
139
+
140
+ All API objects support `#to_json` for easy serialization (caching, logging, API proxying):
141
+
142
+ ```ruby
143
+ event = client.events.retrieve(uuid: "ABC123")
144
+
145
+ event.to_json
146
+ #=> '{"uri":"https://api.calendly.com/scheduled_events/ABC123","name":"30 Minute Meeting",...}'
147
+
148
+ # Works with JSON.generate and nested objects
149
+ JSON.generate(event)
150
+
151
+ # Round-trip: parse back into an Object
152
+ parsed = Calendlyr::Object.new(JSON.parse(event.to_json))
153
+ parsed.name #=> "30 Minute Meeting"
154
+ ```
155
+
156
+ > **Note:** `#to_json` and `#to_h` exclude the internal `client` reference — only API data is serialized.
157
+
158
+ ### Error context
159
+
160
+ API errors now include the HTTP method and path in the message, and expose structured attributes for debugging:
161
+
162
+ ```ruby
163
+ begin
164
+ client.events.retrieve(uuid: "INVALID_UUID")
165
+ rescue Calendlyr::NotFound => error
166
+ error.message #=> "[Error 404] GET /scheduled_events/INVALID_UUID — Not Found. The resource you requested does not exist."
167
+ error.status #=> 404
168
+ error.http_method #=> "GET"
169
+ error.path #=> "/scheduled_events/INVALID_UUID"
170
+ error.response_body #=> { "title" => "Not Found", "message" => "..." }
171
+ end
172
+ ```
173
+
174
+ This makes debugging failed requests much easier without changing existing `rescue Calendlyr::Error` patterns.
175
+
176
+ ## Auto-pagination
177
+
178
+ Calendlyr supports lazy auto-pagination for paginated collection endpoints. There are two ways to consume paginated results:
179
+
180
+ ### `list_all` — Eager, returns a flat Array
181
+
182
+ The simplest option. Fetches every page and returns all items as an Array.
183
+
184
+ ```ruby
185
+ # Get all events across all pages (e.g., hundreds of events)
186
+ events = client.events.list_all(organization: "YOUR_ORG_UUID")
187
+ events #=> [#<Calendlyr::Event>, #<Calendlyr::Event>, ...]
188
+
189
+ # Same pattern works for every resource with a list method:
190
+ client.event_types.list_all(organization: "YOUR_ORG_UUID")
191
+ client.webhooks.list_all(organization: "YOUR_ORG_UUID", scope: "organization")
192
+ client.organizations.list_all_memberships(organization: "YOUR_ORG_UUID")
193
+ client.organizations.list_all_invitations(uuid: "YOUR_ORG_UUID")
194
+ client.organizations.list_all_activity_log(organization: "YOUR_ORG_UUID")
195
+ client.groups.list_all(organization: "YOUR_ORG_UUID")
196
+ client.groups.list_all_relationships(organization: "YOUR_ORG_UUID")
197
+ client.routing_forms.list_all(organization: "YOUR_ORG_UUID")
198
+ client.routing_forms.list_all_submissions(form: "YOUR_FORM_UUID")
199
+ client.availability.list_all_user_busy_times(user: "YOUR_USER_UUID", start_time: "...", end_time: "...")
200
+ client.availability.list_all_user_schedules(user: "YOUR_USER_UUID")
201
+ client.locations.list_all
202
+ client.event_types.list_all_memberships(event_type: "YOUR_EVENT_TYPE_UUID")
203
+ client.outgoing_communications.list_all(organization: "YOUR_ORG_UUID")
204
+ ```
205
+
206
+ ### `auto_paginate` — Lazy Enumerator
207
+
208
+ Returns an `Enumerator::Lazy` that fetches pages on demand. Pages are only requested as you consume items, so you can stop early without fetching all pages.
209
+
210
+ ```ruby
211
+ collection = client.events.list(organization: "YOUR_ORG_UUID")
212
+
213
+ # Take only the first 50 events — fetches only as many pages as needed
214
+ first_50 = collection.auto_paginate.take(50)
215
+
216
+ # Filter lazily — stops fetching once the condition is met
217
+ active = collection.auto_paginate.select { |e| e.status == "active" }.first(10)
218
+
219
+ # Consume all items lazily
220
+ collection.auto_paginate.each do |event|
221
+ puts event.name
222
+ end
223
+ ```
224
+
225
+ ## Documentation
226
+
227
+ For the full list of available resources and methods, check out the [API Reference](docs/resources/).
228
+
229
+ The docs in this repository focus on the Ruby wrapper API. For request/response schemas and endpoint-level behavior, use the official Calendly API docs linked from each resource page.
88
230
 
89
231
  ## Contributing
90
232
 
91
- 1. Fork it ( https://github.com/araluce/calendlyr/fork )
233
+ 1. [Fork it](https://github.com/araluce/calendlyr/fork)
92
234
  2. Create your feature branch (`git checkout -b my-new-feature`)
93
235
  3. Commit your changes (`git commit -am 'Add some feature'`)
94
236
  4. Push to the branch (`git push origin my-new-feature`)
95
237
  5. Create a new Pull Request
96
238
 
97
- When adding resources, update the list of resources in `lib/calendlyr`. Additionally, write a spec and include it in the list in the README
98
-
99
- ## Thanks
100
-
101
- Many thanks to[@markets](https://github.com/markets) (our behind-the-scenes contributor) for all the comments, insights, and tips on this Ruby gem project, and for helping me grow professionally day by day :raised_hands:
102
-
103
- Thanks also to [@excid3](https://github.com/excid3) and his [Vultr.rb](https://github.com/excid3/vultr.rb) rubygem project.
239
+ When adding resources, please write tests and update the [docs](docs/resources/).
data/calendlyr.gemspec CHANGED
@@ -7,20 +7,22 @@ Gem::Specification.new do |spec|
7
7
  spec.email = ["araluce11@gmail.com"]
8
8
 
9
9
  spec.summary = "Ruby bindings for Calendly API."
10
- spec.description = "Ruby bindings for Calendly API. Calendly APIs can be found here: https://calendly.stoplight.io/docs/api-docs/"
10
+ spec.description = "Ruby bindings for Calendly API v2. Full docs: https://developer.calendly.com/api-docs/"
11
11
  spec.homepage = "https://github.com/araluce/calendlyr"
12
12
  spec.license = "MIT"
13
- spec.required_ruby_version = Gem::Requirement.new(">= 2.4.0")
13
+ spec.required_ruby_version = Gem::Requirement.new(">= 3.2.0")
14
14
  spec.metadata["homepage_uri"] = spec.homepage
15
+ spec.metadata["changelog_uri"] = "https://github.com/araluce/calendlyr/blob/master/CHANGELOG.md"
16
+ spec.metadata["documentation_uri"] = "https://github.com/araluce/calendlyr/tree/master/docs"
15
17
 
16
18
  spec.files = `git ls-files -z`.split("\x0")
17
19
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
20
  spec.require_paths = ["lib"]
19
21
 
20
- spec.add_development_dependency "rake", "~> 13.2"
22
+ spec.add_development_dependency "logger", "~> 1.6"
21
23
  spec.add_development_dependency "minitest", "~> 5.25"
22
- spec.add_development_dependency "standard", "~> 1.37" # Max version that supports Ruby 2.7
23
- spec.add_development_dependency "webmock", "~> 3.23"
24
- spec.add_development_dependency "codecov", "~> 0.6"
24
+ spec.add_development_dependency "rake", "~> 13.2"
25
25
  spec.add_development_dependency "simplecov", "~> 0.21"
26
+ spec.add_development_dependency "standard", "~> 1.37"
27
+ spec.add_development_dependency "webmock", "~> 3.23"
26
28
  end
@@ -13,7 +13,8 @@ Visit official [API Doc](https://developer.calendly.com/api-docs/d37c7f031f339-l
13
13
  For the example bellow we will use only required parameters, but you can use any other parameter as well.
14
14
 
15
15
  ```ruby
16
- client.organizations.activity_log(organization: `organization_uri`)
16
+ # organization: accepts a bare UUID or full Calendly URI
17
+ client.organizations.activity_log(organization: "ORG_UUID")
17
18
  #=> #<Calendlyr::Collection @data=[#<Calendlyr::ActivityLog, ...], @client=#<Calendlyr::Client>>
18
19
  ```
19
20
 
@@ -20,8 +20,9 @@ client.availabilities.retrieve_user_schedule(uuid: @uuid)
20
20
  Return the availability schedules of the given user. See [official API doc](https://developer.calendly.com/api-docs/8098de44af94c-list-user-availability-schedules)
21
21
 
22
22
  ```ruby
23
- client.availabilities.list_user_schedules(user: @user)
24
- #=> #<Calendlyr::Collection @data=[#<Calendlyr::Availabilities::UserSchedule uri="https://api.calendly.com/user_availability_schedule/abc123", default=true, name="Working Hours", user="https://api.calendly.com/users/abc123", timezone="America/New_York", rules=[#<OpenStruct type="wday", intervals=[#<OpenStruct from="08:30", to="09:30">], wday="sunday", date="2022-12-31">], client=#<Calendlyr::Client>, uuid="abc123">, #<Calendlyr::Availabilities::UserSchedule uri="https://api.calendly.com/user_availability_schedule/abc456", default=false, name="Evening Hours", user="https://api.calendly.com/users/abc123", timezone="America/New_York", rules=[#<OpenStruct type="wday", intervals=[#<OpenStruct from="08:30", to="17:00">], wday="monday">, #<OpenStruct type="wday", intervals=[#<OpenStruct from="08:30", to="17:00">], wday="tuesday">, #<OpenStruct type="wday", intervals=[], wday="wednesday">, #<OpenStruct type="wday", intervals=[#<OpenStruct from="08:30", to="17:00">], wday="thursday">, #<OpenStruct type="wday", intervals=[#<OpenStruct from="08:30", to="17:00">], wday="friday">, #<OpenStruct type="wday", intervals=[], wday="saturday">, #<OpenStruct type="date", intervals=[#<OpenStruct from="08:30", to="09:30">], date="2028-12-31">], client=#<Calendlyr::Client>, uuid="abc456">], @count=nil, @next_page=nil, @next_page_token=nil, @client=#<Calendlyr::Client>>
23
+ # user: accepts a bare UUID or full Calendly URI
24
+ client.availabilities.list_user_schedules(user: "USER_UUID")
25
+ #=> #<Calendlyr::Collection @data=[#<Calendlyr::Availabilities::UserSchedule>, ...], @count=nil, @next_page=nil, @next_page_token=nil, @client=#<Calendlyr::Client>>
25
26
  ```
26
27
 
27
28
  #### List on me
@@ -14,8 +14,9 @@ Date range can be no greater than 1 week (7 days).
14
14
 
15
15
  For the example bellow we will use only required parameters, but you can use any other parameter as well.
16
16
  ```ruby
17
- client.availabilities.list_user_busy_times(user: `user_uri`, start_time: `start_time`, end_time: `end_time`)
18
- #=> #<Calendlyr::Collection @data=[#<Calendlyr::Availabilities::UserBusyTime type="calendly", start_time="2020-01-02T20:00:00.000000Z", end_time="2020-01-02T20:30:00.000000Z", buffered_start_time="2020-01-02T19:30:00.000000Z", buffered_end_time="2020-01-02T21:00:00.000000Z", event=#<OpenStruct uri="https://api.calendly.com/scheduled_events/abc123">, client=#<Calendlyr::Client>, uuid=nil>, #<Calendlyr::UserBusyTime type="calendly", start_time="2020-01-05T20:00:00.000000Z", end_time="2020-01-05T20:30:00.000000Z", buffered_start_time="2020-01-05T19:30:00.000000Z", buffered_end_time="2020-01-05T21:00:00.000000Z", event=#<OpenStruct uri="https://api.calendly.com/scheduled_events/abc12345">, client=#<Calendlyr::Client>, uuid=nil>, #<Calendlyr::UserBusyTime type="external", start_time="2020-01-07T20:00:00.000000Z", end_time="2020-01-07T20:30:00.000000Z", client=#<Calendlyr::Client>, uuid=nil>], @count=nil, @next_page=nil, @next_page_token=nil, @client=#<Calendlyr::Client>>
17
+ # user: accepts a bare UUID or full Calendly URI
18
+ client.availabilities.list_user_busy_times(user: "USER_UUID", start_time: start_time, end_time: end_time)
19
+ #=> #<Calendlyr::Collection @data=[#<Calendlyr::Availabilities::UserBusyTime>, ...], @count=nil, @next_page=nil, @next_page_token=nil, @client=#<Calendlyr::Client>>
19
20
  ```
20
21
 
21
22
  ## Object methods
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## Delete Invitee Data
4
4
 
5
- Remove invitee data from all previously booked events in your organization..
5
+ Remove invitee data from all previously booked events in your organization.
6
6
  Visit official [API Doc](https://developer.calendly.com/api-docs/4cf896120a018-delete-invitee-data)
7
7
 
8
8
  ```ruby
@@ -0,0 +1,28 @@
1
+ # Availability Schedule Calendlyr::EventTypes::AvailabilitySchedule
2
+
3
+ Availability schedule object for event types.
4
+
5
+ ## Client requests
6
+
7
+ ### List
8
+
9
+ Returns a list of availability schedules for an event type.
10
+
11
+ ```ruby
12
+ client.event_types.list_availability_schedules(event_type_uuid: event_type_uuid)
13
+ #=> #<Calendlyr::Collection @data=[#<Calendlyr::EventTypes::AvailabilitySchedule>, ...], @count=nil, @next_page=nil, @next_page_token=nil, @client=#<Calendlyr::Client>>
14
+ ```
15
+
16
+ ### Update
17
+
18
+ Updates availability schedules for an event type.
19
+
20
+ ```ruby
21
+ client.event_types.update_availability_schedule(
22
+ event_type_uuid: event_type_uuid,
23
+ availability_schedules: [
24
+ {start_time: "2023-10-27T09:00:00Z", end_time: "2023-10-27T12:00:00Z"}
25
+ ]
26
+ )
27
+ #=> {"message"=>"Availability schedules updated successfully."}
28
+ ```
@@ -1,21 +1,25 @@
1
- # Available Time Calendlyr::EventTypes::AvailableTime
1
+ # Available Time (`Calendlyr::EventTypes::AvailableTime`)
2
2
 
3
- An available meeting time slot for the given event type.
3
+ Available time for a specific Event Type in a given time window.
4
4
 
5
- Visit official [API Doc](https://developer.calendly.com/api-docs/2d8d322931358-event-type-available-time)
5
+ Visit official [API Doc](https://developer.calendly.com/api-docs)
6
6
 
7
7
  ## Client requests
8
8
 
9
- ### List
10
- Returns a list of available times for an event type within a specified date range.
9
+ ### List Available Times
11
10
 
12
- Date range can be no greater than 1 week (7 days).
11
+ Returns available time slots for an Event Type between `start_time` and `end_time`.
13
12
 
14
- For the examples bellow we will use only required parameters, but you can use any other parameter as well.
15
-
16
- Visit official [API Doc](https://developer.calendly.com/api-docs/6a1be82aef359-list-event-type-available-times)
13
+ Visit official [API Doc](https://developer.calendly.com/api-docs)
17
14
 
18
15
  ```ruby
19
- client.event_types.list_available_times(event_type, event_type, start_time: start_time, end_time: end_time)
20
- #=> #<Calendlyr::Collection @data=[#<Calendlyr::EventTypes::AvailableTime>, ...], @count=nil, @next_page=nil, @next_page_token=nil, @client=#<Calendlyr::Client>>
16
+ # event_type: accepts a bare UUID or full Calendly URI
17
+ client.event_types.list_available_times(
18
+ event_type: "EVENT_TYPE_UUID",
19
+ start_time: "2026-04-07T09:00:00Z",
20
+ end_time: "2026-04-07T18:00:00Z"
21
+ )
22
+ #=> #<Calendlyr::Collection @data=[#<Calendlyr::EventTypes::AvailableTime>, ...], ...>
21
23
  ```
24
+
25
+ This endpoint does not support pagination.