context-io 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. data/Gemfile +4 -0
  2. data/LICENSE +21 -0
  3. data/README.md +129 -0
  4. data/Rakefile +121 -0
  5. data/SPEC.md +49 -0
  6. data/context-io.gemspec +96 -0
  7. data/lib/context-io.rb +14 -0
  8. data/lib/context-io/account.rb +254 -0
  9. data/lib/context-io/authentication.rb +23 -0
  10. data/lib/context-io/config.rb +103 -0
  11. data/lib/context-io/connection.rb +45 -0
  12. data/lib/context-io/core_ext/hash.rb +31 -0
  13. data/lib/context-io/error.rb +24 -0
  14. data/lib/context-io/error/bad_request.rb +12 -0
  15. data/lib/context-io/error/client_error.rb +10 -0
  16. data/lib/context-io/error/forbidden.rb +12 -0
  17. data/lib/context-io/error/internal_server_error.rb +10 -0
  18. data/lib/context-io/error/not_found.rb +12 -0
  19. data/lib/context-io/error/payment_required.rb +13 -0
  20. data/lib/context-io/error/server_error.rb +10 -0
  21. data/lib/context-io/error/service_unavailable.rb +13 -0
  22. data/lib/context-io/error/unauthorized.rb +12 -0
  23. data/lib/context-io/file.rb +234 -0
  24. data/lib/context-io/folder.rb +90 -0
  25. data/lib/context-io/message.rb +160 -0
  26. data/lib/context-io/oauth_provider.rb +84 -0
  27. data/lib/context-io/request.rb +70 -0
  28. data/lib/context-io/request/oauth.rb +44 -0
  29. data/lib/context-io/resource.rb +16 -0
  30. data/lib/context-io/response.rb +5 -0
  31. data/lib/context-io/response/parse_json.rb +30 -0
  32. data/lib/context-io/response/raise_client_error.rb +59 -0
  33. data/lib/context-io/response/raise_server_error.rb +32 -0
  34. data/lib/context-io/source.rb +193 -0
  35. data/lib/context-io/version.rb +7 -0
  36. data/spec/account_spec.rb +247 -0
  37. data/spec/contextio_spec.rb +45 -0
  38. data/spec/file_spec.rb +101 -0
  39. data/spec/fixtures/accounts.json +21 -0
  40. data/spec/fixtures/files.json +41 -0
  41. data/spec/fixtures/files_group.json +47 -0
  42. data/spec/fixtures/folders.json +1 -0
  43. data/spec/fixtures/messages.json +1 -0
  44. data/spec/fixtures/oauth_providers.json +12 -0
  45. data/spec/fixtures/sources.json +1 -0
  46. data/spec/folder_spec.rb +48 -0
  47. data/spec/message_spec.rb +294 -0
  48. data/spec/oauth_provider_spec.rb +88 -0
  49. data/spec/source_spec.rb +248 -0
  50. data/spec/spec_helper.rb +4 -0
  51. 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
+