cronofy 0.5.3 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
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