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