context-io 0.0.1
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.
- data/Gemfile +4 -0
- data/LICENSE +21 -0
- data/README.md +129 -0
- data/Rakefile +121 -0
- data/SPEC.md +49 -0
- data/context-io.gemspec +96 -0
- data/lib/context-io.rb +14 -0
- data/lib/context-io/account.rb +254 -0
- data/lib/context-io/authentication.rb +23 -0
- data/lib/context-io/config.rb +103 -0
- data/lib/context-io/connection.rb +45 -0
- data/lib/context-io/core_ext/hash.rb +31 -0
- data/lib/context-io/error.rb +24 -0
- data/lib/context-io/error/bad_request.rb +12 -0
- data/lib/context-io/error/client_error.rb +10 -0
- data/lib/context-io/error/forbidden.rb +12 -0
- data/lib/context-io/error/internal_server_error.rb +10 -0
- data/lib/context-io/error/not_found.rb +12 -0
- data/lib/context-io/error/payment_required.rb +13 -0
- data/lib/context-io/error/server_error.rb +10 -0
- data/lib/context-io/error/service_unavailable.rb +13 -0
- data/lib/context-io/error/unauthorized.rb +12 -0
- data/lib/context-io/file.rb +234 -0
- data/lib/context-io/folder.rb +90 -0
- data/lib/context-io/message.rb +160 -0
- data/lib/context-io/oauth_provider.rb +84 -0
- data/lib/context-io/request.rb +70 -0
- data/lib/context-io/request/oauth.rb +44 -0
- data/lib/context-io/resource.rb +16 -0
- data/lib/context-io/response.rb +5 -0
- data/lib/context-io/response/parse_json.rb +30 -0
- data/lib/context-io/response/raise_client_error.rb +59 -0
- data/lib/context-io/response/raise_server_error.rb +32 -0
- data/lib/context-io/source.rb +193 -0
- data/lib/context-io/version.rb +7 -0
- data/spec/account_spec.rb +247 -0
- data/spec/contextio_spec.rb +45 -0
- data/spec/file_spec.rb +101 -0
- data/spec/fixtures/accounts.json +21 -0
- data/spec/fixtures/files.json +41 -0
- data/spec/fixtures/files_group.json +47 -0
- data/spec/fixtures/folders.json +1 -0
- data/spec/fixtures/messages.json +1 -0
- data/spec/fixtures/oauth_providers.json +12 -0
- data/spec/fixtures/sources.json +1 -0
- data/spec/folder_spec.rb +48 -0
- data/spec/message_spec.rb +294 -0
- data/spec/oauth_provider_spec.rb +88 -0
- data/spec/source_spec.rb +248 -0
- data/spec/spec_helper.rb +4 -0
- metadata +214 -0
@@ -0,0 +1,90 @@
|
|
1
|
+
require "context-io/resource"
|
2
|
+
|
3
|
+
module ContextIO
|
4
|
+
class Folder < ContextIO::Resource
|
5
|
+
# @api public
|
6
|
+
# @return [String] The (Context.IO) ID of the account this folder belongs
|
7
|
+
# to, as a hexadecimal string.
|
8
|
+
attr_reader :account_id
|
9
|
+
|
10
|
+
# @api public
|
11
|
+
# @return [String] The label of the source this folder belongs to.
|
12
|
+
attr_reader :source_label
|
13
|
+
|
14
|
+
# @api public
|
15
|
+
# @return [String] The full name of the folder. This includes the name of
|
16
|
+
# parent folders.
|
17
|
+
attr_reader :name
|
18
|
+
|
19
|
+
# @api public
|
20
|
+
# @return [String] The folder hierarchy delimiter.
|
21
|
+
#
|
22
|
+
# @example Get the folder name and not the entire path.
|
23
|
+
# folder.name.split(folder.delim).last
|
24
|
+
attr_reader :delim
|
25
|
+
|
26
|
+
# @api public
|
27
|
+
# @return [Integer] The number of messages in the folder.
|
28
|
+
attr_reader :nb_messages
|
29
|
+
|
30
|
+
# @api public
|
31
|
+
# @return [true, false] Whether this folder is included when Context.IO
|
32
|
+
# syncs with the source.
|
33
|
+
attr_reader :included_in_sync
|
34
|
+
|
35
|
+
# Get all folders for a given source and account
|
36
|
+
#
|
37
|
+
# @api public
|
38
|
+
#
|
39
|
+
# @overload all(account_id, source_label)
|
40
|
+
# @param [#to_s] account_id The account ID
|
41
|
+
# @param [#to_s] source_label The source label
|
42
|
+
# @overload all(source)
|
43
|
+
# @param [ContextIO::Source] source The source object
|
44
|
+
#
|
45
|
+
# @example Find a the folders on a given source.
|
46
|
+
# ContextIO::Folder.all(source)
|
47
|
+
#
|
48
|
+
# @return [Array<ContextIO::Folder>] The folders in the given source.
|
49
|
+
def self.all(*args)
|
50
|
+
if args.length == 1
|
51
|
+
account_id = args.first.account_id
|
52
|
+
source_label = args.first.label
|
53
|
+
elsif args.length == 2
|
54
|
+
account_id = args.first.to_s
|
55
|
+
source_label = args.last.to_s
|
56
|
+
else
|
57
|
+
raise ArgumentError, "Expecting one or two arguments, got #{args.length}"
|
58
|
+
end
|
59
|
+
|
60
|
+
get("/2.0/accounts/#{account_id}/sources/#{source_label}/folders").map do |msg|
|
61
|
+
Folder.from_json(account_id, source_label, msg)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Create a Folder with the JSON data from Context.IO
|
66
|
+
#
|
67
|
+
# @api private
|
68
|
+
#
|
69
|
+
# @param [String] account_id The account ID the source belongs to.
|
70
|
+
# @param [String] source_label The label of the source this folder belongs
|
71
|
+
# to.
|
72
|
+
# @param [Hash] json The parsed JSON object returned by a Context.IO API
|
73
|
+
# request. See their documentation for possible keys.
|
74
|
+
#
|
75
|
+
# @return [ContextIO::Folder] A Folder with the given attributes.
|
76
|
+
def self.from_json(account_id, source_label, json)
|
77
|
+
folder = new
|
78
|
+
folder.instance_eval do
|
79
|
+
@account_id = account_id
|
80
|
+
@source_label = source_label
|
81
|
+
@name = json['name']
|
82
|
+
@delim = json['delim']
|
83
|
+
@nb_messages = json['nb_messages']
|
84
|
+
@included_in_sync = json['included_in_sync']
|
85
|
+
end
|
86
|
+
|
87
|
+
folder
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,160 @@
|
|
1
|
+
require "context-io/resource"
|
2
|
+
|
3
|
+
module ContextIO
|
4
|
+
class Message < Resource
|
5
|
+
attr_accessor :message_id, :account_id, :sources, :from, :to, :cc, :subject, :date, :folders, :files
|
6
|
+
attr_reader :raw_data
|
7
|
+
|
8
|
+
# Public: Get all messages for given account.
|
9
|
+
#
|
10
|
+
# query - An optional Hash (default: {}) containing a query to filter the
|
11
|
+
# responses. For possible values see Context.IO API documentation.
|
12
|
+
# Returns an Array of Message objects.
|
13
|
+
#
|
14
|
+
# account - Account object or ID
|
15
|
+
def self.all(account, query = {})
|
16
|
+
return [] if account.nil?
|
17
|
+
|
18
|
+
account_id = account.is_a?(Account) ? account.id : account.to_s
|
19
|
+
get("/2.0/accounts/#{account_id}/messages", query).map do |msg|
|
20
|
+
Message.from_json(account_id, msg)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.find(account, message_id)
|
25
|
+
return nil if account.nil? or message_id.nil?
|
26
|
+
account_id = account.is_a?(Account) ? account.id : account.to_s
|
27
|
+
|
28
|
+
Message.from_json(account_id, get("/2.0/accounts/#{account_id}/messages/#{message_id}"))
|
29
|
+
end
|
30
|
+
|
31
|
+
# Internal: Create an Message instance from the JSON returned by the
|
32
|
+
# Context.IO server.
|
33
|
+
#
|
34
|
+
# json - The parsed JSON returned by the Context.IO server. See their
|
35
|
+
# documentation for what keys are possible.
|
36
|
+
#
|
37
|
+
# Returns a ContextIO::Message instance.
|
38
|
+
def self.from_json(account_id, json_msg)
|
39
|
+
message = new(account_id, json_msg)
|
40
|
+
message.message_id = json_msg["message_id"]
|
41
|
+
message.subject = json_msg["subject"]
|
42
|
+
message.date = Time.at json_msg["date"]
|
43
|
+
message.sources = json_msg["sources"]
|
44
|
+
message.from = json_msg["addresses"]["from"]
|
45
|
+
message.to = json_msg["addresses"]["to"]
|
46
|
+
message.cc = json_msg["addresses"]["cc"]
|
47
|
+
message.folders = json_msg["folders"]
|
48
|
+
message.files = json_msg["files"]
|
49
|
+
message
|
50
|
+
end
|
51
|
+
|
52
|
+
# Internal: Returns ContextIO::Message object
|
53
|
+
#
|
54
|
+
# raw_data - The parse JSON returned by the Context.IO server.
|
55
|
+
def initialize(account_id, raw_data)
|
56
|
+
@account_id = account_id
|
57
|
+
@raw_data = raw_data
|
58
|
+
@body = {}
|
59
|
+
end
|
60
|
+
|
61
|
+
# Public: Returns message body. Data is lazy loaded. Message
|
62
|
+
# body fetched from Context.IO server contain plain text and html
|
63
|
+
# format and both formats are stored.
|
64
|
+
#
|
65
|
+
# format - String determining required format of message body.
|
66
|
+
# Allowed values are :plain and :html. Default value is :plain.
|
67
|
+
def body(format = :plain)
|
68
|
+
if @body.empty?
|
69
|
+
get("#{url}/body").each do |b|
|
70
|
+
@body[b["type"]] = b["content"]
|
71
|
+
end
|
72
|
+
end
|
73
|
+
@body["text/#{format}"]
|
74
|
+
end
|
75
|
+
|
76
|
+
# Public: Returns message headers. Data is lazy loaded.
|
77
|
+
def headers
|
78
|
+
@headers ||= get("#{url}/headers")
|
79
|
+
end
|
80
|
+
|
81
|
+
# Public: Returns message flags.
|
82
|
+
def flags
|
83
|
+
get("#{url}/flags")
|
84
|
+
end
|
85
|
+
|
86
|
+
def read!
|
87
|
+
flag("seen" => true)
|
88
|
+
end
|
89
|
+
|
90
|
+
def unread!
|
91
|
+
flag("seen" => false)
|
92
|
+
end
|
93
|
+
|
94
|
+
def flagged!
|
95
|
+
flag("flagged" => true)
|
96
|
+
end
|
97
|
+
|
98
|
+
def unflagged!
|
99
|
+
flag("flagged" => false)
|
100
|
+
end
|
101
|
+
|
102
|
+
def answered!
|
103
|
+
flag("answered" => true)
|
104
|
+
end
|
105
|
+
|
106
|
+
def unanswered!
|
107
|
+
flag("answered" => false)
|
108
|
+
end
|
109
|
+
|
110
|
+
def draft!
|
111
|
+
flag("draft" => true)
|
112
|
+
end
|
113
|
+
|
114
|
+
def undraft!
|
115
|
+
flag("draft" => false)
|
116
|
+
end
|
117
|
+
|
118
|
+
def delete!
|
119
|
+
flag("deleted" => true)
|
120
|
+
end
|
121
|
+
|
122
|
+
def undelete!
|
123
|
+
flag("deleted" => false)
|
124
|
+
end
|
125
|
+
|
126
|
+
# Public: Returns array of messages of the thread a given message is in.
|
127
|
+
def thread
|
128
|
+
get("#{url}/thread")["messages"].map do |m|
|
129
|
+
Message.from_json(account_id, m)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def copy(folder_name, destination_source = nil)
|
134
|
+
copy_move(folder_name, false, destination_source)
|
135
|
+
end
|
136
|
+
|
137
|
+
def move(folder_name, destination_source = nil)
|
138
|
+
copy_move(folder_name, true, destination_source)
|
139
|
+
end
|
140
|
+
|
141
|
+
private
|
142
|
+
def url
|
143
|
+
"/2.0/accounts/#{account_id}/messages/#{message_id}"
|
144
|
+
end
|
145
|
+
|
146
|
+
def copy_move(folder_name, move, destination_source)
|
147
|
+
raise ArgumentError.new("Valid values for 'move' flag are 1 and 0") unless [true, false].include? move
|
148
|
+
destination = folder_name.to_s
|
149
|
+
raise ArgumentError.new("Destination folder cannot be empty") if destination.empty?
|
150
|
+
options = {:dst_folder => destination, :move => move ? 1 :0}
|
151
|
+
options[:dst_source] = destination_source if destination_source
|
152
|
+
post(url, options)['success']
|
153
|
+
end
|
154
|
+
|
155
|
+
def flag(value = {})
|
156
|
+
response = post("#{url}/flags", value)
|
157
|
+
response["success"]
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
module ContextIO
|
2
|
+
class OAuthProvider < Resource
|
3
|
+
# @api public
|
4
|
+
# @return [String] The identification of the OAuth provider. This must be
|
5
|
+
# either "GMAIL" or "GOOGLEAPPSMARKETPLACE".
|
6
|
+
attr_reader :type
|
7
|
+
|
8
|
+
# @api public
|
9
|
+
# @return [String] The OAuth consumer key.
|
10
|
+
attr_reader :consumer_key
|
11
|
+
|
12
|
+
# @api public
|
13
|
+
# @return [String] The OAuth consumer secret.
|
14
|
+
attr_reader :consumer_secret
|
15
|
+
|
16
|
+
# Get all OAuth providers configured
|
17
|
+
#
|
18
|
+
# @api public
|
19
|
+
#
|
20
|
+
# @return [Array<ContextIO::OAuthProvider>] All OAuth providers.
|
21
|
+
def self.all
|
22
|
+
get('/2.0/oauth_providers').map { |provider| from_json(provider) }
|
23
|
+
end
|
24
|
+
|
25
|
+
# Create an OAuth provider
|
26
|
+
#
|
27
|
+
# @api public
|
28
|
+
#
|
29
|
+
# @param ['GMAIL', 'GOOGLEAPPSMARKETPLACE', :gmail, :googleappsmarketplace]
|
30
|
+
# type The identification of the OAuth provider.
|
31
|
+
# @param [#to_s] consumer_key The OAuth consumer key.
|
32
|
+
# @param [#to_s] consumer_secret The OAuth consumer secret.
|
33
|
+
#
|
34
|
+
# @return [true, false] Whether the create succeeded or not.
|
35
|
+
def self.create(type, consumer_key, consumer_secret)
|
36
|
+
post('/2.0/oauth_providers', {
|
37
|
+
:type => type.to_s.upcase,
|
38
|
+
:provider_consumer_key => consumer_key.to_s,
|
39
|
+
:provider_consumer_secret => consumer_secret.to_s
|
40
|
+
})['success']
|
41
|
+
end
|
42
|
+
|
43
|
+
# Retrieve an OAuth provider
|
44
|
+
#
|
45
|
+
# @api public
|
46
|
+
#
|
47
|
+
# @param [#to_s] consumer_key The OAuth consumer key.
|
48
|
+
#
|
49
|
+
# @return [ContextIO::OAuthProvider] The OAuth provider.
|
50
|
+
def self.find(consumer_key)
|
51
|
+
from_json(get("/2.0/oauth_providers/#{consumer_key}"))
|
52
|
+
end
|
53
|
+
|
54
|
+
# Destroy the OAuth provider
|
55
|
+
#
|
56
|
+
# @api public
|
57
|
+
#
|
58
|
+
# @return [true, false] Whether the destroy was successful or not.
|
59
|
+
def destroy
|
60
|
+
delete("/2.0/oauth_providers/#@consumer_key")['success']
|
61
|
+
end
|
62
|
+
|
63
|
+
# Create an OAuth provider with the JSON data from Context.IO.
|
64
|
+
#
|
65
|
+
# @api private
|
66
|
+
#
|
67
|
+
# @param [Hash] json The parsed JSON object returned by a Context.IO API
|
68
|
+
# request. See their documentation for possible keys.
|
69
|
+
#
|
70
|
+
# @return [ContextIO::OAuthProvider] An OAuth provider with the given
|
71
|
+
# attributes.
|
72
|
+
def self.from_json(json)
|
73
|
+
provider = new
|
74
|
+
provider.instance_eval do
|
75
|
+
@type = json['type']
|
76
|
+
@consumer_key = json['provider_consumer_key']
|
77
|
+
@consumer_secret = json['provider_consumer_secret']
|
78
|
+
end
|
79
|
+
|
80
|
+
provider
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module ContextIO
|
2
|
+
# Methods for sending HTTP requests
|
3
|
+
#
|
4
|
+
# @api private
|
5
|
+
module Request
|
6
|
+
# Perform an HTTP DELETE request
|
7
|
+
#
|
8
|
+
# @param [String] path The path to request.
|
9
|
+
# @param [Hash] params Parameters to put in the query part of the URL
|
10
|
+
#
|
11
|
+
# @return [Hash, Array, Object] The parsed JSON response.
|
12
|
+
def delete(path, params={})
|
13
|
+
request(:delete, path, params)
|
14
|
+
end
|
15
|
+
|
16
|
+
# Perform an HTTP GET request
|
17
|
+
#
|
18
|
+
# @param [String] path The path to request.
|
19
|
+
# @param [Hash] params The parameters to put in the query part of the URL.
|
20
|
+
#
|
21
|
+
# @return [Hash, Array, Object] The parsed JSON response.
|
22
|
+
def get(path, params={})
|
23
|
+
request(:get, path, params)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Perform an HTTP POST request
|
27
|
+
#
|
28
|
+
# @param [String] path The path to request.
|
29
|
+
# @param [Hash] params The parameters to put in the body of the request.
|
30
|
+
#
|
31
|
+
# @return [Hash, Array, Object] The parsed JSON response.
|
32
|
+
def post(path, params={})
|
33
|
+
request(:post, path, params)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Perform an HTTP PUT request
|
37
|
+
#
|
38
|
+
# @param [String] path The path to request.
|
39
|
+
# @param [Hash] The parameters to put in the body of the request.
|
40
|
+
#
|
41
|
+
# @return [Hash, Array, Object] The parsed JSON response.
|
42
|
+
def put(path, params={})
|
43
|
+
request(:put, path, params)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Perform an HTTP request
|
47
|
+
#
|
48
|
+
# @param [:delete, :get, :put, :post] method The HTTP method to send.
|
49
|
+
# @param [String] path The path to request.
|
50
|
+
# @param [Hash] The parameters to put in the query part of the URL (for
|
51
|
+
# DELETE and GET requests) or in the body of the request (for POST and PUT
|
52
|
+
# requests).
|
53
|
+
#
|
54
|
+
# @return [Hash, Array, Object] The parsed JSON response.
|
55
|
+
def request(method, path, params)
|
56
|
+
response = connection(params.delete(:raw)).send(method) do |request|
|
57
|
+
case method.to_sym
|
58
|
+
when :delete, :get, :put
|
59
|
+
request.url(path, params)
|
60
|
+
when :post
|
61
|
+
request.path = path
|
62
|
+
request.body = params unless params.empty?
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
response.body
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
require 'simple_oauth'
|
3
|
+
|
4
|
+
module ContextIO
|
5
|
+
module Request
|
6
|
+
# Faraday middleware for handling the OAuth header
|
7
|
+
#
|
8
|
+
# @api private
|
9
|
+
class ContextIOOAuth < Faraday::Middleware
|
10
|
+
# Add the OAuth header
|
11
|
+
#
|
12
|
+
# @param [Hash] env The Rack environment
|
13
|
+
#
|
14
|
+
# @return [Array] The Rack response
|
15
|
+
def call(env)
|
16
|
+
params = env[:body] || {}
|
17
|
+
signature_params = params
|
18
|
+
params.each do |key, value|
|
19
|
+
signature_params = {} if value.respond_to?(:content_type)
|
20
|
+
end
|
21
|
+
header = SimpleOAuth::Header.new(env[:method], env[:url], signature_params, @options)
|
22
|
+
env[:request_headers]['Authorization'] = header.to_s
|
23
|
+
|
24
|
+
@app.call(env)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Initialize the OAuth middleware
|
28
|
+
#
|
29
|
+
# @param [#call] The next Rack middleware of app to call.
|
30
|
+
# @param [Hash] options The authentication options to use for OAuth
|
31
|
+
# authentication.
|
32
|
+
# @option options [String] :consumer_key The OAuth consumer key
|
33
|
+
# @option options [String] :consumer_secret The OAuth consumer secret
|
34
|
+
# @option options [nil] :token The OAuth token, should be nil since
|
35
|
+
# Context.IO doesn't support three-legged authentication.
|
36
|
+
# @option options [nil] :token_secret The Oauth token secret, should be
|
37
|
+
# nil since Context.IO doesn't support three-legged authentication.
|
38
|
+
def initialize(app, options)
|
39
|
+
@app, @options = app, options
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|