gcal-unit 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +11 -0
- data/README.md +134 -0
- data/Rakefile +131 -0
- data/gcal-unit.gemspec +57 -0
- data/lib/gcal_unit/authorization.rb +25 -0
- data/lib/gcal_unit/calendar.rb +14 -0
- data/lib/gcal_unit/calendar_list.rb +13 -0
- data/lib/gcal_unit/client.rb +135 -0
- data/lib/gcal_unit/configuration.rb +10 -0
- data/lib/gcal_unit/errors.rb +7 -0
- data/lib/gcal_unit/event.rb +97 -0
- data/lib/gcal_unit.rb +39 -0
- data/spec/lib/gcal_unit/authorization_spec.rb +12 -0
- data/spec/lib/gcal_unit/calendar_list_spec.rb +21 -0
- data/spec/lib/gcal_unit/calendar_spec.rb +21 -0
- data/spec/lib/gcal_unit/configuration_spec.rb +37 -0
- data/spec/lib/gcal_unit/event_spec.rb +138 -0
- data/spec/spec_helper.rb +35 -0
- metadata +80 -0
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,134 @@
|
|
1
|
+
G-G-G-GCal-Unit!
|
2
|
+
==========================
|
3
|
+
***
|
4
|
+
|
5
|
+
![GCal Unit](https://s3.amazonaws.com/github-gcal-unit/gcal-unit.png)
|
6
|
+
|
7
|
+
## GANGSTA SH*T
|
8
|
+
|
9
|
+
GCal-Unit is a bullet proof vest for the Google Calendar API. It will pimp the hell out of your applications. This rapper is used only for Google Calendar API calls and not any other Google APIs (except for making those Google tokens fresh).
|
10
|
+
|
11
|
+
## LOADING THE CLIP
|
12
|
+
|
13
|
+
To start caping fools, you have to install this sh*t.
|
14
|
+
|
15
|
+
```
|
16
|
+
gem install gcal-unit
|
17
|
+
```
|
18
|
+
|
19
|
+
This will also load [httparty](http://github.com/jnunemaker/httparty) in your pimp cup.
|
20
|
+
|
21
|
+
## CANDY SHOP
|
22
|
+
|
23
|
+
I'll break it down for you, baby it's simple.
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
#token is the user's Google API access token, yo
|
27
|
+
|
28
|
+
# Get a list of a your Google calendars
|
29
|
+
list = Google::CalendarList.new(token).list
|
30
|
+
|
31
|
+
# Get the user's primary Google calendar
|
32
|
+
primary = Google::Calendar.new(token).get("primary")
|
33
|
+
|
34
|
+
# Get a list of events from a calendar
|
35
|
+
events = Google::Event.new(token).nested_for('primary').list
|
36
|
+
|
37
|
+
# Get a single event from a calendar
|
38
|
+
event = Google::Event.new(token).nested_for('primary').get(event_id)
|
39
|
+
|
40
|
+
# Insert an event for a calendar
|
41
|
+
event = Google::Event.new(token).nested_for('primary').insert({
|
42
|
+
end: { date: { '1975-07-06' } },
|
43
|
+
start: { date: { '1975-07-06' } },
|
44
|
+
description: { string: { 'I was born.' } }
|
45
|
+
})
|
46
|
+
|
47
|
+
# Update an event for a calendar
|
48
|
+
event = Google::Event.new(token).nested_for('primary').update(event_id, {
|
49
|
+
end: { date: { 'I was born with a pimp cup.' } }
|
50
|
+
})
|
51
|
+
|
52
|
+
# Delete an event
|
53
|
+
Google::Event.new(token).nested_for('primary').delete(event_id)
|
54
|
+
|
55
|
+
# Use the quick add feature
|
56
|
+
event = Google::Event.new(token).nested_for('primary').quick_add('got shot 9 times yesterday at 2am')
|
57
|
+
```
|
58
|
+
|
59
|
+
The base URI for all of the API operations is: [http://www.googleapis.com/calendar/v3](http://www.googleapis.com/calendar/v3)
|
60
|
+
|
61
|
+
If you have an interest in reading other things than c-notes, you can find the Google API Calendar documentation at [http://developers.google.com/google-apps/calendar/v3/reference](http://developers.google.com/google-apps/calendar/v3/reference)
|
62
|
+
|
63
|
+
Each Google Calendar API model requires an API token. This token (as well as a refresh token) can be retrieved by using OAuth (we recommend [OmniAuth](https://github.com/intridea/omniauth)). If the token expires, you can use the authentication method in this library and your refresh token to refresh the API token for future calls.
|
64
|
+
|
65
|
+
```ruby
|
66
|
+
# refresh_token is your Google API refresh token for your registered application
|
67
|
+
|
68
|
+
client = Google::Authorization.new(refresh_token)
|
69
|
+
token = client.refresh()['access_token'] # use this token for all the api calls
|
70
|
+
```
|
71
|
+
|
72
|
+
## WANKSTA
|
73
|
+
|
74
|
+
Configure the Google API wrapper with the sensitive data needed to make Google API calls.
|
75
|
+
|
76
|
+
```ruby
|
77
|
+
Google.configure do |config|
|
78
|
+
config.api_key = ENV['GOOGLE_API_KEY']
|
79
|
+
config.client_id = ENV['GOOGLE_CLIENT_ID']
|
80
|
+
config.client_secret = ENV['GOOGLE_CLIENT_SECRET']
|
81
|
+
# You can also set the refresh token here as well for later retreival but
|
82
|
+
# refresh tokens are not app specific but user specific, yo
|
83
|
+
end
|
84
|
+
```
|
85
|
+
|
86
|
+
## KNOW YOUR HOOD
|
87
|
+
|
88
|
+
When contributing use [dotenv](https://github.com/bkeepers/dotenv) to hold your sensitive data. The gem contains an example `.env` file. The `.env` file is in the .gitignore so your sensitive information will not be exposed.
|
89
|
+
|
90
|
+
## IN DA CLUB
|
91
|
+
|
92
|
+
To run the tests, there is some configuration that probably needs to occur.
|
93
|
+
The test suite uses VCR to make the initial calls to Google and then records the response.
|
94
|
+
In order to make that initial call to Google work, we included a simple Sinatra application that you can
|
95
|
+
run to record the correct refresh token for authentication.
|
96
|
+
|
97
|
+
Here are the steps to make yo sh*t tight.
|
98
|
+
|
99
|
+
1. visit https://developers.google.com/google-apps/calendar/
|
100
|
+
1. sign in (upper right) with a Google account you want to use for testing
|
101
|
+
1. visit https://code.google.com/apis/console
|
102
|
+
1. create a project
|
103
|
+
1. turn on calendar api
|
104
|
+
1. visit api access
|
105
|
+
1. create an oauth 2.0 client id
|
106
|
+
1. choose web application
|
107
|
+
1. choose http://localhost for hostname
|
108
|
+
1. click on create client id
|
109
|
+
1. edit settings and change redirect uri to http://localhost:3000/auth/google_oauth2/callback
|
110
|
+
1. cp example.env .env
|
111
|
+
1. change the GOOGLE_CLIENT_ID key to your client id in the .env file
|
112
|
+
1. change the GOOGLE_CLIENT_SECRET key to your client secret in the .env file
|
113
|
+
1. change the GOOGLE_API_KEY key to your API key in the .env file
|
114
|
+
1. change the GOOGLE_TEST_USER key to who you are going to sign in as below when you run the Sinatra app
|
115
|
+
1. bundle install --gemfile=bootstrapper/Gemfile
|
116
|
+
1. rackup bootstrapper/config.ru -p 3000
|
117
|
+
1. open your browser to http://localhost:3000 and authenticate with Google
|
118
|
+
1. the GOOGLE_REFRESH_TOKEN will be added to your .env file
|
119
|
+
1. add the user email that you signed in with to the .env file for the GOOGLE_TEST_USER key
|
120
|
+
|
121
|
+
(Welcome to OAuth. So many steps, my grill is tarnishing)
|
122
|
+
|
123
|
+
## GET RICH OR DIE TRYIN'
|
124
|
+
|
125
|
+
To contribute, just take a hit and do the rest.
|
126
|
+
|
127
|
+
* Fork the project
|
128
|
+
* Create a test that indicates your behavior
|
129
|
+
* If you are using an http call, wrap your call with VCR
|
130
|
+
* Code your feature or fix the bug
|
131
|
+
* Commit (do not bump the version)
|
132
|
+
* Send pull request
|
133
|
+
|
134
|
+
Released under the [MIT license](http://www.opensource.org/licenses/mit-license.php).
|
data/Rakefile
ADDED
@@ -0,0 +1,131 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'date'
|
4
|
+
|
5
|
+
#############################################################################
|
6
|
+
#
|
7
|
+
# Helper functions
|
8
|
+
#
|
9
|
+
#############################################################################
|
10
|
+
|
11
|
+
def name
|
12
|
+
@name ||= Dir['*.gemspec'].first.split('.').first
|
13
|
+
end
|
14
|
+
|
15
|
+
def dir_name
|
16
|
+
name.gsub('-', '_')
|
17
|
+
end
|
18
|
+
|
19
|
+
def version
|
20
|
+
line = File.read("lib/#{dir_name}.rb")[/^\s*VERSION\s*=\s*.*/]
|
21
|
+
line.match(/.*VERSION\s*=\s*['"](.*)['"]/)[1]
|
22
|
+
end
|
23
|
+
|
24
|
+
def date
|
25
|
+
Date.today.to_s
|
26
|
+
end
|
27
|
+
|
28
|
+
def rubyforge_project
|
29
|
+
name
|
30
|
+
end
|
31
|
+
|
32
|
+
def gemspec_file
|
33
|
+
"#{name}.gemspec"
|
34
|
+
end
|
35
|
+
|
36
|
+
def gem_file
|
37
|
+
"#{name}-#{version}.gem"
|
38
|
+
end
|
39
|
+
|
40
|
+
def replace_header(head, header_name)
|
41
|
+
head.sub!(/(\.#{header_name}\s*= ').*'/) { "#{$1}#{send(header_name)}'"}
|
42
|
+
end
|
43
|
+
|
44
|
+
#############################################################################
|
45
|
+
#
|
46
|
+
# Standard tasks
|
47
|
+
#
|
48
|
+
#############################################################################
|
49
|
+
|
50
|
+
task :default => :validate
|
51
|
+
|
52
|
+
desc "Open an irb session preloaded with this library"
|
53
|
+
task :console do
|
54
|
+
sh "irb -rubygems -r ./lib/#{dir_name}.rb"
|
55
|
+
end
|
56
|
+
|
57
|
+
#############################################################################
|
58
|
+
#
|
59
|
+
# Custom tasks (add your own tasks here)
|
60
|
+
#
|
61
|
+
#############################################################################
|
62
|
+
|
63
|
+
|
64
|
+
|
65
|
+
#############################################################################
|
66
|
+
#
|
67
|
+
# Packaging tasks
|
68
|
+
#
|
69
|
+
#############################################################################
|
70
|
+
|
71
|
+
desc "Create tag v#{version} and build and push #{gem_file} to Rubygems"
|
72
|
+
task :release => :build do
|
73
|
+
unless `git branch` =~ /^\* master$/
|
74
|
+
puts "You must be on the master branch to release!"
|
75
|
+
exit!
|
76
|
+
end
|
77
|
+
sh "git commit --allow-empty -a -m 'Release #{version}'"
|
78
|
+
sh "git tag v#{version}"
|
79
|
+
sh "git push bf master"
|
80
|
+
sh "git push bf v#{version}"
|
81
|
+
sh "gem push pkg/#{name}-#{version}.gem"
|
82
|
+
end
|
83
|
+
|
84
|
+
desc "Build #{gem_file} into the pkg directory"
|
85
|
+
task :build => :gemspec do
|
86
|
+
sh "mkdir -p pkg"
|
87
|
+
sh "gem build #{gemspec_file}"
|
88
|
+
sh "mv #{gem_file} pkg"
|
89
|
+
end
|
90
|
+
|
91
|
+
desc "Generate #{gemspec_file}"
|
92
|
+
task :gemspec => :validate do
|
93
|
+
# read spec file and split out manifest section
|
94
|
+
spec = File.read(gemspec_file)
|
95
|
+
head, manifest, tail = spec.split(" # = MANIFEST =\n")
|
96
|
+
|
97
|
+
# replace name version and date
|
98
|
+
replace_header(head, :name)
|
99
|
+
replace_header(head, :version)
|
100
|
+
replace_header(head, :date)
|
101
|
+
#comment this out if your rubyforge_project has a different name
|
102
|
+
replace_header(head, :rubyforge_project)
|
103
|
+
|
104
|
+
# determine file list from git ls-files
|
105
|
+
files = `git ls-files`.
|
106
|
+
split("\n").
|
107
|
+
sort.
|
108
|
+
reject { |file| file =~ /^\./ }.
|
109
|
+
reject { |file| file =~ /^(rdoc|pkg)/ }.
|
110
|
+
map { |file| " #{file}" }.
|
111
|
+
join("\n")
|
112
|
+
|
113
|
+
# piece file back together and write
|
114
|
+
manifest = " s.files = %w[\n#{files}\n ]\n"
|
115
|
+
spec = [head, manifest, tail].join(" # = MANIFEST =\n")
|
116
|
+
File.open(gemspec_file, 'w') { |io| io.write(spec) }
|
117
|
+
puts "Updated #{gemspec_file}"
|
118
|
+
end
|
119
|
+
|
120
|
+
desc "Validate #{gemspec_file}"
|
121
|
+
task :validate do
|
122
|
+
libfiles = Dir['lib/*'] - ["lib/#{dir_name}.rb", "lib/#{dir_name}"]
|
123
|
+
unless libfiles.empty?
|
124
|
+
puts "Directory `lib` should only contain a `#{dir_name}.rb` file and `#{dir_name}` dir."
|
125
|
+
exit!
|
126
|
+
end
|
127
|
+
unless Dir['VERSION*'].empty?
|
128
|
+
puts "A `VERSION` file at root level violates Gem best practices."
|
129
|
+
exit!
|
130
|
+
end
|
131
|
+
end
|
data/gcal-unit.gemspec
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
|
2
|
+
Gem::Specification.new do |s|
|
3
|
+
s.specification_version = 2 if s.respond_to? :specification_version=
|
4
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
5
|
+
s.rubygems_version = '1.3.5'
|
6
|
+
|
7
|
+
s.name = 'gcal-unit'
|
8
|
+
s.version = '1.0.0'
|
9
|
+
s.date = '2012-08-30'
|
10
|
+
s.rubyforge_project = 'gcal-unit'
|
11
|
+
|
12
|
+
s.summary = "Pimp ass API wrapper for the Google Calendar API."
|
13
|
+
s.description = "Wraps HTTP calls for the Google Calendar API."
|
14
|
+
|
15
|
+
s.authors = ["Brilliant Fantastic"]
|
16
|
+
s.email = 'support@brilliantfantastic.com'
|
17
|
+
s.homepage = 'http://github.com/brilliantfantastic/gcal-unit'
|
18
|
+
|
19
|
+
s.require_paths = %w[lib]
|
20
|
+
s.executables = []
|
21
|
+
|
22
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
23
|
+
s.extra_rdoc_files = %w[README.md]
|
24
|
+
|
25
|
+
s.add_dependency('httparty', "~> 0.7.8")
|
26
|
+
|
27
|
+
## Leave this section as-is. It will be automatically generated from the
|
28
|
+
## contents of your Git repository via the gemspec task. DO NOT REMOVE
|
29
|
+
## THE MANIFEST COMMENTS, they are used as delimiters by the task.
|
30
|
+
# = MANIFEST =
|
31
|
+
s.files = %w[
|
32
|
+
Gemfile
|
33
|
+
README.md
|
34
|
+
Rakefile
|
35
|
+
bootstrapper/Gemfile
|
36
|
+
bootstrapper/config.ru
|
37
|
+
example.env
|
38
|
+
gcal-unit.gemspec
|
39
|
+
lib/gcal_unit.rb
|
40
|
+
lib/gcal_unit/authorization.rb
|
41
|
+
lib/gcal_unit/calendar.rb
|
42
|
+
lib/gcal_unit/calendar_list.rb
|
43
|
+
lib/gcal_unit/client.rb
|
44
|
+
lib/gcal_unit/configuration.rb
|
45
|
+
lib/gcal_unit/errors.rb
|
46
|
+
lib/gcal_unit/event.rb
|
47
|
+
spec/lib/gcal_unit/authorization_spec.rb
|
48
|
+
spec/lib/gcal_unit/calendar_list_spec.rb
|
49
|
+
spec/lib/gcal_unit/calendar_spec.rb
|
50
|
+
spec/lib/gcal_unit/configuration_spec.rb
|
51
|
+
spec/lib/gcal_unit/event_spec.rb
|
52
|
+
spec/spec_helper.rb
|
53
|
+
]
|
54
|
+
# = MANIFEST =
|
55
|
+
|
56
|
+
s.files.reject! { |f| f.include?("bootstrapper") || f.include?("example.env") }
|
57
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Google
|
2
|
+
# Public: API methods to authorize a specific user for
|
3
|
+
# the API calls.
|
4
|
+
class Authorization < Client
|
5
|
+
base_uri 'https://accounts.google.com'
|
6
|
+
|
7
|
+
# Public: Returns a new access token for a specific refresh token.
|
8
|
+
#
|
9
|
+
# Returns a hash of metadata for refreshed user access.
|
10
|
+
def refresh
|
11
|
+
options = {
|
12
|
+
:body => {
|
13
|
+
:client_id => Google.configuration.client_id,
|
14
|
+
:client_secret => Google.configuration.client_secret,
|
15
|
+
:refresh_token => @token,
|
16
|
+
:grant_type => 'refresh_token'
|
17
|
+
},
|
18
|
+
:headers => {
|
19
|
+
'Content-Type' => 'application/x-www-form-urlencoded'
|
20
|
+
}
|
21
|
+
}
|
22
|
+
http_post "/o/oauth2/token", options
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Google
|
2
|
+
# Public: API methods for a singular calendar.
|
3
|
+
class Calendar < Client
|
4
|
+
# Public: Gets a single calendar.
|
5
|
+
#
|
6
|
+
# id - The id of the calendar to retrieve.
|
7
|
+
#
|
8
|
+
# Returns a hash of metadata for a calendar.
|
9
|
+
def get(id)
|
10
|
+
response = http_get "/calendars/#{id}"
|
11
|
+
JSON.parse(response.body)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Google
|
2
|
+
# Public: API methods for a list of calendars.
|
3
|
+
class CalendarList < Client
|
4
|
+
# Public: Gets a list of calendars for the user specified
|
5
|
+
# with the provided token.
|
6
|
+
#
|
7
|
+
# Returns a hash of metadata for a calendar list.
|
8
|
+
def list
|
9
|
+
response = http_get '/users/me/calendarList'
|
10
|
+
JSON.parse response.body
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
module Google
|
2
|
+
# Public : Performs http calls to the Google APIs.
|
3
|
+
class Client
|
4
|
+
include HTTParty
|
5
|
+
base_uri 'https://www.googleapis.com/calendar/v3'
|
6
|
+
|
7
|
+
# Public: Initializes a new instance of a client.
|
8
|
+
#
|
9
|
+
# token - The user token to make the call.
|
10
|
+
def initialize(token)
|
11
|
+
@token = token
|
12
|
+
end
|
13
|
+
|
14
|
+
# Public: Performs a GET request for an http call with the correct queries
|
15
|
+
# and headers applied.
|
16
|
+
#
|
17
|
+
# path - the url for the GET request
|
18
|
+
# options - additional options for the GET request
|
19
|
+
# block - called after the GET request is complete
|
20
|
+
#
|
21
|
+
# Raises Google::AuthenticationRequiredError if the token is no longer valid
|
22
|
+
#
|
23
|
+
# Returns the response
|
24
|
+
def http_get(path, options={}, &block)
|
25
|
+
apply_api_key options # apply the API key if it is not there
|
26
|
+
apply_auth_header options # apply the authorization header if it is not there
|
27
|
+
|
28
|
+
response = self.class.get path, options, &block
|
29
|
+
raise AuthenticationRequiredError.new if response.code == 401
|
30
|
+
response
|
31
|
+
end
|
32
|
+
|
33
|
+
# Public: Performs a POST request for an http call with the correct queries
|
34
|
+
# and headers applied.
|
35
|
+
#
|
36
|
+
# path - the url for the POST request
|
37
|
+
# options - additional options for the POST request
|
38
|
+
# block - called after the POST request is complete
|
39
|
+
#
|
40
|
+
# Raises Google::AuthenticationRequiredError if the token is no longer valid
|
41
|
+
#
|
42
|
+
# Returns the response
|
43
|
+
def http_post(path, options={}, &block)
|
44
|
+
apply_api_key options # apply the API key if it is not there
|
45
|
+
apply_auth_header options # apply the authorization header if it is not there
|
46
|
+
apply_content_type_header options
|
47
|
+
apply_content_length_header options
|
48
|
+
|
49
|
+
response = self.class.post path, options, &block
|
50
|
+
raise AuthenticationRequiredError.new if response.code == 401
|
51
|
+
response
|
52
|
+
end
|
53
|
+
|
54
|
+
# Public: Performs a PUT request for an http call with the correct queries and
|
55
|
+
# headers applied.
|
56
|
+
#
|
57
|
+
# path - the url for the PUT request
|
58
|
+
# options - additional options for the PUT request
|
59
|
+
# block - called after the PUT request is complete
|
60
|
+
#
|
61
|
+
# Raises Google::AuthenticationRequiredError if the token is no longer valid
|
62
|
+
#
|
63
|
+
# Returns the response
|
64
|
+
def http_put(path, options={}, &block)
|
65
|
+
apply_api_key options # apply the API key if it is not there
|
66
|
+
apply_auth_header options # apply the authorization header if it is not there
|
67
|
+
apply_content_type_header options
|
68
|
+
|
69
|
+
response = self.class.put path, options, &block
|
70
|
+
raise AuthenticationRequiredError.new if response.code == 401
|
71
|
+
response
|
72
|
+
end
|
73
|
+
|
74
|
+
# Public: Performs a DELETE request for an http call with the correct queries and
|
75
|
+
# headers applied.
|
76
|
+
#
|
77
|
+
# path - the url for a DELETE request
|
78
|
+
# options - additional options for the DELETE request
|
79
|
+
# block - called after the DELETE request is complete
|
80
|
+
#
|
81
|
+
# Raises Google::AuthenticationRequiredError if the token is no longer valid
|
82
|
+
#
|
83
|
+
# Returns the response
|
84
|
+
def http_delete(path, options={}, &block)
|
85
|
+
apply_api_key options # apply the API key if it is not there
|
86
|
+
apply_auth_header options # apply the authorization header if it is not there
|
87
|
+
|
88
|
+
response = self.class.delete path, options, &block
|
89
|
+
raise AuthenticationRequiredError.new if response.code == 401
|
90
|
+
response
|
91
|
+
end
|
92
|
+
|
93
|
+
protected
|
94
|
+
# Protected: Adds the api key value to the query options
|
95
|
+
#
|
96
|
+
# Returns nothing
|
97
|
+
def apply_api_key(options)
|
98
|
+
options.merge! :query => {} unless options.has_key? :query
|
99
|
+
options[:query].merge!({ :key => Google.configuration.api_key })
|
100
|
+
end
|
101
|
+
|
102
|
+
# Protected: Adds the authorization token to the header
|
103
|
+
#
|
104
|
+
# Returns nothing
|
105
|
+
def apply_auth_header(options)
|
106
|
+
apply_header options, "Authorization", "Bearer #{@token}"
|
107
|
+
end
|
108
|
+
|
109
|
+
# Protected: Adds a default of application/json to the Content-Type unless
|
110
|
+
# one is already specified.
|
111
|
+
#
|
112
|
+
# Returns nothing
|
113
|
+
def apply_content_type_header(options)
|
114
|
+
if !options.has_key?(:headers) || !options[:headers].has_key?("Content-Type")
|
115
|
+
apply_header options, "Content-Type", "application/json"
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# Protected: Applies the Content-Length of the body
|
120
|
+
#
|
121
|
+
# Returns nothing
|
122
|
+
def apply_content_length_header(options)
|
123
|
+
options[:body].nil? ? length = 0 : options[:body].length
|
124
|
+
apply_header options, "Content-Length", length.to_s
|
125
|
+
end
|
126
|
+
|
127
|
+
# Protected: Applies the specified key and value combination to the header
|
128
|
+
#
|
129
|
+
# Returns nothing
|
130
|
+
def apply_header(options, key, value)
|
131
|
+
options.merge! :headers => {} unless options.has_key? :headers
|
132
|
+
options[:headers].merge!({ key => value })
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
module Google
|
2
|
+
# Public: API methods for a calendar events.
|
3
|
+
class Event < Client
|
4
|
+
# Public: Saves a calendar id for methods that require a
|
5
|
+
# calendar for the API methods.
|
6
|
+
#
|
7
|
+
# calendar_id - Identifier for a specific calendar.
|
8
|
+
#
|
9
|
+
# Returns the event object for chaining
|
10
|
+
def nested_for(calendar_id)
|
11
|
+
@calendar_id = calendar_id
|
12
|
+
self
|
13
|
+
end
|
14
|
+
|
15
|
+
# Public: Gets a list of events for a specific calendar.
|
16
|
+
#
|
17
|
+
# Raises a Google::CalendarRequiredError if a calendar is not
|
18
|
+
# supplied (nested_for) was not called.
|
19
|
+
#
|
20
|
+
# Returns a hash of metadata for the calendars events.
|
21
|
+
def list
|
22
|
+
raise Google::CalendarRequiredError if @calendar_id.nil?
|
23
|
+
response = http_get "/calendars/#{@calendar_id}/events"
|
24
|
+
JSON.parse(response.body)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Public: Gets a single event.
|
28
|
+
#
|
29
|
+
# id - The id of the event to retrieve.
|
30
|
+
#
|
31
|
+
# Raises a Google::CalendarRequiredError if a calendar is not
|
32
|
+
# supplied (nested_for) was not called.
|
33
|
+
#
|
34
|
+
# Returns a hash of metadata for an event.
|
35
|
+
def get(id)
|
36
|
+
raise Google::CalendarRequiredError if @calendar_id.nil?
|
37
|
+
response = http_get "/calendars/#{@calendar_id}/events/#{id}"
|
38
|
+
JSON.parse(response.body)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Public: Inserts a new single event.
|
42
|
+
#
|
43
|
+
# event - A hash containing the data for an event to add.
|
44
|
+
#
|
45
|
+
# Raises a Google::CalendarRequiredError if a calendar is not
|
46
|
+
# supplied (nested_for) was not called.
|
47
|
+
#
|
48
|
+
# Returns a hash of metadata for the event that was added.
|
49
|
+
def insert(event)
|
50
|
+
raise Google::CalendarRequiredError if @calendar_id.nil?
|
51
|
+
response = http_post "/calendars/#{@calendar_id}/events", :body => JSON.dump(event)
|
52
|
+
JSON.parse(response.body)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Public: Inserts a new single event that is parsed from a line of text.
|
56
|
+
#
|
57
|
+
# text - A sentence that is parsed into a single event.
|
58
|
+
#
|
59
|
+
# Raises a Google::CalendarRequiredError if a calendar is not
|
60
|
+
# supplied (nested_for) was not called.
|
61
|
+
#
|
62
|
+
# Returns a hash of metadata for the event that was added.
|
63
|
+
def quick_add(text)
|
64
|
+
raise Google::CalendarRequiredError if @calendar_id.nil?
|
65
|
+
response = http_post "/calendars/#{@calendar_id}/events/quickAdd", :query => { :text => text }
|
66
|
+
JSON.parse(response.body)
|
67
|
+
end
|
68
|
+
|
69
|
+
# Public: Updates a single event.
|
70
|
+
#
|
71
|
+
# id - The id of the event to update.
|
72
|
+
# event - A hash containing the data for the event to update.
|
73
|
+
#
|
74
|
+
# Raises a Google::CalendarRequiredError if a calendar is not
|
75
|
+
# supplied (nested_for) was not called.
|
76
|
+
#
|
77
|
+
# Returns a hash of metadata for the event that was updated.
|
78
|
+
def update(id, event)
|
79
|
+
raise Google::CalendarRequiredError if @calendar_id.nil?
|
80
|
+
response = http_put "/calendars/#{@calendar_id}/events/#{id}", :body => JSON.dump(event)
|
81
|
+
JSON.parse(response.body)
|
82
|
+
end
|
83
|
+
|
84
|
+
# Public: Deletes a single event.
|
85
|
+
#
|
86
|
+
# id - The id of the event to delete.
|
87
|
+
#
|
88
|
+
# Raises a Google::CalendarRequiredError if a calendar is not
|
89
|
+
# supplied (nested_for) was not called.
|
90
|
+
#
|
91
|
+
# Returns the response from the delete operation.
|
92
|
+
def delete(id)
|
93
|
+
raise Google::CalendarRequiredError if @calendar_id.nil?
|
94
|
+
http_delete "/calendars/#{@calendar_id}/events/#{id}"
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
data/lib/gcal_unit.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'httparty'
|
2
|
+
require 'json'
|
3
|
+
require 'open-uri'
|
4
|
+
|
5
|
+
require 'gcal_unit/client'
|
6
|
+
require 'gcal_unit/calendar_list'
|
7
|
+
require 'gcal_unit/calendar'
|
8
|
+
require 'gcal_unit/event'
|
9
|
+
require 'gcal_unit/errors'
|
10
|
+
require 'gcal_unit/authorization'
|
11
|
+
require 'gcal_unit/configuration'
|
12
|
+
|
13
|
+
# Public: Contains all the methods for wrapping the Google API.
|
14
|
+
module Google
|
15
|
+
# Public: Holds the configuration for calling methods within
|
16
|
+
# the Google API.
|
17
|
+
#
|
18
|
+
# Returns a hash used for configuring the calls.
|
19
|
+
# :api_key - Identifies the application requesting the call.
|
20
|
+
# Sent with all the API calls.
|
21
|
+
# :client_id - The OAuth client id that is used to generate
|
22
|
+
# access tokens.
|
23
|
+
# :client_secret - The OAuth client secret that is used to
|
24
|
+
# generate access tokens.
|
25
|
+
# :refresh_token - A token that is used to retrieve an access
|
26
|
+
# token when the original token expires.
|
27
|
+
def self.configuration
|
28
|
+
@configuration ||= Google::Configuration.new
|
29
|
+
end
|
30
|
+
|
31
|
+
# Public: Allows configuration for the Google API.
|
32
|
+
#
|
33
|
+
# Yields the configuration for the Google API.
|
34
|
+
def self.configure
|
35
|
+
yield configuration if block_given?
|
36
|
+
end
|
37
|
+
|
38
|
+
VERSION = "1.0.0"
|
39
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Google::Authorization do
|
4
|
+
let(:client) { Google::Authorization.new(Google.configuration.refresh_token) }
|
5
|
+
|
6
|
+
describe ".refresh" do
|
7
|
+
use_vcr_cassette "authorization_success"
|
8
|
+
it "should return a new access token" do
|
9
|
+
client.refresh()['access_token'].should_not be_nil
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Google::CalendarList do
|
4
|
+
let(:client) { Google::CalendarList.new(token) }
|
5
|
+
|
6
|
+
describe ".list" do
|
7
|
+
context "without a token" do
|
8
|
+
use_vcr_cassette "authentication_needed"
|
9
|
+
it "should raise an error if authentication is needed" do
|
10
|
+
expect { Google::CalendarList.new(nil).list }.to raise_error Google::AuthenticationRequiredError
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
context "with a token" do
|
15
|
+
use_vcr_cassette "calendar_list_success"
|
16
|
+
it "should return a list of calendars for the authorized user" do
|
17
|
+
client.list["items"].count.should > 0
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Google::Calendar do
|
4
|
+
let(:client) { Google::Calendar.new(token) }
|
5
|
+
|
6
|
+
describe ".get" do
|
7
|
+
context "without a token" do
|
8
|
+
use_vcr_cassette "authorization_success"
|
9
|
+
it "should raise an error if authentication is needed" do
|
10
|
+
expect { Google::Calendar.new(nil).get('any_string') }.to raise_error Google::AuthenticationRequiredError
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
context "with a token" do
|
15
|
+
use_vcr_cassette "calendar_success"
|
16
|
+
it "should return a primary google calendar" do
|
17
|
+
client.get("primary")["id"].should == ENV['GOOGLE_TEST_USER']
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Google do
|
4
|
+
after :all do
|
5
|
+
configure
|
6
|
+
end
|
7
|
+
|
8
|
+
describe ".configure" do
|
9
|
+
it "assigns a value to the api_key" do
|
10
|
+
Google.configure do |config|
|
11
|
+
config.api_key = '1234'
|
12
|
+
end
|
13
|
+
Google.configuration.api_key.should == '1234'
|
14
|
+
end
|
15
|
+
|
16
|
+
it "assigns a value to the client_id" do
|
17
|
+
Google.configure do |config|
|
18
|
+
config.client_id = '6789'
|
19
|
+
end
|
20
|
+
Google.configuration.client_id.should == '6789'
|
21
|
+
end
|
22
|
+
|
23
|
+
it "assigns a value to the client_secret" do
|
24
|
+
Google.configure do |config|
|
25
|
+
config.client_secret = '36748'
|
26
|
+
end
|
27
|
+
Google.configuration.client_secret.should == '36748'
|
28
|
+
end
|
29
|
+
|
30
|
+
it "assigns a value to the refresh_token" do
|
31
|
+
Google.configure do |config|
|
32
|
+
config.refresh_token = '1009'
|
33
|
+
end
|
34
|
+
Google.configuration.refresh_token = '1009'
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Google::Event do
|
4
|
+
let(:client) { Google::Event.new(token) }
|
5
|
+
let(:event) do
|
6
|
+
{
|
7
|
+
end: { date: (Date.today + 1).strftime("%Y-%m-%d") },
|
8
|
+
start: { date: Date.today.strftime("%Y-%m-%d") }
|
9
|
+
}
|
10
|
+
end
|
11
|
+
|
12
|
+
describe ".update" do
|
13
|
+
it "should raise an error if the calendar id was not provided" do
|
14
|
+
expect { client.update(nil, nil) }.to raise_error Google::CalendarRequiredError
|
15
|
+
end
|
16
|
+
|
17
|
+
context "needs an existing event" do
|
18
|
+
before :all do
|
19
|
+
VCR.use_cassette "event_insert_success" do
|
20
|
+
@event = client.nested_for('primary').insert event
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
after :all do
|
25
|
+
VCR.use_cassette "event_delete_success" do
|
26
|
+
client.nested_for('primary').delete @event['id']
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
context "has a calendar" do
|
31
|
+
use_vcr_cassette "event_update_success"
|
32
|
+
|
33
|
+
it "should change the event when the data is valid" do
|
34
|
+
@event['description'] = "This has changed"
|
35
|
+
response = client.nested_for('primary').update(@event['id'], @event)
|
36
|
+
response['description'].should == "This has changed"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe ".list" do
|
43
|
+
it "should raise an error if the calendar id was not provided" do
|
44
|
+
expect { client.list }.to raise_error Google::CalendarRequiredError
|
45
|
+
end
|
46
|
+
|
47
|
+
context "without a token" do
|
48
|
+
use_vcr_cassette "authentication_needed"
|
49
|
+
it "should raise an error if authentication is needed" do
|
50
|
+
expect { Google::Event.new('').nested_for('primary').list }.to raise_error Google::AuthenticationRequiredError
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
context "with a token" do
|
55
|
+
use_vcr_cassette "event_list_success"
|
56
|
+
it "should return a list of events" do
|
57
|
+
client.nested_for('primary').list['kind'].should == 'calendar#events'
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
describe ".get" do
|
63
|
+
it "should raise an error if the calendar id was not provided" do
|
64
|
+
expect { client.get(nil) }.to raise_error Google::CalendarRequiredError
|
65
|
+
end
|
66
|
+
|
67
|
+
context "needs an existing event" do
|
68
|
+
before :all do
|
69
|
+
VCR.use_cassette "event_insert_success" do
|
70
|
+
@event = client.nested_for('primary').insert event
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
after :all do
|
75
|
+
VCR.use_cassette "event_delete_success" do
|
76
|
+
client.nested_for('primary').delete @event['id']
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
context "with a calendar" do
|
81
|
+
use_vcr_cassette "event_get_success"
|
82
|
+
it "should return a single event" do
|
83
|
+
client.nested_for('primary').get(@event['id'])['kind'].should == 'calendar#event'
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
describe ".delete" do
|
90
|
+
it "should raise an error if the calendar id was not provided" do
|
91
|
+
expect { client.delete(nil) }.to raise_error Google::CalendarRequiredError
|
92
|
+
end
|
93
|
+
|
94
|
+
context "needs an existing event" do
|
95
|
+
before :all do
|
96
|
+
VCR.use_cassette "event_insert_success" do
|
97
|
+
@event = client.nested_for('primary').insert event
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
context "has a calendar" do
|
102
|
+
use_vcr_cassette "event_delete_success"
|
103
|
+
it "should delete an event by id" do
|
104
|
+
client.nested_for('primary').delete(@event['id']).code.should == 204
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
describe ".insert" do
|
111
|
+
it "should raise an error if the calendar id was not provided" do
|
112
|
+
expect { client.insert(nil) }.to raise_error Google::CalendarRequiredError
|
113
|
+
end
|
114
|
+
|
115
|
+
context "with a calendar" do
|
116
|
+
use_vcr_cassette "event_insert_success"
|
117
|
+
it "should insert a new event when the data is valid" do
|
118
|
+
response = client.nested_for('primary').insert(event)
|
119
|
+
response['kind'].should == 'calendar#event'
|
120
|
+
client.nested_for('primary').delete response['id'] # clean up
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
describe ".quickAdd" do
|
126
|
+
use_vcr_cassette "event_quickadd_success"
|
127
|
+
|
128
|
+
it "should raise an error if the calendar id was not provided" do
|
129
|
+
expect { client.quick_add(nil) }.to raise_error Google::CalendarRequiredError
|
130
|
+
end
|
131
|
+
|
132
|
+
it "should insert a new event when the data is valid" do
|
133
|
+
response = client.nested_for('primary').quick_add("meet Jimmy today at 1pm")
|
134
|
+
response['kind'].should == 'calendar#event'
|
135
|
+
client.nested_for('primary').delete response['id'] # clean up
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require './lib/gcal_unit'
|
2
|
+
require 'dotenv'
|
3
|
+
require 'vcr'
|
4
|
+
|
5
|
+
def token
|
6
|
+
@token
|
7
|
+
end
|
8
|
+
|
9
|
+
def configure
|
10
|
+
Google.configure do |config|
|
11
|
+
Dotenv.load
|
12
|
+
config.api_key = ENV['GOOGLE_API_KEY']
|
13
|
+
config.client_id = ENV['GOOGLE_CLIENT_ID']
|
14
|
+
config.client_secret = ENV['GOOGLE_CLIENT_SECRET']
|
15
|
+
config.refresh_token = ENV['GOOGLE_REFRESH_TOKEN']
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
VCR.configure do |c|
|
20
|
+
c.cassette_library_dir = 'spec/cassettes'
|
21
|
+
c.hook_into :fakeweb
|
22
|
+
c.default_cassette_options = { :record => :new_episodes }
|
23
|
+
end
|
24
|
+
|
25
|
+
RSpec.configure do |config|
|
26
|
+
config.before(:all) do
|
27
|
+
configure
|
28
|
+
VCR.use_cassette "authorization_success" do
|
29
|
+
# Ensure a new access token before all the specs are run
|
30
|
+
client = Google::Authorization.new Google.configuration.refresh_token
|
31
|
+
@token = client.refresh()['access_token']
|
32
|
+
end
|
33
|
+
end
|
34
|
+
config.extend VCR::RSpec::Macros
|
35
|
+
end
|
metadata
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: gcal-unit
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Brilliant Fantastic
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-08-30 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: httparty
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 0.7.8
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 0.7.8
|
30
|
+
description: Wraps HTTP calls for the Google Calendar API.
|
31
|
+
email: support@brilliantfantastic.com
|
32
|
+
executables: []
|
33
|
+
extensions: []
|
34
|
+
extra_rdoc_files:
|
35
|
+
- README.md
|
36
|
+
files:
|
37
|
+
- Gemfile
|
38
|
+
- README.md
|
39
|
+
- Rakefile
|
40
|
+
- gcal-unit.gemspec
|
41
|
+
- lib/gcal_unit.rb
|
42
|
+
- lib/gcal_unit/authorization.rb
|
43
|
+
- lib/gcal_unit/calendar.rb
|
44
|
+
- lib/gcal_unit/calendar_list.rb
|
45
|
+
- lib/gcal_unit/client.rb
|
46
|
+
- lib/gcal_unit/configuration.rb
|
47
|
+
- lib/gcal_unit/errors.rb
|
48
|
+
- lib/gcal_unit/event.rb
|
49
|
+
- spec/lib/gcal_unit/authorization_spec.rb
|
50
|
+
- spec/lib/gcal_unit/calendar_list_spec.rb
|
51
|
+
- spec/lib/gcal_unit/calendar_spec.rb
|
52
|
+
- spec/lib/gcal_unit/configuration_spec.rb
|
53
|
+
- spec/lib/gcal_unit/event_spec.rb
|
54
|
+
- spec/spec_helper.rb
|
55
|
+
homepage: http://github.com/brilliantfantastic/gcal-unit
|
56
|
+
licenses: []
|
57
|
+
post_install_message:
|
58
|
+
rdoc_options:
|
59
|
+
- --charset=UTF-8
|
60
|
+
require_paths:
|
61
|
+
- lib
|
62
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
63
|
+
none: false
|
64
|
+
requirements:
|
65
|
+
- - ! '>='
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '0'
|
68
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
69
|
+
none: false
|
70
|
+
requirements:
|
71
|
+
- - ! '>='
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '0'
|
74
|
+
requirements: []
|
75
|
+
rubyforge_project: gcal-unit
|
76
|
+
rubygems_version: 1.8.23
|
77
|
+
signing_key:
|
78
|
+
specification_version: 2
|
79
|
+
summary: Pimp ass API wrapper for the Google Calendar API.
|
80
|
+
test_files: []
|