contextio-lite 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 9a4c95db7436871ec9123eb36072dd02e24f5078
4
+ data.tar.gz: 55b65431a9f3f5b517992e5ed3cde2ac2aa4198a
5
+ SHA512:
6
+ metadata.gz: 56066f305c8e1341b3f911f18a25bce82297c99c1b6697569704d954fae1503f9c57179e16a8cc54917d4924b6c0d1b2e133da7738b1786e71fd137989d42641
7
+ data.tar.gz: 4d245707e67caeac959882263e47dd5b121f7464bf01251b707259e4b463d8c2992bc402f6c776af395da4d1d6a690b17c14256a6910464ed6c6853e495b86ab
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in contextio-lite.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 javi
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,114 @@
1
+ # Contextio::Lite
2
+
3
+ This is API client for the [Context.IO Lite](https://context.io/) version based on [Context.IO Ruby](https://github.com/contextio/contextio-ruby) for the 2.0 version.
4
+ It works in the same way as the official, calling first a user object and then working with the collections...
5
+
6
+ ## Installation
7
+
8
+ Add this line to your application's Gemfile:
9
+
10
+ ```ruby
11
+ gem 'contextio-lite'
12
+ ```
13
+
14
+ And then execute:
15
+
16
+ $ bundle
17
+
18
+ Or install it yourself as:
19
+
20
+ $ gem install contextio-lite
21
+
22
+ ## Usage
23
+
24
+ ### Get a client
25
+
26
+ ```ruby
27
+ require 'contextio/lite'
28
+
29
+ client = ContextIO::Lite.new(API_KEY, API_SECRET)
30
+ ```
31
+
32
+ ### Dealing with users
33
+
34
+ List all the registered lite users IDs
35
+
36
+ ```ruby
37
+ p client.users.map(&:id)
38
+ ```
39
+
40
+ CRUD
41
+
42
+ ```ruby
43
+ user = client.users.create email: 'bob@gmail.com', first_name: 'Bob', server: 'imap.gmail.com' username: 'bob', 'use_sll':true, 'port': 993, 'type': 'IMAP
44
+
45
+ user_id = user.id
46
+
47
+ client.users[user_id].update :hash_attrs
48
+
49
+ client.users[user_id].delete
50
+ ```
51
+
52
+ ### Retrieving messages
53
+
54
+ To access the emails, first you need to select an email_account and the folder containing your email
55
+
56
+ So if you want to list the user email accounts or folders you can do like this
57
+
58
+ ```ruby
59
+ p client[user_id].email_accounts.map(&:label)
60
+ p client[user_id].email_accounts[label].folders.map(&:name) # can access email_accounts by number => email_accounts[0]
61
+ ```
62
+
63
+ Listing all the email subjects inside a folder (limited by context IO to 100)
64
+ ```ruby
65
+ client[user_id].email_accounts[0].folders['INBOX'].messages do
66
+ p messages.subject
67
+ end
68
+ ```
69
+
70
+ And if you want to filter the emails
71
+
72
+ ```ruby
73
+ client[user_id].email_accounts[0].folder['INBOX'].messages.where(limit: 3)
74
+ ```
75
+
76
+ You also may want to access the content of one message
77
+
78
+ ```ruby
79
+ client[user_id].email_accounts[0].folder['INBOX'].messages['<message_id>'].body_plain # or body_html
80
+
81
+ client[user_id].email_accounts[0].folder['INBOX'].messages['<message_id>'].with(include_body:true).body_plain
82
+ ```
83
+
84
+ The first one calls https://api.context.io/lite/users/id/email_accounts/label/folders/folder/messages/message_id/body
85
+ and the second calls https://api.context.io/lite/users/id/email_accounts/label/folders/folder/messages/message_id?include_body=1
86
+ but they return the same.
87
+
88
+ And the above should works also with 'flags' or 'headers'.
89
+
90
+ ### Webhooks
91
+
92
+ One of the main feature that this version has and 2.0 doesn't is the real-time webhooks.
93
+ So in order to work with them we can do
94
+
95
+ ```ruby
96
+ client[user_id].webhooks.map(&:webhook_id) # listing all the webhooks an user has
97
+
98
+ client[user_id].create callback_url, failure_url, filter_folder_added: 'INBOX', include_body: true
99
+ ```
100
+
101
+ So it will call the callback_url every time there is a new message on the user INBOX folder, posting the message info and body included.
102
+
103
+
104
+ ## Contributing
105
+
106
+ 1. Fork it ( https://github.com/javijuol/contextio-lite/fork )
107
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
108
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
109
+ 4. Push to the branch (`git push origin my-new-feature`)
110
+ 5. Create a new Pull Request
111
+
112
+ ## Copyright
113
+
114
+ This gem is distributed under the MIT License. See LICENSE.md for details.
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require File.expand_path('../lib/contextio', __FILE__)
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'contextio-lite'
8
+ spec.version = ContextIO.version
9
+ spec.authors = ['Javier Juan']
10
+ spec.email = ['javier@promivia.com']
11
+ spec.summary = %q{Provides interface to Context.IO Lite API}
12
+ spec.description = %q{Short implementation of a client rest API for the Context.IO Lite API}
13
+ spec.homepage = 'https://github.com/javijuol/contextio-lite'
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.add_development_dependency 'bundler', '~> 1.7'
22
+ spec.add_development_dependency 'rake', '~> 10.0'
23
+ spec.add_development_dependency 'debase', '~> 0'
24
+ spec.add_development_dependency 'faraday', '~> 0.9.1'
25
+ spec.add_development_dependency 'faraday_middleware', '~> 0.9.1'
26
+
27
+ end
@@ -0,0 +1,14 @@
1
+ module ContextIO
2
+ VERSION = '0.0.2'
3
+
4
+ # @private
5
+ # Handle for the `API` instance. For internal use only.
6
+ attr_reader :api
7
+
8
+ def self.version
9
+ VERSION
10
+ end
11
+
12
+ end
13
+
14
+ require_relative 'contextio/lite'
@@ -0,0 +1,213 @@
1
+ require 'uri'
2
+ require 'json'
3
+ require 'faraday'
4
+ require 'faraday_middleware'
5
+
6
+ module ContextIO
7
+ module API
8
+ class AbstractAPI
9
+
10
+ # @private
11
+ BASE_URL = 'https://api.context.io'
12
+
13
+ # @return [String] The version of the Context.IO API this version of the
14
+ # gem is intended for use with.
15
+ def self.version
16
+ raise NotDefinedError, 'VERSION is not defined in your API subclassed model.' if self::VERSION.nil?
17
+ self::VERSION
18
+ end
19
+
20
+ # @return [String] The base URL the API is served from.
21
+ def self.base_url
22
+ BASE_URL
23
+ end
24
+
25
+ def self.user_agent_string
26
+ raise NotDefinedError, 'user_agent_string undefined in your API subclassed model.'
27
+ end
28
+
29
+ def user_agent_string
30
+ self.class.user_agent_string
31
+ end
32
+
33
+ attr_accessor :base_url, :version
34
+
35
+ # @!attribute [r] key
36
+ # @return [String] The OAuth key for the user's Context.IO account.
37
+ # @!attribute [r] secret
38
+ # @return [String] The OAuth secret for the user's Context.IO account.
39
+ # @!attribute [r] opts
40
+ # @return [Hash] opts Optional options for OAuth connections.
41
+ attr_reader :key, :secret, :opts
42
+
43
+ # @param [String] key The user's OAuth key for their Context.IO account.
44
+ # @param [String] secret The user's OAuth secret for their Context.IO account.
45
+ # @param [Hash] opts Optional options for OAuth connections. ie. :timeout and :open_timeout are supported
46
+ def initialize(key, secret, opts={})
47
+ @key = key
48
+ @secret = secret
49
+ @opts = opts || {}
50
+ @base_url = self.class.base_url
51
+ @version = self.class.version
52
+ end
53
+
54
+ # Generates the path for a resource_path and params hash for use with the API.
55
+ #
56
+ # @param [String] resource_path The resource_path or full resource URL for
57
+ # the resource being acted on.
58
+ # @param [{String, Symbol => String, Symbol, Array<String, Symbol>}] params
59
+ # A Hash of the query parameters for the action represented by this path.
60
+ def path(resource_path, params = {})
61
+ "/#{version}/#{strip_resource_path(resource_path)}#{self.class.hash_to_url_params(params)}"
62
+ end
63
+
64
+ # Makes a request against the Context.IO API.
65
+ #
66
+ # @param [String, Symbol] method The HTTP verb for the request (lower case).
67
+ # @param [String] resource_path The path to the resource in question.
68
+ # @param [{String, Symbol => String, Symbol, Array<String, Symbol>}] params
69
+ # A Hash of the query parameters for the action represented by this
70
+ # request.
71
+ #
72
+ # @raise [API::Error] if the response code isn't in the 200 or 300 range.
73
+ def request(method, resource_path, params = {})
74
+ response = oauth_request(method, resource_path, params, { 'Accept' => 'application/json' })
75
+
76
+ with_error_handling(response) do |response|
77
+ parse_json(response.body)
78
+ end
79
+ end
80
+
81
+ def raw_request(method, resource_path, params={})
82
+ response = oauth_request(method, resource_path, params)
83
+
84
+ with_error_handling(response) do |response|
85
+ response.body
86
+ end
87
+ end
88
+
89
+ protected
90
+
91
+ # Makes a request signed for OAuth, encoding parameters correctly, etc.
92
+ #
93
+ # @param [String, Symbol] method The HTTP verb for the request (lower case).
94
+ # @param [String] resource_path The path to the resource in question.
95
+ # @param [{String, Symbol => String, Symbol, Array<String, Symbol>}] params
96
+ # A Hash of the query parameters for the action represented by this
97
+ # request.
98
+ # @param [{String, Symbol => String, Symbol, Array<String, Symbol>}] headers
99
+ # A Hash of headers to be merged with the default headers for making
100
+ # requests.
101
+ #
102
+ # @return [Faraday::Response] The response object from the request.
103
+ def oauth_request(method, resource_path, params, headers=nil)
104
+ normalized_params = params.inject({}) do |normalized_params, (key, value)|
105
+ normalized_params[key.to_sym] = value
106
+ normalized_params
107
+ end
108
+
109
+ connection.send(method, path(resource_path), normalized_params, headers) do |request|
110
+ if request.method == :put
111
+ request.params = normalized_params
112
+ request.body = {}
113
+ end
114
+ end
115
+ end
116
+
117
+ # So that we can accept full URLs, this strips the domain and version number
118
+ # out and returns just the resource path.
119
+ #
120
+ # @param [#to_s] resource_path The full URL or path for a resource.
121
+ #
122
+ # @return [String] The resource path.
123
+ def strip_resource_path(resource_path)
124
+ resource_path.to_s.gsub("#{base_url}/#{version}/", '')
125
+ end
126
+
127
+ # Context.IO's API expects query parameters that are arrays to be comma
128
+ # separated, rather than submitted more than once. This munges those arrays
129
+ # and then URL-encodes the whole thing into a query string.
130
+ #
131
+ # @param [{String, Symbol => String, Symbol, Array<String, Symbol>}] params
132
+ # A Hash of the query parameters.
133
+ #
134
+ # @return [String] A URL-encoded version of the query parameters.
135
+ def self.hash_to_url_params(params = {})
136
+ return '' if params.empty?
137
+
138
+ params = params.inject({}) do |memo, (k, v)|
139
+ memo[k] = Array(v).join(',')
140
+
141
+ memo
142
+ end
143
+
144
+ "?#{URI.encode_www_form(params)}"
145
+ end
146
+
147
+ # @!attribute [r] connection
148
+ # @return [Faraday::Connection] A handle on the Faraday connection object.
149
+ def connection
150
+ @connection ||= Faraday::Connection.new(base_url) do |faraday|
151
+ faraday.headers['User-Agent'] = user_agent_string
152
+
153
+ faraday.request :oauth, consumer_key: key, consumer_secret: secret
154
+ faraday.request :url_encoded
155
+
156
+ faraday.adapter Faraday.default_adapter
157
+ end
158
+ end
159
+
160
+ # Errors can come in a few shapes and we want to detect them and extract the
161
+ # useful information. If no errors are found, it calls the provided block
162
+ # and passes the response through.
163
+ #
164
+ # @param [Faraday::Response] response A response object from making a request to the
165
+ # API with Faraday.
166
+ #
167
+ # @raise [API::Error] if the response code isn't in the 200 or 300 range.
168
+ def with_error_handling(response, &block)
169
+ return block.call(response) if response.success?
170
+
171
+ parsed_body = parse_json(response.body)
172
+ message = determine_best_error_message(parsed_body) || "HTTP #{response.status} Error"
173
+
174
+ raise ContextIO::API::Error, message
175
+ end
176
+
177
+ # Parses JSON if there's valid JSON passed in.
178
+ #
179
+ # @param [String] document A string you suspect may be a JSON document.
180
+ #
181
+ # @return [Hash, Array, Nil] Either a parsed version of the JSON document or
182
+ # nil, if the document wasn't valid JSON.
183
+ def parse_json(document)
184
+ return JSON.parse(document.to_s)
185
+ rescue JSON::ParserError => e
186
+ return nil
187
+ end
188
+
189
+
190
+ # Given a parsed JSON body from an error response, figures out if it can
191
+ # pull useful information therefrom.
192
+ #
193
+ # @param [Hash] parsed_body A Hash parsed from a JSON document that may
194
+ # describe an error condition.
195
+ #
196
+ # @return [String, Nil] If it can, it will return a human-readable
197
+ # error-describing String. Otherwise, nil.
198
+ def determine_best_error_message(parsed_body)
199
+ return unless parsed_body.respond_to?(:[])
200
+
201
+ if parsed_body['type'] == 'error'
202
+ return parsed_body['value']
203
+ elsif parsed_body.has_key?('success') && !parsed_body['success']
204
+ return [parsed_body['feedback_code'], parsed_body['connectionLog']].compact.join("\n")
205
+ end
206
+ end
207
+ end
208
+
209
+ class NotDefinedError < StandardError; end
210
+ class Error < StandardError; end
211
+
212
+ end
213
+ end
@@ -0,0 +1,17 @@
1
+ module ContextIO
2
+ module API
3
+ module AssociationHelpers
4
+ def self.class_for_association_name(association_name)
5
+ associations[association_name]
6
+ end
7
+
8
+ def self.register_resource(klass, association_name)
9
+ associations[association_name] = klass
10
+ end
11
+
12
+ def self.associations
13
+ @associations ||= {}
14
+ end
15
+ end
16
+ end
17
+ end