cronofy 0.5.3 → 0.6.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2b042634f429bc12cf929f77ab3736bea099b155
4
- data.tar.gz: 46301a97a1d85dd7477d3d63a43c8be5ec675051
3
+ metadata.gz: bc7139bd209e180668455e51a5973e065bedd6b2
4
+ data.tar.gz: 830ebcf54b85a0aba2150e30ca5e0b2d5d0f1ae4
5
5
  SHA512:
6
- metadata.gz: 3d3ecec788a50f3195af14120ff3e23c157903d721f68fa3fdbf435357516ea779d5cf08443c0e86de91169f0297697a561319efbb69b648b5fd6778b286c0f8
7
- data.tar.gz: 1ed2f31caf7e1f5670c5e24ee5dc58a5af46738622b240ac0e886e2af6286fe41f712a8e6f3c03ea536cb1ade3fcde3c5664b3b27e7bd611d0556af0fbeadebf
6
+ metadata.gz: d93493fd9c16426323f15d9ffeb0b726bcb2bfaf341f838687b360807ef1cea427a05dbffb5b010694ff30b40ea1f43579711e30c96e6c32c4c0f181a6cd6060
7
+ data.tar.gz: 49c9dd164f765ca56629514bfa6ef07f69d7859b3f77a8162b7912a07429e4fa5b94a65e4daedec6f32ee241d653aef129789078267aead1ab2d2d86734cc5f3
data/README.md CHANGED
@@ -2,9 +2,8 @@
2
2
 
