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.
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
+