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.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +9 -5
- data/.gitignore +3 -1
- data/CHANGELOG.md +98 -0
- data/README.md +209 -73
- data/calendlyr.gemspec +8 -6
- data/docs/resources/activity_log/list_activity_log_entries.md +2 -1
- data/docs/resources/availabilities/user_availability_schedule.md +3 -2
- data/docs/resources/availabilities/user_busy_time.md +3 -2
- data/docs/resources/data_compliance.md +1 -1
- data/docs/resources/event_types/availability_schedule.md +28 -0
- data/docs/resources/event_types/available_time.md +15 -11
- data/docs/resources/event_types/event_type.md +21 -9
- data/docs/resources/event_types/membership.md +15 -16
- data/docs/resources/events/cancellation.md +1 -1
- data/docs/resources/events/event.md +6 -5
- data/docs/resources/events/invitee.md +18 -0
- data/docs/resources/events/invitee_no_show.md +2 -1
- data/docs/resources/groups/group.md +2 -1
- data/docs/resources/locations/location.md +16 -0
- data/docs/resources/organizations/membership.md +3 -2
- data/docs/resources/organizations/organization.md +13 -9
- data/docs/resources/outgoing_communications/outgoing_communication.md +28 -0
- data/docs/resources/routing_forms/routing_form.md +2 -1
- data/docs/resources/routing_forms/submission.md +2 -1
- data/docs/resources/scheduling_links/scheduling_link.md +26 -0
- data/docs/resources/share.md +4 -10
- data/docs/resources/webhooks/invitee_payload.md +10 -16
- data/docs/resources/webhooks/payload.md +32 -3
- data/docs/resources/webhooks/sample.md +23 -0
- data/docs/resources/webhooks/subscription.md +10 -6
- data/lib/calendlyr/client.rb +7 -2
- data/lib/calendlyr/collection.rb +42 -9
- data/lib/calendlyr/configuration.rb +24 -0
- data/lib/calendlyr/error.rb +65 -27
- data/lib/calendlyr/object.rb +85 -13
- data/lib/calendlyr/objects/event_type.rb +0 -4
- data/lib/calendlyr/objects/event_types/availability_schedule.rb +8 -0
- data/lib/calendlyr/objects/event_types/available_time.rb +5 -1
- data/lib/calendlyr/objects/event_types/membership.rb +4 -7
- data/lib/calendlyr/objects/events/invitee.rb +1 -1
- data/lib/calendlyr/objects/location.rb +4 -0
- data/lib/calendlyr/objects/organization.rb +0 -4
- data/lib/calendlyr/objects/outgoing_communication.rb +6 -0
- data/lib/calendlyr/objects/scheduling_link.rb +2 -5
- data/lib/calendlyr/objects/share.rb +0 -5
- data/lib/calendlyr/objects/webhooks/payload.rb +30 -0
- data/lib/calendlyr/resource.rb +84 -12
- data/lib/calendlyr/resources/availability.rb +14 -2
- data/lib/calendlyr/resources/event_types.rb +45 -5
- data/lib/calendlyr/resources/events.rb +20 -2
- data/lib/calendlyr/resources/groups.rb +14 -2
- data/lib/calendlyr/resources/locations.rb +13 -0
- data/lib/calendlyr/resources/organizations.rb +25 -3
- data/lib/calendlyr/resources/outgoing_communications.rb +11 -3
- data/lib/calendlyr/resources/routing_forms.rb +14 -2
- data/lib/calendlyr/resources/scheduling_links.rb +5 -2
- data/lib/calendlyr/resources/shares.rb +1 -0
- data/lib/calendlyr/resources/webhooks.rb +11 -3
- data/lib/calendlyr/version.rb +1 -1
- data/lib/calendlyr/webhook.rb +105 -0
- data/lib/calendlyr.rb +50 -0
- data/logos/calendlyr.png +0 -0
- data/logos/calendlyr_bg_white.png +0 -0
- data/test/calendlyr/client_test.rb +29 -0
- data/test/calendlyr/collection_test.rb +168 -0
- data/test/calendlyr/configuration_test.rb +157 -0
- data/test/calendlyr/object_test.rb +82 -1
- data/test/calendlyr/objects/event_type_test.rb +0 -15
- data/test/calendlyr/objects/event_types/availability_schedule_test.rb +20 -0
- data/test/calendlyr/objects/events/cancellation_test.rb +1 -1
- data/test/calendlyr/objects/events/guest_test.rb +1 -1
- data/test/calendlyr/objects/events/invitee_no_show_test.rb +1 -1
- data/test/calendlyr/objects/events/invitee_test.rb +10 -3
- data/test/calendlyr/objects/location_test.rb +22 -0
- data/test/calendlyr/objects/organization_test.rb +0 -8
- data/test/calendlyr/objects/organizations/invitation_test.rb +1 -1
- data/test/calendlyr/objects/share_test.rb +3 -9
- data/test/calendlyr/objects/webhooks/payload_test.rb +15 -0
- data/test/calendlyr/resource_test.rb +456 -2
- data/test/calendlyr/resources/availabilities/user_busy_times_test.rb +26 -0
- data/test/calendlyr/resources/availabilities/user_schedules_test.rb +25 -0
- data/test/calendlyr/resources/data_compliance_test.rb +1 -4
- data/test/calendlyr/resources/event_types_test.rb +132 -0
- data/test/calendlyr/resources/events_test.rb +87 -0
- data/test/calendlyr/resources/groups_test.rb +54 -0
- data/test/calendlyr/resources/locations_test.rb +30 -0
- data/test/calendlyr/resources/organizations_test.rb +96 -2
- data/test/calendlyr/resources/outgoing_communications_test.rb +34 -8
- data/test/calendlyr/resources/routing_forms_test.rb +57 -0
- data/test/calendlyr/resources/scheduling_links_test.rb +31 -6
- data/test/calendlyr/resources/shares_test.rb +15 -0
- data/test/calendlyr/resources/webhooks_test.rb +63 -5
- data/test/calendlyr/webhook_test.rb +292 -0
- data/test/fixtures/activity_log/list_page2.json +30 -0
- data/test/fixtures/event_invitees/list_page2.json +35 -0
- data/test/fixtures/event_invitees/retrieve.json +11 -1
- data/test/fixtures/event_type_availability_schedules/list.json +17 -0
- data/test/fixtures/event_type_availability_schedules/update.json +3 -0
- data/test/fixtures/event_type_available_times/list.json +0 -12
- data/test/fixtures/event_type_memberships/list.json +43 -0
- data/test/fixtures/event_type_memberships/list_page2.json +33 -0
- data/test/fixtures/event_types/create.json +30 -0
- data/test/fixtures/event_types/list_page2.json +37 -0
- data/test/fixtures/event_types/update.json +30 -0
- data/test/fixtures/events/create_invitee.json +37 -0
- data/test/fixtures/events/list_page2.json +29 -0
- data/test/fixtures/events/retrieve.json +12 -2
- data/test/fixtures/group_relationships/list_page2.json +35 -0
- data/test/fixtures/groups/list_page2.json +16 -0
- data/test/fixtures/locations/list.json +16 -0
- data/test/fixtures/locations/list_page2.json +16 -0
- data/test/fixtures/objects/event.json +10 -2
- data/test/fixtures/objects/event_types/availability_schedule.json +6 -0
- data/test/fixtures/objects/location.json +5 -0
- data/test/fixtures/organizations/list_invitations_page2.json +18 -0
- data/test/fixtures/organizations/list_memberships_page2.json +26 -0
- data/test/fixtures/organizations/retrieve.json +11 -0
- data/test/fixtures/outgoing_communications/list.json +4 -6
- data/test/fixtures/outgoing_communications/list_page2.json +21 -0
- data/test/fixtures/routing_forms/list_page2.json +17 -0
- data/test/fixtures/routing_forms/list_routing_form_submission_page2.json +29 -0
- data/test/fixtures/user_availability_schedules/list_page1.json +16 -0
- data/test/fixtures/user_availability_schedules/list_page2.json +16 -0
- data/test/fixtures/user_busy_times/list_page1.json +18 -0
- data/test/fixtures/user_busy_times/list_page2.json +13 -0
- data/test/fixtures/webhooks/list_page2.json +23 -0
- data/test/fixtures/webhooks/sample.json +55 -94
- data/test/test_helper.rb +19 -7
- metadata +70 -27
- data/docs/resources/scheduling_link.md +0 -26
- data/test/calendlyr/objects/event_types/available_time_test.rb +0 -20
- data/test/calendlyr/objects/event_types/membership_test.rb +0 -32
- data/test/calendlyr/objects/scheduling_link_test.rb +0 -17
- data/test/calendlyr/resources/event_types/membership_test.rb +0 -22
- data/test/fixtures/objects/event_types/available_time.json +0 -6
- data/test/fixtures/objects/event_types/membership.json +0 -65
- data/test/fixtures/objects/scheduling_links/event_type.json +0 -5
data/lib/calendlyr/error.rb
CHANGED
|
@@ -1,54 +1,92 @@
|
|
|
1
1
|
module Calendlyr
|
|
2
|
-
class Error < StandardError
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
2
|
+
class Error < StandardError
|
|
3
|
+
attr_reader :status, :http_method, :path, :response_body
|
|
4
|
+
|
|
5
|
+
def initialize(message = nil, status: nil, http_method: nil, path: nil, response_body: nil)
|
|
6
|
+
@status = status
|
|
7
|
+
@http_method = http_method
|
|
8
|
+
@path = path
|
|
9
|
+
@response_body = response_body
|
|
10
|
+
super(message)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
9
13
|
|
|
10
|
-
class
|
|
14
|
+
class WebhookSignatureError < Error; end
|
|
11
15
|
|
|
12
|
-
class
|
|
16
|
+
class WebhookTimestampError < Error; end
|
|
13
17
|
|
|
14
|
-
class
|
|
18
|
+
class PaymentRequired < Error; end
|
|
15
19
|
|
|
16
|
-
|
|
20
|
+
ERROR_TYPES = {
|
|
21
|
+
"400" => "BadRequest",
|
|
22
|
+
"401" => "Unauthenticated",
|
|
23
|
+
"403" => "PermissionDenied",
|
|
24
|
+
"404" => "NotFound",
|
|
25
|
+
"424" => "ExternalCalendarError",
|
|
26
|
+
"429" => "TooManyRequests",
|
|
27
|
+
"500" => "InternalServerError"
|
|
28
|
+
}
|
|
17
29
|
|
|
18
|
-
|
|
30
|
+
ERROR_TYPES.values.each do |error_class|
|
|
31
|
+
Calendlyr.const_set(error_class, Class.new(Error))
|
|
32
|
+
end
|
|
19
33
|
|
|
20
34
|
class ResponseErrorHandler
|
|
21
|
-
|
|
22
|
-
"400" => BadRequest,
|
|
23
|
-
"401" => Unauthenticated,
|
|
24
|
-
"403" => PermissionDenied,
|
|
25
|
-
"404" => NotFound,
|
|
26
|
-
"424" => ExternalCalendarError,
|
|
27
|
-
"429" => TooManyRequests,
|
|
28
|
-
"500" => InternalServerError
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
def initialize(code, body)
|
|
35
|
+
def initialize(code, body, method: nil, path: nil)
|
|
32
36
|
@code = code
|
|
33
37
|
@body = body
|
|
38
|
+
@method = method
|
|
39
|
+
@path = path
|
|
34
40
|
end
|
|
35
41
|
|
|
36
42
|
def error
|
|
37
43
|
return too_many_requests_error if @code == "429"
|
|
38
44
|
|
|
39
|
-
error_type.new(
|
|
45
|
+
error_type.new(
|
|
46
|
+
message,
|
|
47
|
+
status: @code,
|
|
48
|
+
http_method: @method,
|
|
49
|
+
path: @path,
|
|
50
|
+
response_body: @body
|
|
51
|
+
)
|
|
40
52
|
end
|
|
41
53
|
|
|
42
54
|
private
|
|
43
55
|
|
|
44
56
|
def error_type
|
|
45
|
-
return PaymentRequired if @code == "403" && @body
|
|
57
|
+
return PaymentRequired if @code == "403" && @body.fetch("message", "").include?("upgrade")
|
|
46
58
|
|
|
47
|
-
ERROR_TYPES[@code]
|
|
59
|
+
klass = "Calendlyr::#{Calendlyr::ERROR_TYPES[@code]}"
|
|
60
|
+
Calendlyr.const_get(klass)
|
|
48
61
|
end
|
|
49
62
|
|
|
50
63
|
def too_many_requests_error
|
|
51
|
-
error_type.new(
|
|
64
|
+
error_type.new(
|
|
65
|
+
contextual_message("Too many requests, please try again later."),
|
|
66
|
+
status: @code,
|
|
67
|
+
http_method: @method,
|
|
68
|
+
path: @path,
|
|
69
|
+
response_body: @body
|
|
70
|
+
)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def message
|
|
74
|
+
contextual_message(body_message)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def body_message
|
|
78
|
+
[@body["title"], @body["message"]].compact.reject(&:empty?).join(". ")
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def contextual_message(body_message)
|
|
82
|
+
base = "[Error #{@code}]"
|
|
83
|
+
return [base, body_message].reject(&:empty?).join(" ") unless @method && @path
|
|
84
|
+
|
|
85
|
+
path = @path.start_with?("/") ? @path : "/#{@path}"
|
|
86
|
+
context = "#{@method} #{path}"
|
|
87
|
+
return "#{base} #{context}" if body_message.empty?
|
|
88
|
+
|
|
89
|
+
"#{base} #{context} — #{body_message}"
|
|
52
90
|
end
|
|
53
91
|
end
|
|
54
92
|
end
|
data/lib/calendlyr/object.rb
CHANGED
|
@@ -1,31 +1,103 @@
|
|
|
1
|
-
require "
|
|
1
|
+
require "json"
|
|
2
2
|
|
|
3
3
|
module Calendlyr
|
|
4
|
-
class Object
|
|
4
|
+
class Object
|
|
5
|
+
FILTERED_KEYS = %w[client].freeze
|
|
6
|
+
private_constant :FILTERED_KEYS
|
|
7
|
+
|
|
5
8
|
def self.get_slug(path)
|
|
6
9
|
path.split("/").last
|
|
7
10
|
end
|
|
8
11
|
|
|
9
|
-
|
|
10
|
-
|
|
12
|
+
private_class_method :get_slug
|
|
13
|
+
|
|
14
|
+
def initialize(attributes = nil, add_uuid: true, **kwargs)
|
|
15
|
+
attrs = (attributes || {}).merge(kwargs).dup
|
|
16
|
+
if add_uuid && !attrs.key?(:uuid) && !attrs.key?("uuid")
|
|
17
|
+
attrs = attrs.merge(uuid: extract_uuid(attrs))
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
@attributes = attrs.each_with_object({}) do |(key, value), hash|
|
|
21
|
+
hash[key.to_s] = wrap(value)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def method_missing(name, *args, &block)
|
|
26
|
+
if args.empty? && block.nil?
|
|
27
|
+
return @attributes[name.to_s] if @attributes.key?(name.to_s)
|
|
28
|
+
|
|
29
|
+
return nil
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
super
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def respond_to_missing?(name, include_private = false)
|
|
36
|
+
@attributes.key?(name.to_s) || super
|
|
11
37
|
end
|
|
12
38
|
|
|
13
|
-
def
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
elsif obj.is_a?(Array)
|
|
17
|
-
obj.map { |o| to_ostruct(o) }
|
|
18
|
-
else # Assumed to be a primitive value
|
|
19
|
-
obj
|
|
39
|
+
def to_h
|
|
40
|
+
@attributes.except(*FILTERED_KEYS).each_with_object({}) do |(key, value), hash|
|
|
41
|
+
hash[key.to_sym] = unwrap(value)
|
|
20
42
|
end
|
|
21
43
|
end
|
|
22
44
|
|
|
45
|
+
def to_json(*)
|
|
46
|
+
to_h.to_json(*)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def inspect
|
|
50
|
+
attributes = @attributes.map do |key, value|
|
|
51
|
+
"#{key}=#{value.inspect}"
|
|
52
|
+
end.join(" ")
|
|
53
|
+
"#<#{self.class} #{attributes}>"
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def ==(other)
|
|
57
|
+
other.is_a?(self.class) && to_h == other.to_h
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def eql?(other)
|
|
61
|
+
self == other
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def hash
|
|
65
|
+
to_h.hash
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
private
|
|
69
|
+
|
|
23
70
|
def extract_uuid(attrs)
|
|
24
|
-
attrs["uri"]
|
|
71
|
+
uri = attrs["uri"] || attrs[:uri]
|
|
72
|
+
uri ? get_slug(uri) : nil
|
|
25
73
|
end
|
|
26
74
|
|
|
75
|
+
protected
|
|
76
|
+
|
|
27
77
|
def get_slug(path)
|
|
28
|
-
|
|
78
|
+
self.class.send(:get_slug, path)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
private
|
|
82
|
+
|
|
83
|
+
def wrap(value)
|
|
84
|
+
if value.is_a?(Hash)
|
|
85
|
+
self.class.new(value, add_uuid: false)
|
|
86
|
+
elsif value.is_a?(Array)
|
|
87
|
+
value.map { |item| wrap(item) }
|
|
88
|
+
else
|
|
89
|
+
value
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def unwrap(value)
|
|
94
|
+
if value.is_a?(self.class)
|
|
95
|
+
value.to_h
|
|
96
|
+
elsif value.is_a?(Array)
|
|
97
|
+
value.map { |item| unwrap(item) }
|
|
98
|
+
else
|
|
99
|
+
value
|
|
100
|
+
end
|
|
29
101
|
end
|
|
30
102
|
end
|
|
31
103
|
end
|
|
@@ -7,9 +7,5 @@ module Calendlyr
|
|
|
7
7
|
def create_share(**params)
|
|
8
8
|
client.shares.create(**params.merge(event_type: uri))
|
|
9
9
|
end
|
|
10
|
-
|
|
11
|
-
def available_times(start_time:, end_time:, **params)
|
|
12
|
-
client.event_types.list_available_times(**params.merge(event_type: uri, start_time: start_time, end_time: end_time))
|
|
13
|
-
end
|
|
14
10
|
end
|
|
15
11
|
end
|
|
@@ -1,11 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
class EventTypes::Membership < Object
|
|
3
|
-
def associated_event_type
|
|
4
|
-
client.event_types.retrieve(uuid: get_slug(event_type.uri))
|
|
5
|
-
end
|
|
1
|
+
# frozen_string_literal: true
|
|
6
2
|
|
|
7
|
-
|
|
8
|
-
|
|
3
|
+
module Calendlyr
|
|
4
|
+
module EventTypes
|
|
5
|
+
class Membership < Object
|
|
9
6
|
end
|
|
10
7
|
end
|
|
11
8
|
end
|
|
@@ -43,10 +43,6 @@ module Calendlyr
|
|
|
43
43
|
client.webhooks.create(**params.merge(organization: uri, url: url, events: events, scope: scope))
|
|
44
44
|
end
|
|
45
45
|
|
|
46
|
-
def sample_webhook_data(event:, scope:, **params)
|
|
47
|
-
client.webhooks.sample_webhook_data(event: event, scope: scope, organization: uri, **params)
|
|
48
|
-
end
|
|
49
|
-
|
|
50
46
|
# Invitations
|
|
51
47
|
def invite_user(email:, **params)
|
|
52
48
|
client.organizations.invite(**params.merge(organization_uuid: uuid, email: email))
|
|
@@ -1,4 +1,34 @@
|
|
|
1
1
|
module Calendlyr
|
|
2
2
|
class Webhooks::Payload < Object
|
|
3
|
+
INVITEE_EVENTS = %w[
|
|
4
|
+
invitee.created
|
|
5
|
+
invitee.canceled
|
|
6
|
+
invitee_no_show.created
|
|
7
|
+
invitee_no_show.deleted
|
|
8
|
+
].freeze
|
|
9
|
+
|
|
10
|
+
def initialize(attributes = nil, add_uuid: true, **kwargs)
|
|
11
|
+
attrs = (attributes || {}).merge(kwargs)
|
|
12
|
+
attrs = attrs.merge("payload" => payload_object(attrs))
|
|
13
|
+
|
|
14
|
+
super(attrs, add_uuid: add_uuid)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def payload_object(attrs)
|
|
20
|
+
payload = attrs["payload"] || attrs[:payload]
|
|
21
|
+
return payload unless payload.is_a?(Hash)
|
|
22
|
+
|
|
23
|
+
return Webhooks::InviteePayload.new(payload, add_uuid: false) if INVITEE_EVENTS.include?(attrs["event"] || attrs[:event])
|
|
24
|
+
|
|
25
|
+
payload
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def wrap(value)
|
|
29
|
+
return value unless value.is_a?(Hash)
|
|
30
|
+
|
|
31
|
+
Calendlyr::Object.new(value, add_uuid: false)
|
|
32
|
+
end
|
|
3
33
|
end
|
|
4
34
|
end
|
data/lib/calendlyr/resource.rb
CHANGED
|
@@ -8,6 +8,8 @@ module Calendlyr
|
|
|
8
8
|
attr_reader :client
|
|
9
9
|
|
|
10
10
|
ERROR_CODES = %w[400 401 403 404 424 429 500]
|
|
11
|
+
MAX_RETRIES = 3
|
|
12
|
+
RETRY_BACKOFF = [1, 2, 4]
|
|
11
13
|
|
|
12
14
|
def initialize(client)
|
|
13
15
|
@client = client
|
|
@@ -16,15 +18,23 @@ module Calendlyr
|
|
|
16
18
|
private
|
|
17
19
|
|
|
18
20
|
def get_request(url, params: {})
|
|
19
|
-
handle_response request(url, Net::HTTP::Get, params: params)
|
|
21
|
+
handle_response request(url, Net::HTTP::Get, params: params), method: "GET", path: url
|
|
20
22
|
end
|
|
21
23
|
|
|
22
24
|
def post_request(url, body:)
|
|
23
|
-
handle_response request(url, Net::HTTP::Post, body: body)
|
|
25
|
+
handle_response request(url, Net::HTTP::Post, body: body), method: "POST", path: url
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def patch_request(url, body:)
|
|
29
|
+
handle_response request(url, Net::HTTP::Patch, body: body), method: "PATCH", path: url
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def put_request(url, body:)
|
|
33
|
+
handle_response request(url, Net::HTTP::Put, body: body), method: "PUT", path: url
|
|
24
34
|
end
|
|
25
35
|
|
|
26
36
|
def delete_request(url, params: {})
|
|
27
|
-
handle_response request(url, Net::HTTP::Delete, params: params)
|
|
37
|
+
handle_response request(url, Net::HTTP::Delete, params: params), method: "DELETE", path: url
|
|
28
38
|
end
|
|
29
39
|
|
|
30
40
|
def request(url, req_type, body: {}, params: {}, base_url: Client::BASE_URL)
|
|
@@ -37,30 +47,92 @@ module Calendlyr
|
|
|
37
47
|
|
|
38
48
|
http = Net::HTTP.new(uri.host, uri.port)
|
|
39
49
|
http.use_ssl = true
|
|
40
|
-
http.
|
|
50
|
+
http.open_timeout = client.open_timeout
|
|
51
|
+
http.read_timeout = client.read_timeout
|
|
41
52
|
|
|
42
53
|
request = req_type.new(uri)
|
|
43
54
|
request["Content-Type"] = "application/json"
|
|
44
55
|
request["Authorization"] = "Bearer #{client.token}"
|
|
45
56
|
request.body = body.to_json if body.any?
|
|
46
57
|
|
|
47
|
-
|
|
58
|
+
logging_enabled = !logger.nil?
|
|
59
|
+
start_time = monotonic_now if logging_enabled
|
|
60
|
+
request_method = request.method
|
|
61
|
+
request_url = uri.to_s if logging_enabled
|
|
62
|
+
attempts = 0
|
|
63
|
+
response = nil
|
|
64
|
+
|
|
65
|
+
loop do
|
|
66
|
+
response = http.request(request)
|
|
67
|
+
break unless response.code == "429"
|
|
68
|
+
break if attempts >= MAX_RETRIES
|
|
69
|
+
|
|
70
|
+
backoff_seconds = retry_after_seconds(response, attempts)
|
|
71
|
+
log(:warn, "retry_attempt=#{attempts + 1} method=#{request_method} url=#{request_url} status=429 backoff_seconds=#{backoff_seconds}")
|
|
72
|
+
sleep backoff_seconds
|
|
73
|
+
attempts += 1
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
if logging_enabled
|
|
77
|
+
log(:info, "method=#{request_method} url=#{request_url} status=#{response.code} duration_ms=#{elapsed_milliseconds(start_time)}")
|
|
78
|
+
log(:debug, "response_body=#{truncated_body(response.body.to_s)}")
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
response
|
|
48
82
|
end
|
|
49
83
|
|
|
50
|
-
def handle_response(response)
|
|
51
|
-
|
|
84
|
+
def handle_response(response, method: nil, path: nil)
|
|
85
|
+
body_string = response.body.to_s
|
|
52
86
|
|
|
53
87
|
body = begin
|
|
54
|
-
JSON.parse(
|
|
55
|
-
rescue
|
|
88
|
+
body_string.empty? ? {} : JSON.parse(body_string)
|
|
89
|
+
rescue JSON::ParserError
|
|
56
90
|
{}
|
|
57
91
|
end
|
|
58
92
|
|
|
59
93
|
if ERROR_CODES.include? response.code
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
body
|
|
94
|
+
log(:error, "method=#{method} path=/#{path} status=#{response.code} response_body=#{truncated_body(body_string)}")
|
|
95
|
+
raise ResponseErrorHandler.new(response.code, body, method: method, path: path).error
|
|
63
96
|
end
|
|
97
|
+
|
|
98
|
+
body
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def retry_after_seconds(response, attempt)
|
|
102
|
+
retry_after = response["Retry-After"]
|
|
103
|
+
return retry_after.to_i if retry_after&.match?(/^\d+$/)
|
|
104
|
+
|
|
105
|
+
RETRY_BACKOFF.fetch(attempt)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def expand_uri(value, resource_type)
|
|
109
|
+
return value if value.nil? || value.start_with?("https://")
|
|
110
|
+
|
|
111
|
+
"#{Client::BASE_URL}/#{resource_type}/#{value}"
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def logger
|
|
115
|
+
client.logger
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def log(level, message)
|
|
119
|
+
return unless logger
|
|
120
|
+
|
|
121
|
+
logger.public_send(level, "[calendlyr] #{message}")
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def truncated_body(body_string)
|
|
125
|
+
return body_string if body_string.length <= 1000
|
|
126
|
+
|
|
127
|
+
"#{body_string[0, 1000]}... (truncated)"
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def monotonic_now
|
|
131
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def elapsed_milliseconds(start_time)
|
|
135
|
+
((monotonic_now - start_time) * 1000).round(1)
|
|
64
136
|
end
|
|
65
137
|
end
|
|
66
138
|
end
|
|
@@ -2,14 +2,26 @@ module Calendlyr
|
|
|
2
2
|
class AvailabilityResource < Resource
|
|
3
3
|
# User Busy Time
|
|
4
4
|
def list_user_busy_times(user:, start_time:, end_time:, **params)
|
|
5
|
+
next_page_caller = ->(page_token:) { list_user_busy_times(user: user, start_time: start_time, end_time: end_time, **params, page_token: page_token) }
|
|
6
|
+
user = expand_uri(user, "users")
|
|
5
7
|
response = get_request("user_busy_times", params: {user: user, start_time: start_time, end_time: end_time}.merge(params).compact)
|
|
6
|
-
Collection.from_response(response, type: Availabilities::UserBusyTime, client: client)
|
|
8
|
+
Collection.from_response(response, type: Availabilities::UserBusyTime, client: client, next_page_caller: next_page_caller)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def list_all_user_busy_times(user:, start_time:, end_time:, **params)
|
|
12
|
+
list_user_busy_times(user: user, start_time: start_time, end_time: end_time, **params).auto_paginate.to_a
|
|
7
13
|
end
|
|
8
14
|
|
|
9
15
|
# User Schedule
|
|
10
16
|
def list_user_schedules(user:, **params)
|
|
17
|
+
next_page_caller = ->(page_token:) { list_user_schedules(user: user, **params, page_token: page_token) }
|
|
18
|
+
user = expand_uri(user, "users")
|
|
11
19
|
response = get_request("user_availability_schedules", params: {user: user}.merge(params).compact)
|
|
12
|
-
Collection.from_response(response, type: Availabilities::UserSchedule, client: client)
|
|
20
|
+
Collection.from_response(response, type: Availabilities::UserSchedule, client: client, next_page_caller: next_page_caller)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def list_all_user_schedules(user:, **params)
|
|
24
|
+
list_user_schedules(user: user, **params).auto_paginate.to_a
|
|
13
25
|
end
|
|
14
26
|
|
|
15
27
|
def retrieve_user_schedule(uuid:)
|
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
module Calendlyr
|
|
2
2
|
class EventTypesResource < Resource
|
|
3
3
|
def list(**params)
|
|
4
|
+
next_page_caller = ->(page_token:) { list(**params, page_token: page_token) }
|
|
5
|
+
params[:user] = expand_uri(params[:user], "users") if params[:user]
|
|
6
|
+
params[:organization] = expand_uri(params[:organization], "organizations") if params[:organization]
|
|
4
7
|
response = get_request("event_types", params: params)
|
|
5
|
-
Collection.from_response(response, type: EventType, client: client)
|
|
8
|
+
Collection.from_response(response, type: EventType, client: client, next_page_caller: next_page_caller)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def list_all(**params)
|
|
12
|
+
list(**params).auto_paginate.to_a
|
|
6
13
|
end
|
|
7
14
|
|
|
8
15
|
def retrieve(uuid:)
|
|
@@ -14,15 +21,48 @@ module Calendlyr
|
|
|
14
21
|
EventType.new post_request("one_off_event_types", body: body).dig("resource").merge(client: client)
|
|
15
22
|
end
|
|
16
23
|
|
|
17
|
-
def
|
|
18
|
-
|
|
19
|
-
|
|
24
|
+
def create(name:, duration:, pooling_type:, **params)
|
|
25
|
+
body = {name: name, duration: duration, pooling_type: pooling_type}.merge(params)
|
|
26
|
+
EventType.new post_request("event_types", body: body).dig("resource").merge(client: client)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def update(uuid:, **params)
|
|
30
|
+
EventType.new patch_request("event_types/#{uuid}", body: params).dig("resource").merge(client: client)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Availability Schedules
|
|
34
|
+
def list_availability_schedules(event_type_uuid:, **params)
|
|
35
|
+
next_page_caller = ->(page_token:) { list_availability_schedules(event_type_uuid: event_type_uuid, **params, page_token: page_token) }
|
|
36
|
+
response = get_request("event_type_availability_schedules", params: {event_type_uuid: event_type_uuid}.merge(params))
|
|
37
|
+
Collection.from_response(response, type: EventTypes::AvailabilitySchedule, client: client, next_page_caller: next_page_caller)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def list_all_availability_schedules(event_type_uuid:, **params)
|
|
41
|
+
list_availability_schedules(event_type_uuid: event_type_uuid, **params).auto_paginate.to_a
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def update_availability_schedule(event_type_uuid:, availability_schedules:, **params)
|
|
45
|
+
body = {event_type_uuid: event_type_uuid, availability_schedules: availability_schedules}.merge(params)
|
|
46
|
+
patch_request("event_type_availability_schedules", body: body)
|
|
20
47
|
end
|
|
21
48
|
|
|
22
|
-
# Available Times
|
|
49
|
+
# Available Times (no pagination — endpoint does not support it)
|
|
23
50
|
def list_available_times(event_type:, start_time:, end_time:, **params)
|
|
51
|
+
event_type = expand_uri(event_type, "event_types")
|
|
24
52
|
response = get_request("event_type_available_times", params: {event_type: event_type, start_time: start_time, end_time: end_time}.merge(params))
|
|
25
53
|
Collection.from_response(response, type: EventTypes::AvailableTime, client: client)
|
|
26
54
|
end
|
|
55
|
+
|
|
56
|
+
# Event Type Memberships
|
|
57
|
+
def list_memberships(event_type:, **params)
|
|
58
|
+
next_page_caller = ->(page_token:) { list_memberships(event_type: event_type, **params, page_token: page_token) }
|
|
59
|
+
event_type = expand_uri(event_type, "event_types")
|
|
60
|
+
response = get_request("event_type_memberships", params: {event_type: event_type}.merge(params))
|
|
61
|
+
Collection.from_response(response, type: EventTypes::Membership, client: client, next_page_caller: next_page_caller)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def list_all_memberships(event_type:, **params)
|
|
65
|
+
list_memberships(event_type: event_type, **params).auto_paginate.to_a
|
|
66
|
+
end
|
|
27
67
|
end
|
|
28
68
|
end
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
module Calendlyr
|
|
2
2
|
class EventsResource < Resource
|
|
3
3
|
def list(**params)
|
|
4
|
+
next_page_caller = ->(page_token:) { list(**params, page_token: page_token) }
|
|
5
|
+
params[:user] = expand_uri(params[:user], "users") if params[:user]
|
|
4
6
|
response = get_request("scheduled_events", params: params)
|
|
5
|
-
Collection.from_response(response, type: Event, client: client)
|
|
7
|
+
Collection.from_response(response, type: Event, client: client, next_page_caller: next_page_caller)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def list_all(**params)
|
|
11
|
+
list(**params).auto_paginate.to_a
|
|
6
12
|
end
|
|
7
13
|
|
|
8
14
|
def retrieve(uuid:)
|
|
@@ -15,20 +21,32 @@ module Calendlyr
|
|
|
15
21
|
|
|
16
22
|
# Invitee
|
|
17
23
|
def list_invitees(uuid:, **params)
|
|
24
|
+
next_page_caller = ->(page_token:) { list_invitees(uuid: uuid, **params, page_token: page_token) }
|
|
18
25
|
response = get_request("scheduled_events/#{uuid}/invitees", params: params)
|
|
19
|
-
Collection.from_response(response, type: Events::Invitee, client: client)
|
|
26
|
+
Collection.from_response(response, type: Events::Invitee, client: client, next_page_caller: next_page_caller)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def list_all_invitees(uuid:, **params)
|
|
30
|
+
list_invitees(uuid: uuid, **params).auto_paginate.to_a
|
|
20
31
|
end
|
|
21
32
|
|
|
22
33
|
def retrieve_invitee(event_uuid:, invitee_uuid:)
|
|
23
34
|
Events::Invitee.new get_request("scheduled_events/#{event_uuid}/invitees/#{invitee_uuid}").dig("resource").merge(client: client)
|
|
24
35
|
end
|
|
25
36
|
|
|
37
|
+
def create_invitee(event_type:, start_time:, invitee:, **params)
|
|
38
|
+
event_type = expand_uri(event_type, "event_types")
|
|
39
|
+
body = {event_type: event_type, start_time: start_time, invitee: invitee}.merge(params)
|
|
40
|
+
Events::Invitee.new post_request("invitees", body: body).dig("invitee").merge(client: client)
|
|
41
|
+
end
|
|
42
|
+
|
|
26
43
|
# Invitee No Show
|
|
27
44
|
def retrieve_invitee_no_show(uuid:)
|
|
28
45
|
Events::InviteeNoShow.new get_request("invitee_no_shows/#{uuid}").dig("resource").merge(client: client)
|
|
29
46
|
end
|
|
30
47
|
|
|
31
48
|
def create_invitee_no_show(invitee:)
|
|
49
|
+
invitee = expand_uri(invitee, "invitees")
|
|
32
50
|
body = {invitee: invitee}
|
|
33
51
|
Events::InviteeNoShow.new post_request("invitee_no_shows", body: body).dig("resource").merge(client: client)
|
|
34
52
|
end
|