3
3
  [![Build Status](https://travis-ci.org/cronofy/cronofy-ruby.svg?branch=master)](https://travis-ci.org/cronofy/cronofy-ruby)
4
4
  [![Gem Version](https://badge.fury.io/rb/cronofy.svg)](http://badge.fury.io/rb/cronofy)
5
- [![Join the chat at https://gitter.im/cronofy/cronofy-ruby](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/cronofy/cronofy-ruby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
6
5
 
7
- [Cronofy](http://www.cronofy.com) - one API for all the calendars (Google, Outlook, iCloud, Exchange).
6
+ [Cronofy](https://www.cronofy.com) - one API for all the calendars (Google, iCloud, Exchange, Office 365, Outlook.com)
8
7
 
9
8
  ## Installation
10
9
 
@@ -14,92 +13,98 @@ Add this line to your application's Gemfile:
14
13
  gem 'cronofy'
15
14
  ```
16
15
 
17
- And then execute:
16
+ And then at your command prompt run:
18
17
 
19
- $ bundle
18
+ ```
19
+ bundle install
20
+ ```
20
21
 
21
- Or install it yourself as:
22
+ ## Usage
22
23
 
23
- $ gem install cronofy
24
+ In order to use the Cronofy API you will need to [create a developer account](https://app.cronofy.com/sign_up/new).
24
25
 
25
- ## Usage
26
+ From there you can [create personal access tokens](https://app.cronofy.com/oauth/applications/5447ae289bd94726da00000f/tokens)
27
+ to access your own calendars, or you can [create an OAuth application](https://app.cronofy.com/oauth/applications/new)
28
+ to obtain an OAuth `client_id` and `client_secret` to be able to use the full
29
+ API.
26
30
 
27
- You have to register on the Cronofy website and create an application there. You will then get a client id and client secret.
31
+ ## Creating a client
28
32
 
29
- You can either set them as the enviroment variables `CRONOFY_CLIENT_ID` and `CRONOFY_CLIENT_SECRET` and have them picked up automatically when creating a new `Cronofy::Client`:
33
+ To make calls to the Cronofy API you must create a `Cronofy::Client`. This takes
34
+ four keyword arguments, all of which are optional:
30
35
 
31
36
  ```ruby
32
- cronofy = Cronofy::Client.new
37
+ cronofy = Cronofy::Client.new(
38
+ client_id: 'CLIENT_ID',
39
+ client_secret: 'CLIENT_SECRET',
40
+ access_token: 'ACCESS_TOKEN',
41
+ refresh_token: 'REFRESH_TOKEN'
42
+ )
33
43
  ```
34
44
 
35
- Or you can specify them explicitly:
45
+ When using a [personal access token](https://app.cronofy.com/oauth/applications/5447ae289bd94726da00000f/tokens)
46
+ you only need to provide the `access_token` argument.
36
47
 
37
- ```ruby
38
- cronofy = Cronofy::Client.new(client_id: 'CLIENT_ID', client_secret: 'CLIENT_SECRET')
39
- ```
48
+ When working against your own OAuth application you will need to provide the
49
+ `client_id` and `client_secret` when [going through the authorization process](https://www.cronofy.com/developers/api/#authorization)
50
+ for a user, and when [refreshing an access token](https://www.cronofy.com/developers/api/#token-refresh).
40
51
 
41
- You can also pass an existing access token and refresh token if you already have a pair for the user:
52
+ If `client_id` and `client_secret` are not specified explicitly the values from
53
+ the environment variables `CRONOFY_CLIENT_ID` and `CRONOFY_CLIENT_SECRET` will
54
+ be used if present.
42
55
 
43
- ```ruby
44
- cronofy = Cronofy::Client.new(access_token: 'ACCESS_TOKEN', refresh_token: 'REFRESH_TOKEN')
45
- ```
56
+ ## Authorization
46
57
 
47
- ### Authorization
58
+ [API documentation](https://www.cronofy.com/developers/api/#authorization)
48
59
 
49
- Generate a link for a user to grant access for their calendars:
60
+ Generate a link for a user to grant access to their calendars:
50
61
 
51
62
  ```ruby
52
- cronofy.user_auth_link('http://localhost:3000/oauth2/callback')
63
+ authorization_url = cronofy.user_auth_link('http://yoursite.dev/oauth2/callback')
53
64
  ```
54
65
 
55
- The returned URL is a page on your website that will handle the OAuth 2.0 callback and receive a code parameter. You can then use that code to retrieve an OAuth token granting access to the user's Cronofy account:
66
+ The callback URL is a page on your website that will handle the OAuth 2.0
67
+ callback and receive a `code` parameter. You can then use that code to retrieve
68
+ an OAuth token granting access to the user's Cronofy account:
56
69
 
57
70
  ```ruby
58
- token = cronofy.get_token_from_code(code, 'http://localhost:3000/oauth2/callback')
71
+ response = cronofy.get_token_from_code(code, 'http://yoursite.dev/oauth2/callback')
59
72
  ```
60
73
 
61
- You should save the `access_token` and `refresh_token` for later use.
74
+ You should save the response's `access_token` and `refresh_token` for later use.
62
75
 
63
- ### List calendars
76
+ Note that the **exact same** callback URL must be passed to both methods for
77
+ access to be granted.
64
78
 
65
- Get a list of all the user calendars:
79
+ If you use the [omniauth gem](https://rubygems.org/gems/omniauth), you can use
80
+ our [omniauth-cronofy strategy gem](https://rubygems.org/gems/omniauth-cronofy)
81
+ to perform this process.
66
82
 
67
- ```ruby
68
- cronofy.list_calendars
69
- ```
83
+ ## List calendars
70
84
 
71
- You will get a list of the user's calendars with each entry being a wrapped
72
- version of the following JSON structure:
73
-
74
- ```json
75
- {
76
- "provider_name": "google",
77
- "profile_name": "YYYYYYYY@gmail.com",
78
- "calendar_id": "cal_YYYYYYYY-UNIQUE_CAL_ID_HERE-YYYYYYYY",
79
- "calendar_name": "Office Calendar",
80
- "calendar_readonly": false,
81
- "calendar_deleted": false
82
- }
83
- ```
85
+ [API documentation](https://www.cronofy.com/developers/api/#calendars)
84
86
 
85
- The properties can be accessed like so:
87
+ Get a list of all the user's calendars:
86
88
 
87
89
  ```ruby
88
- calendar = cronofy.list_calendars.first
89
- calendar.calendar_id
90
- # => "cal_YYYYYYYY-UNIQUE_CAL_ID_HERE-YYYYYYYY"
90
+ calendars = cronofy.list_calendars
91
91
  ```
92
92
 
93
- ### Read events
93
+ ## Read events
94
+
95
+ [API documentation](https://www.cronofy.com/developers/api/#read-events)
96
+
97
+ Get a list of events from the user's calendars:
94
98
 
95
99
  ```ruby
96
100
  events = cronofy.read_events
97
- events.first
98
- # => {"calendar_id" => ....}
99
101
  ```
100
102
 
103
+ Note that the gem handles iterating through the pages on your behalf.
101
104
 
102
- ### Create or update events
105
+ ## Create or update events
106
+
107
+ [API documentation](https://www.cronofy.com/developers/api/#upsert-event)
103
108
 
104
109
  To create/update an event in the user's calendar:
105
110
 
@@ -118,7 +123,9 @@ event_data = {
118
123
  cronofy.upsert_event(calendar_id, event_data)
119
124
  ```
120
125
 
121
- ### Delete events
126
+ ## Delete events
127
+
128
+ [API documentation](https://www.cronofy.com/developers/api/#delete-event)
122
129
 
123
130
  To delete an event from user's calendar:
124
131
 
@@ -128,6 +135,6 @@ cronofy.delete_event(calendar_id, 'uniq-id')
128
135
 
129
136
  ## Links
130
137
 
131
- * [API Docs](http://www.cronofy.com/developers/api)
138
+ * [API documentation](https://www.cronofy.com/developers/api)
132
139
  * [API mailing list](https://groups.google.com/d/forum/cronofy-api)
133
140
 
@@ -177,7 +177,49 @@ module Cronofy
177
177
  end
178
178
 
179
179
  url = ::Cronofy.api_url + "/v1/events"
180
- ReadEventsIterator.new(access_token!, url, params)
180
+ PagedResultIterator.new(PagedEventsResult, :events, access_token!, url, params)
181
+ end
182
+
183
+ # Public: Returns a lazily-evaluated Enumerable of FreeBusy that satisfy the
184
+ # given query criteria.
185
+ #
186
+ # options - The Hash options used to refine the selection (default: {}):
187
+ # :from - The minimum Date from which to return events
188
+ # (optional).
189
+ # :to - The Date to return events up until (optional).
190
+ # :tzid - A String representing a known time zone
191
+ # identifier from the IANA Time Zone Database
192
+ # (default: Etc/UTC).
193
+ # :include_managed - A Boolean specifying whether events that you
194
+ # are managing for the account should be
195
+ # included or excluded from the results
196
+ # (optional).
197
+ #
198
+ # The first page will be retrieved eagerly so that common errors will happen
199
+ # inline. However, subsequent pages (if any) will be requested lazily.
200
+ #
201
+ # See http://www.cronofy.com/developers/api/alpha#free-busy for reference.
202
+ #
203
+ # Returns a lazily-evaluated Enumerable of FreeBusy
204
+ #
205
+ # Raises Cronofy::CredentialsMissingError if no credentials available.
206
+ # Raises Cronofy::AuthenticationFailureError if the access token is no
207
+ # longer valid.
208
+ # Raises Cronofy::AuthorizationFailureError if the access token does not
209
+ # include the required scope.
210
+ # Raises Cronofy::InvalidRequestError if the request contains invalid
211
+ # parameters.
212
+ # Raises Cronofy::TooManyRequestsError if the request exceeds the rate
213
+ # limits for the application.
214
+ def free_busy(options = {})
215
+ params = FREE_BUSY_DEFAULT_PARAMS.merge(options)
216
+
217
+ FREE_BUSY_TIME_PARAMS.select { |tp| params.key?(tp) }.each do |tp|
218
+ params[tp] = to_iso8601(params[tp])
219
+ end
220
+
221
+ url = ::Cronofy.api_url + "/v1/free_busy"
222
+ PagedResultIterator.new(PagedFreeBusyResult, :free_busy, access_token!, url, params)
181
223
  end
182
224
 
183
225
  # Public: Deletes an event from the specified calendar
@@ -399,8 +441,13 @@ module Cronofy
399
441
 
400
442
  private
401
443
 
402
- READ_EVENTS_DEFAULT_PARAMS = { tzid: "Etc/UTC" }.freeze
444
+ FREE_BUSY_DEFAULT_PARAMS = { tzid: "Etc/UTC" }.freeze
445
+ FREE_BUSY_TIME_PARAMS = %i{
446
+ from
447
+ to
448
+ }.freeze
403
449
 
450
+ READ_EVENTS_DEFAULT_PARAMS = { tzid: "Etc/UTC" }.freeze
404
451
  READ_EVENTS_TIME_PARAMS = %i{
405
452
  from
406
453
  to
@@ -460,10 +507,12 @@ module Cronofy
460
507
  end
461
508
  end
462
509
 
463
- class ReadEventsIterator
510
+ class PagedResultIterator
464
511
  include Enumerable
465
512
 
466
- def initialize(access_token, url, params)
513
+ def initialize(page_parser, items_key, access_token, url, params)
514
+ @page_parser = page_parser
515
+ @items_key = items_key
467
516
  @access_token = access_token
468
517
  @url = url
469
518
  @params = params
@@ -473,15 +522,15 @@ module Cronofy
473
522
  def each
474
523
  page = @first_page
475
524
 
476
- page.events.each do |event|
477
- yield event
525
+ page[@items_key].each do |item|
526
+ yield item
478
527
  end
479
528
 
480
529
  while page.pages.next_page?
481
530
  page = get_page(page.pages.next_page)
482
531
 
483
- page.events.each do |event|
484
- yield event
532
+ page[@items_key].each do |item|
533
+ yield item
485
534
  end
486
535
  end
487
536
  end
@@ -511,7 +560,7 @@ module Cronofy
511
560
  end
512
561
 
513
562
  def parse_page(response)
514
- ResponseParser.new(response).parse_json(PagedEventsResult)
563
+ ResponseParser.new(response).parse_json(@page_parser)
515
564
  end
516
565
  end
517
566
  end
@@ -197,6 +197,25 @@ module Cronofy
197
197
  coerce_key :events, Events
198
198
  end
199
199
 
200
+ class FreeBusy < Hashie::Mash
201
+ include Hashie::Extensions::Coercion
202
+
203
+ coerce_key :start, EventTime
204
+ coerce_key :end, EventTime
205
+ end
206
+
207
+ module FreeBusyEnumerable
208
+ def self.coerce(values)
209
+ values.map { |v| FreeBusy.new(v) }
210
+ end
211
+ end
212
+
213
+ class PagedFreeBusyResult < Hashie::Mash
214
+ include Hashie::Extensions::Coercion
215
+
216
+ coerce_key :free_busy, FreeBusyEnumerable
217
+ end
218
+
200
219
  class Profile < Hashie::Mash
201
220
  end
202
221
  end
@@ -1,3 +1,3 @@
1
1
  module Cronofy
2
- VERSION = "0.5.3".freeze
2
+ VERSION = "0.6.0".freeze
3
3
  end
@@ -606,4 +606,192 @@ describe Cronofy::Client do
606
606
  it_behaves_like 'a Cronofy request with mapped return value'
607
607
  end
608
608
  end
609
+
610
+ describe 'Free busy' do
611
+ describe '#free_busy' do
612
+ before do
613
+ stub_request(method, request_url)
614
+ .with(headers: request_headers,
615
+ body: request_body)
616
+ .to_return(status: correct_response_code,
617
+ headers: correct_response_headers,
618
+ body: correct_response_body.to_json)
619
+
620
+ stub_request(:get, next_page_url)
621
+ .with(headers: request_headers)
622
+ .to_return(status: correct_response_code,
623
+ headers: correct_response_headers,
624
+ body: next_page_body.to_json)
625
+ end
626
+
627
+
628
+ let(:request_url_prefix) { 'https://api.cronofy.com/v1/free_busy' }
629
+ let(:method) { :get }
630
+ let(:correct_response_code) { 200 }
631
+ let(:next_page_url) do
632
+ "https://next.page.com/08a07b034306679e"
633
+ end
634
+
635
+ let(:params) { Hash.new }
636
+ let(:request_url) { request_url_prefix + "?tzid=Etc/UTC" }
637
+
638
+ let(:correct_response_body) do
639
+ {
640
+ 'pages' => {
641
+ 'current' => 1,
642
+ 'total' => 2,
643
+ 'next_page' => next_page_url
644
+ },
645
+ 'free_busy' => [
646
+ {
647
+ 'calendar_id' => 'cal_U9uuErStTG@EAAAB_IsAsykA2DBTWqQTf-f0kJw',
648
+ 'start' => '2014-09-06',
649
+ 'end' => '2014-09-08',
650
+ 'free_busy_status' => 'busy',
651
+ },
652
+ {
653
+ 'calendar_id' => 'cal_U9uuErStTG@EAAAB_IsAsykA2DBTWqQTf-f0kJw',
654
+ 'start' => '2014-09-13T19:00:00Z',
655
+ 'end' => '2014-09-13T21:00:00Z',
656
+ 'free_busy_status' => 'tentative',
657
+ }
658
+ ]
659
+ }
660
+ end
661
+
662
+ let(:next_page_body) do
663
+ {
664
+ 'pages' => {
665
+ 'current' => 2,
666
+ 'total' => 2,
667
+ },
668
+ 'free_busy' => [
669
+ {
670
+ 'calendar_id' => 'cal_U9uuErStTG@EAAAB_IsAsykA2DBTWqQTf-f0kJw',
671
+ 'start' => '2014-09-07',
672
+ 'end' => '2014-09-09',
673
+ 'free_busy_status' => 'busy',
674
+ },
675
+ {
676
+ 'calendar_id' => 'cal_U9uuErStTG@EAAAB_IsAsykA2DBTWqQTf-f0kJw',
677
+ 'start' => '2014-09-14T19:00:00Z',
678
+ 'end' => '2014-09-14T21:00:00Z',
679
+ 'free_busy_status' => 'tentative',
680
+ }
681
+ ]
682
+ }
683
+ end
684
+
685
+ let(:correct_mapped_result) do
686
+ first_page_items = correct_response_body['free_busy'].map { |period| Cronofy::FreeBusy.new(period) }
687
+ second_page_items = next_page_body['free_busy'].map { |period| Cronofy::FreeBusy.new(period) }
688
+
689
+ first_page_items + second_page_items
690
+ end
691
+
692
+ subject do
693
+ # By default force evaluation
694
+ client.free_busy(params).to_a
695
+ end
696
+
697
+ context 'when all params are passed' do
698
+ let(:params) do
699
+ {
700
+ from: Time.new(2014, 9, 1, 0, 0, 1, '+00:00'),
701
+ to: Time.new(2014, 10, 1, 0, 0, 1, '+00:00'),
702
+ tzid: 'Etc/UTC',
703
+ include_managed: true,
704
+ }
705
+ end
706
+ let(:request_url) do
707
+ "#{request_url_prefix}?from=2014-09-01T00:00:01Z" \
708
+ "&to=2014-10-01T00:00:01Z&tzid=Etc/UTC&include_managed=true"
709
+ end
710
+
711
+ it_behaves_like 'a Cronofy request'
712
+ it_behaves_like 'a Cronofy request with mapped return value'
713
+ end
714
+
715
+ context 'when some params are passed' do
716
+ let(:params) do
717
+ {
718
+ from: Time.new(2014, 9, 1, 0, 0, 1, '+00:00'),
719
+ }
720
+ end
721
+ let(:request_url) do
722
+ "#{request_url_prefix}?from=2014-09-01T00:00:01Z" \
723
+ "&tzid=Etc/UTC"
724
+ end
725
+
726
+ it_behaves_like 'a Cronofy request'
727
+ it_behaves_like 'a Cronofy request with mapped return value'
728
+ end
729
+
730
+ context "when unknown flags are passed" do
731
+ let(:params) do
732
+ {
733
+ unknown_bool: true,
734
+ unknown_number: 5,
735
+ unknown_string: "foo-bar-baz",
736
+ }
737
+ end
738
+
739
+ let(:request_url) do
740
+ "#{request_url_prefix}?tzid=Etc/UTC" \
741
+ "&unknown_bool=true" \
742
+ "&unknown_number=5" \
743
+ "&unknown_string=foo-bar-baz"
744
+ end
745
+
746
+ it_behaves_like 'a Cronofy request'
747
+ it_behaves_like 'a Cronofy request with mapped return value'
748
+ end
749
+
750
+ context "next page not found" do
751
+ before do
752
+ stub_request(:get, next_page_url)
753
+ .with(headers: request_headers)
754
+ .to_return(status: 404,
755
+ headers: correct_response_headers)
756
+ end
757
+
758
+ it "raises an error" do
759
+ expect{ subject }.to raise_error(::Cronofy::NotFoundError)
760
+ end
761
+ end
762
+
763
+ context "only first period" do
764
+ before do
765
+ # Ensure an error if second page is requested
766
+ stub_request(:get, next_page_url)
767
+ .with(headers: request_headers)
768
+ .to_return(status: 404,
769
+ headers: correct_response_headers)
770
+ end
771
+
772
+ let(:first_period) do
773
+ Cronofy::FreeBusy.new(correct_response_body["free_busy"].first)
774
+ end
775
+
776
+ subject do
777
+ client.free_busy(params).first
778
+ end
779
+
780
+ it "returns the first period from the first page" do
781
+ expect(subject).to eq(first_period)
782
+ end
783
+ end
784
+
785
+ context "without calling #to_a to force full evaluation" do
786
+ subject { client.free_busy(params) }
787
+
788
+ it_behaves_like 'a Cronofy request'
789
+
790
+ # We expect it to behave like a Cronofy request as the first page is
791
+ # requested eagerly so that the majority of errors will happen inline
792
+ # rather than lazily happening wherever the iterator may have been
793
+ # passed.
794
+ end
795
+ end
796
+ end
609
797
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cronofy
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.3
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sergii Paryzhskyi
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2015-09-09 00:00:00.000000000 Z
12
+ date: 2015-10-09 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: oauth2