google_client 0.1.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.
@@ -0,0 +1,56 @@
1
+ module GoogleClient
2
+
3
+ class Contact
4
+
5
+ attr_reader :id
6
+ attr_accessor :name
7
+ attr_accessor :email
8
+ attr_accessor :phone_number
9
+ attr_accessor :user
10
+
11
+ def initialize(params = {})
12
+ @id = params[:id]
13
+ @name = params[:name]
14
+ @email = params[:email]
15
+ @phone_number = params[:phone_number]
16
+ @user = params[:user]
17
+ end
18
+
19
+ def to_s
20
+ "#{self.class.name} => { name: #{@name}, email: #{@email}, :phone_number => #{@phone_number} }"
21
+ end
22
+
23
+
24
+ def save
25
+
26
+ end
27
+
28
+ class << self
29
+ def build_contact(data, user = nil)
30
+ id = begin
31
+ data["id"]["$t"].split("base/").last
32
+ rescue
33
+ nil
34
+ end
35
+ name = begin
36
+ data["title"]["$t"].split("full/").last
37
+ rescue
38
+ ""
39
+ end
40
+ email = begin
41
+ data["gd$email"].collect { |address| address.select { |item| item["address"] }.values }.flatten
42
+ rescue
43
+ []
44
+ end
45
+ phone_number = begin
46
+ data["gd$phoneNumber"].collect { |number| number.select { |item| item["$t"] }.values }.flatten
47
+ rescue
48
+ []
49
+ end
50
+ Contact.new({:name => name, :email => email, :phone_number => phone_number, :user => user})
51
+ end
52
+ end
53
+
54
+ end
55
+
56
+ end
@@ -0,0 +1,39 @@
1
+ require 'rails'
2
+ require 'ostruct'
3
+
4
+ module GoogleClient
5
+ #
6
+ # This class defines the GoogleClient Rails Engine to handle OAuth authentication.
7
+ # The Engine defines two new routes to handle each of the OAuth steps
8
+ # 1.- forward the user request to Google server
9
+ # 2.- get the oauth code, request a valid access token and forward the token info to a user defined action
10
+ #
11
+ # How to configure GoogleClient Engine
12
+ #
13
+ # :uri => Google API endpoint
14
+ # :client_id => token that identifies your application in OAuth mechanism
15
+ # :client_secret => token that secures your communication in OAuth mechanism
16
+ # :forward_action => controller#action where google_client#code action will redirect the user token data:
17
+ # - :access_token
18
+ # - :expires_in
19
+ # - :refresh_token
20
+ #
21
+ # These configuration can be included in an application initializer, i.e. config/initializers/google_client.rb
22
+ #
23
+ # Rails.application.config.google_client.client_id = "<client_id>"
24
+ # Rails.application.config.google_client.client_secret = "<client_secret"
25
+ # Rails.application.config.google_client.forward_action = "controller#action" that will receive the user token data
26
+ #
27
+
28
+ class Engine < Rails::Engine
29
+
30
+ # we need to create the hashblue config hash before loading the application initializer
31
+ initializer :google_client, {:before => :load_config_initializers} do |app|
32
+
33
+ app.config.google_client = OpenStruct.new
34
+ # HashBlue API endpoint
35
+ app.config.google_client.uri = "https://www.google.com"
36
+ end
37
+
38
+ end
39
+ end
@@ -0,0 +1,13 @@
1
+ module GoogleClient
2
+
3
+ Error = Class.new StandardError
4
+
5
+ NotFoundError = Class.new Error
6
+
7
+ AuthenticationError = Class.new Error
8
+
9
+ BadRequestError = Class.new Error
10
+
11
+ ConflictError = Class.new Error
12
+
13
+ end
@@ -0,0 +1,135 @@
1
+ module GoogleClient
2
+ class Event
3
+
4
+ include Format
5
+
6
+ attr_reader :id
7
+ attr_accessor :calendar_id
8
+ attr_accessor :calendar
9
+ attr_accessor :title
10
+ attr_accessor :description
11
+ attr_accessor :attendees
12
+ attr_accessor :start_time
13
+ attr_accessor :end_time
14
+ attr_accessor :location
15
+ attr_accessor :attendees
16
+ attr_accessor :comments
17
+
18
+ def initialize(params = {})
19
+ @id = params[:event_id] || params[:id] || nil
20
+ @title = params[:title]
21
+ @description = params[:description]
22
+ @location = params[:location]
23
+ @start_time = params[:start_time]
24
+ @end_time = params[:end_time]
25
+ @calendar_id = params[:calendar_id]
26
+ @calendar = params[:calendar]
27
+ @comments = params[:comments]
28
+ @attendees = params[:attendees]
29
+
30
+ @calendar.nil? or @connection = @calendar.connection
31
+ @json_mode = true
32
+ block_given? and yield self
33
+ end
34
+
35
+ def to_s
36
+ "#{self.class.name} => { id: #{@id}, title: #{@title}, description: #{@description}, :start_time => #{@start_time}, :end_time => #{@end_time}, :location => #{@location}, :attendees => #{@attendees} }"
37
+ end
38
+
39
+ def to_hash
40
+ {
41
+ :title => @title,
42
+ :details => @description,
43
+ :timeZone => @timezone,
44
+ :when => [{:start => @start_time,
45
+ :end => @end_time}]
46
+ }.delete_if{|k,v| v.nil?}
47
+ end
48
+
49
+
50
+ def connection
51
+ @connection or raise RuntimeError.new "Http connection not established"
52
+ end
53
+
54
+ ##
55
+ # Save the Event in the server
56
+ # ==== Return
57
+ # * *Event* instance
58
+ def save
59
+ if @id.nil?
60
+ data = decode_response connection.post("/calendar/feeds/#{calendar}/private/full", {:data => self.to_hash})
61
+ @id = data["data"]["id"]
62
+ else
63
+ # put
64
+ end
65
+ self
66
+ end
67
+
68
+ def calendar
69
+ calendar= if @calendar.nil?
70
+ if @calendar_id.nil?
71
+ raise Error.new "Event must be associated to a calendar"
72
+ else
73
+ @calendar_id
74
+ end
75
+ else
76
+ @calendar.id
77
+ end
78
+ end
79
+
80
+ ##
81
+ # Delete the Event from the server
82
+ # ==== Return
83
+ # * *true* if sucessful
84
+ # * raise Error if failure
85
+ def delete
86
+ @id.nil? and raise Error.new "event cannot be deleted if has not an unique identifier"
87
+ connection.delete "/calendar/feeds/#{calendar}/private/full/#{@id}", nil, {"If-Match" => "*"}
88
+ true
89
+ end
90
+
91
+ ##
92
+ # Fetch the specific event from Google Calendar
93
+ # ==== Return
94
+ # * *Event*
95
+ def fetch
96
+ if @calendar_id.nil?
97
+ @calendar.nil? and raise Error.new "calendar or calendar_id must be valid in the event"
98
+ @calendar_id = @calendar.id
99
+ end
100
+ data = connection.get "/calendar/feeds/#{@calendar_id}/private/full/#{@id}"
101
+ self.class.build_event data["entry"], @calendar
102
+ end
103
+
104
+ class << self
105
+
106
+ def create(params)
107
+ event = if block_given?
108
+ new(params, &Proc.new)
109
+ else
110
+ new(params)
111
+ end
112
+ end
113
+
114
+ def build_event(data, calendar = nil)
115
+ attendees = data["gd$who"]
116
+ attendees.nil? or attendees = attendees.map{|attendee| {:name => attendee["valueString"], :email => attendee["email"]}}
117
+
118
+ start_time = data["gd$when"][0]["startTime"]
119
+ end_time = data["gd$when"][0]["endTime"]
120
+
121
+ Event.new({:id => data["id"]["$t"].split("full/").last,
122
+ :calendar => calendar,
123
+ :title => data["title"]["$t"],
124
+ :description => data["content"]["$t"],
125
+ :location => data["gd$where"][0]["valueString"],
126
+ :comments => data["gd$comments"],
127
+ :attendees => attendees,
128
+ :start_time => start_time,
129
+ :end_time => end_time})
130
+ end
131
+ end
132
+
133
+ end
134
+
135
+ end
@@ -0,0 +1,19 @@
1
+ module GoogleClient
2
+
3
+ module Format
4
+
5
+ def decode_response(response)
6
+ response = if json?
7
+ JSON.parse(response.body)
8
+ else
9
+ raise Error.new "Unknow data format"
10
+ end
11
+ end
12
+
13
+ def json?
14
+ defined? @json_mode and return @json_mode
15
+ raise Error.new "Unknown data format"
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,113 @@
1
+ require 'rest-client'
2
+ require 'addressable/uri'
3
+
4
+ module GoogleClient
5
+ class HttpConnection
6
+
7
+ attr_accessor :headers
8
+ attr_accessor :access_token
9
+ attr_accessor :query_params
10
+ attr_accessor :headers
11
+
12
+ def initialize(uri, query_params = {}, headers = {})
13
+ @headers = headers
14
+ uri = URI.parse(uri)
15
+ @host = uri.host
16
+ @port = uri.port
17
+ @scheme = uri.scheme
18
+ @query_params = query_params
19
+ @headers = headers
20
+ end
21
+
22
+ ##
23
+ # Http::GET request
24
+ #
25
+ # ==== Parameters
26
+ # * *path* URI path
27
+ # * *query_params* query parameters to be added to the request
28
+ #
29
+ # ==== Return
30
+ # * Hash JSON decoded response
31
+ def get(path, query_params = {}, headers = {})
32
+ uri = create_uri(path, self.query_params.merge(query_params))
33
+ RestClient.get(uri.to_s, self.headers.merge(headers)) do |response, request, result, &block|
34
+ handle_response(response, request, result, &block)
35
+ end
36
+ end
37
+
38
+ def post(path, body = {}, headers = {})
39
+ uri = create_uri(path, {})
40
+
41
+ _headers = self.headers.merge(headers)
42
+ if _headers.has_key?("Content-Type") && _headers["Content-Type"].eql?("json")
43
+ body.is_a?(Hash) and body = body.to_json
44
+ end
45
+ RestClient.post(uri.to_s, body, _headers) do |response, request, result, &block|
46
+ handle_response(response, request, result, &block)
47
+ end
48
+ end
49
+
50
+ def delete(path, query_params = {}, headers = {})
51
+ uri = create_uri(path, {})
52
+ uri = uri.to_s[0..-2]
53
+ RestClient.delete(uri.to_s, headers.merge({:Authorization => self.headers[:Authorization]})) do |response, request, result, &block|
54
+ handle_response(response, request, result, &block)
55
+ end
56
+ end
57
+
58
+ def create_uri(path, query_params = {})
59
+ Addressable::URI.new({:host => @host,
60
+ :port => @port,
61
+ :scheme => @scheme,
62
+ :path => path,
63
+ :query => create_query(query_params)})
64
+ end
65
+
66
+ private
67
+
68
+ ##
69
+ # This method is used to handle the HTTP response
70
+ # ==== Parameters
71
+ # *response* HTTP response
72
+ # *request* HTTP request
73
+ # *result*
74
+ def handle_response(response, request, result, &block)
75
+ case response.code
76
+ when 200..207
77
+ response.return!(request, result, &block)
78
+ when 301..307
79
+ response.follow_redirection(request, result, &block)
80
+ when 400
81
+ raise BadRequestError.new(result.body)
82
+ when 401
83
+ raise AuthenticationError.new(result.body)
84
+ when 404
85
+ raise NotFoundError.new(result.body)
86
+ when 409
87
+ raise ConflictError.new(result.body)
88
+ when 422
89
+ raise Error.new(result.body)
90
+ when 402..408,410..421,423..499
91
+ response.return!(request, result, &block)
92
+ when 500..599
93
+ raise Error.new(result.body)
94
+ else
95
+ response.return!(request, result, &block)
96
+ end
97
+ end
98
+
99
+ ##
100
+ # ==== Parameters
101
+ # * *query_params* query parameters to be added to the request
102
+ #
103
+ # ==== Return
104
+ # * String with the parameters concatened and escaped using CGI.escape
105
+ def create_query(query_params)
106
+ query_params.map do |k, v|
107
+ "#{CGI.escape(k.to_s)}=#{CGI.escape(v.to_s)}"
108
+ end.join("&")
109
+ end
110
+
111
+ end
112
+
113
+ end
@@ -0,0 +1,15 @@
1
+ module GoogleClient
2
+ class Profile
3
+ attr_accessor :email
4
+ attr_accessor :external_id
5
+
6
+ def initialize(params ={})
7
+ @email = params[:email]
8
+ @external_id = params[:external_id]
9
+ end
10
+
11
+ def to_s
12
+ "#{self.class.name} => { email: #{@email}, external_id: #{@external_id} }"
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,130 @@
1
+ module GoogleClient
2
+ class User
3
+
4
+ attr_accessor :oauth_credentials
5
+ attr_accessor :json_mode
6
+
7
+ include Format
8
+
9
+ def initialize(oauth_credentials, user_credentials = nil)
10
+ @oauth_credentials = oauth_credentials
11
+ @json_mode = true
12
+ end
13
+
14
+ # Get user profile
15
+ #
16
+ # ==== Return
17
+ # * Profile instance
18
+ def profile
19
+ data = decode_response http.get "/m8/feeds/contacts/default/full", {"max-results" => 1}
20
+ email = data["feed"]["id"]["$t"]
21
+ Profile.new({:email => email, :external_id => email.split("@").first})
22
+ rescue
23
+ Profile.new
24
+ end
25
+
26
+ # Refresh an invalid access_token
27
+ # ==== Parameters
28
+ # * *refresh_token*
29
+ # * *client_id*
30
+ # * *client_secret*
31
+ #
32
+ # ==== Return
33
+ # * Hash with the following keys:
34
+ # * access_token
35
+ # * token_type
36
+ # * expires_in
37
+ # * BadRequestError if invalid refresh token or invalid client
38
+ def refresh refresh_token, client_id, client_secret
39
+ _params = {
40
+ :client_id => client_id,
41
+ :client_secret => client_secret,
42
+ :refresh_token => refresh_token,
43
+ :grant_type => "refresh_token"
44
+ }
45
+ data = HttpConnection.new("https://accounts.google.com",
46
+ {:alt => "json"},
47
+ {:Authorization => "OAuth #{oauth_credentials}",
48
+ "Content-Type" => "application/x-www-form-urlencoded",
49
+ :Accept => "application/json"}).post "/o/oauth2/token", _params
50
+ decode_response data.body
51
+ end
52
+
53
+ # Same as refresh method, but also updates the oauth_credentials variable with the new granted token
54
+ def refresh! refresh_token, client_id, client_secret
55
+ data = refresh refresh_token, client_id, client_secret
56
+ @oauth_credentials = data["access_token"]
57
+ data
58
+ end
59
+
60
+ ##
61
+ #
62
+ # ==== Parameters
63
+ # * *calendar_id* Calendar unique identifier
64
+ #
65
+ # ==== Return
66
+ # * *Calendar* instance
67
+ # * *Array of Calendar* instances
68
+ def calendar(calendar_id = :all)
69
+ calendars = if calendar_id.nil? || calendar_id.eql?(:all)
70
+ calendars = decode_response http.get "/calendar/feeds/default/allcalendars/full"
71
+ calendars = calendars["feed"]["entry"]
72
+ calendars.map{ |calendar| Calendar.build_calendar(calendar, self)}
73
+ elsif calendar_id.eql?(:own)
74
+ calendars = decode_response http.get "/calendar/feeds/default/owncalendars/full"
75
+ calendars = calendars["feed"]["entry"]
76
+ calendars.map{ |calendar| Calendar.build_calendar(calendar, self)}
77
+ elsif calendar_id.is_a?(String)
78
+ Calendar.new({:id => calendar_id, :user => self}).fetch
79
+ elsif calendar_id.is_a?(Hash)
80
+ # TODO add support to {:title => calendar_title}
81
+ raise ArgumentError.new "Invalid argument type #{calendar_id.class}"
82
+ else
83
+ raise ArgumentError.new "Invalid argument type #{calendar_id.class}"
84
+ end
85
+ end
86
+
87
+ # Create a calendar
88
+ #
89
+ # ==== Parameters
90
+ # * *params* Hash
91
+ # * :title
92
+ # * :details
93
+ # * :timezone
94
+ # * :location
95
+ #
96
+ # ==== Return
97
+ # Calendar instance
98
+ def create_calendar(params)
99
+ calendar = if block_given?
100
+ Calendar.create(params.merge({:user => self}), &Proc.new)
101
+ else
102
+ Calendar.create(params.merge({:user => self}))
103
+ end
104
+ calendar.save
105
+ end
106
+
107
+ # Fetch user contacts
108
+ def contacts
109
+ contacts = decode_response http.get "/m8/feeds/contacts/default/full", {"max-results" => "1000"}
110
+ contacts = contacts["feed"]["entry"]
111
+ contacts.map{|contact| Contact.build_contact(contact, self)}
112
+ end
113
+
114
+ ##
115
+ # Method that creates and returns the HttpConnection instance that shall be used
116
+ #
117
+ # ==== Return
118
+ # * *HttpConnection* instance
119
+ def http
120
+ @http ||= HttpConnection.new("https://www.google.com",
121
+ {:alt => "json"},
122
+ {:Authorization => "OAuth #{oauth_credentials}",
123
+ "Content-Type" => "json",
124
+ :Accept => "application/json"})
125
+ end
126
+
127
+ alias :connection :http
128
+
129
+ end
130
+ end