contextio 0.5.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +4 -0
- data/.gitignore +8 -0
- data/.rspec +1 -0
- data/.yardopts +1 -0
- data/ChangeLog.md +5 -0
- data/Gemfile +3 -0
- data/LICENSE.md +20 -0
- data/README.md +62 -22
- data/Rakefile +46 -36
- data/contextio.gemspec +30 -0
- data/lib/contextio.rb +69 -583
- data/lib/contextio/account.rb +132 -0
- data/lib/contextio/account_collection.rb +57 -0
- data/lib/contextio/account_sync_data.rb +22 -0
- data/lib/contextio/api.rb +162 -0
- data/lib/contextio/api/association_helpers.rb +17 -0
- data/lib/contextio/api/resource.rb +230 -0
- data/lib/contextio/api/resource_collection.rb +174 -0
- data/lib/contextio/api/url_builder.rb +153 -0
- data/lib/contextio/body_part.rb +45 -0
- data/lib/contextio/body_part_collection.rb +13 -0
- data/lib/contextio/connect_token.rb +57 -0
- data/lib/contextio/connect_token_collection.rb +44 -0
- data/lib/contextio/contact.rb +43 -0
- data/lib/contextio/contact_collection.rb +21 -0
- data/lib/contextio/email_address.rb +53 -0
- data/lib/contextio/email_address_collection.rb +21 -0
- data/lib/contextio/email_settings.rb +146 -0
- data/lib/contextio/file.rb +92 -0
- data/lib/contextio/file_collection.rb +13 -0
- data/lib/contextio/folder.rb +56 -0
- data/lib/contextio/folder_collection.rb +18 -0
- data/lib/contextio/folder_sync_data.rb +32 -0
- data/lib/contextio/message.rb +96 -0
- data/lib/contextio/message_collection.rb +35 -0
- data/lib/contextio/oauth_provider.rb +29 -0
- data/lib/contextio/oauth_provider_collection.rb +46 -0
- data/lib/contextio/source.rb +55 -0
- data/lib/contextio/source_collection.rb +41 -0
- data/lib/contextio/source_sync_data.rb +23 -0
- data/lib/contextio/thread.rb +15 -0
- data/lib/contextio/thread_collection.rb +25 -0
- data/lib/contextio/version.rb +11 -0
- data/lib/contextio/webhook.rb +39 -0
- data/lib/contextio/webhook_collection.rb +26 -0
- data/spec/config.yml.example +3 -0
- data/spec/contextio/account_collection_spec.rb +78 -0
- data/spec/contextio/account_spec.rb +52 -0
- data/spec/contextio/api/association_helpers_spec.rb +28 -0
- data/spec/contextio/api/resource_collection_spec.rb +286 -0
- data/spec/contextio/api/resource_spec.rb +467 -0
- data/spec/contextio/api/url_builder_spec.rb +78 -0
- data/spec/contextio/api_spec.rb +123 -0
- data/spec/contextio/connect_token_collection_spec.rb +74 -0
- data/spec/contextio/connect_token_spec.rb +58 -0
- data/spec/contextio/email_settings_spec.rb +112 -0
- data/spec/contextio/oauth_provider_collection_spec.rb +36 -0
- data/spec/contextio/oauth_provider_spec.rb +120 -0
- data/spec/contextio/source_collection_spec.rb +57 -0
- data/spec/contextio/source_spec.rb +52 -0
- data/spec/contextio/version_spec.rb +10 -0
- data/spec/contextio_spec.rb +64 -0
- data/spec/spec_helper.rb +17 -0
- metadata +234 -12
- data/README.textile +0 -29
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'contextio/api/resource'
|
2
|
+
|
3
|
+
class ContextIO
|
4
|
+
class Contact
|
5
|
+
include ContextIO::API::Resource
|
6
|
+
|
7
|
+
self.primary_key = :email
|
8
|
+
self.association_name = :contact
|
9
|
+
|
10
|
+
belongs_to :account
|
11
|
+
|
12
|
+
lazy_attributes :emails, :name, :thumbnail, :last_received, :last_sent,
|
13
|
+
:count
|
14
|
+
private :last_received, :last_sent
|
15
|
+
|
16
|
+
def email
|
17
|
+
@email ||= emails.first
|
18
|
+
end
|
19
|
+
|
20
|
+
def last_received_at
|
21
|
+
last_received ? Time.at(last_received) : nil
|
22
|
+
end
|
23
|
+
|
24
|
+
def last_sent_at
|
25
|
+
last_sent ? Time.at(last_sent) : nil
|
26
|
+
end
|
27
|
+
|
28
|
+
# Poor man's has_many
|
29
|
+
def threads
|
30
|
+
account.threads.where(email: email)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Poor man's has_many
|
34
|
+
def messages
|
35
|
+
account.messages.where(email: email)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Poor man's has_many
|
39
|
+
def files
|
40
|
+
account.files.where(email: email)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require_relative 'api/resource_collection'
|
2
|
+
require_relative 'contact'
|
3
|
+
|
4
|
+
class ContextIO
|
5
|
+
class ContactCollection
|
6
|
+
include ContextIO::API::ResourceCollection
|
7
|
+
|
8
|
+
self.resource_class = ContextIO::Contact
|
9
|
+
self.association_name = :contacts
|
10
|
+
|
11
|
+
belongs_to :account
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
# The request that comes back is not formatted the same as other
|
16
|
+
# collections, so we need to override it and pick out the right key.
|
17
|
+
def attribute_hashes
|
18
|
+
@attribute_hashes ||= api.request(:get, resource_url, where_constraints)['matches']
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'contextio/api/association_helpers'
|
2
|
+
|
3
|
+
class ContextIO
|
4
|
+
class EmailAddress
|
5
|
+
def self.association_name
|
6
|
+
:email_address
|
7
|
+
end
|
8
|
+
ContextIO::API::AssociationHelpers.register_resource(self, :email_address)
|
9
|
+
|
10
|
+
# (see ContextIO#api)
|
11
|
+
attr_reader :api, :account, :email, :validated, :primary
|
12
|
+
private :validated, :primary
|
13
|
+
#
|
14
|
+
# @private
|
15
|
+
#
|
16
|
+
# For internal use only. Users of this gem shouldn't be calling this
|
17
|
+
# directly.
|
18
|
+
#
|
19
|
+
# @param [API] api A handle on the Context.IO API.
|
20
|
+
# @param [Hash{String, Symbol => String, Numeric, Boolean}] options A Hash
|
21
|
+
# of attributes describing the resource.
|
22
|
+
def initialize(api, options = {})
|
23
|
+
@api = api
|
24
|
+
@account = options.delete(:account) || options.delete('account')
|
25
|
+
|
26
|
+
options.each do |key, value|
|
27
|
+
instance_variable_set("@#{key}", value)
|
28
|
+
|
29
|
+
unless self.respond_to?(key)
|
30
|
+
define_singleton_method(key) do
|
31
|
+
instance_variable_get("@#{key}")
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def validated?
|
38
|
+
!!validated
|
39
|
+
end
|
40
|
+
|
41
|
+
def primary?
|
42
|
+
!!primary
|
43
|
+
end
|
44
|
+
|
45
|
+
def set_primary
|
46
|
+
api.request(:post, resource_url, primary: 1)['success']
|
47
|
+
end
|
48
|
+
|
49
|
+
def delete
|
50
|
+
api.request(:delete, resource_url)['success']
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require_relative 'api/resource_collection'
|
2
|
+
require_relative 'email_address'
|
3
|
+
|
4
|
+
class ContextIO
|
5
|
+
class EmailAddressCollection
|
6
|
+
include ContextIO::API::ResourceCollection
|
7
|
+
|
8
|
+
self.resource_class = ContextIO::EmailAddress
|
9
|
+
self.association_name = :email_addresses
|
10
|
+
|
11
|
+
belongs_to :account
|
12
|
+
|
13
|
+
def create(address)
|
14
|
+
result_hash = api.request(:post, resource_url, email_address: address)
|
15
|
+
|
16
|
+
result_hash.delete('success')
|
17
|
+
|
18
|
+
resource_class.new(api, result_hash)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
class ContextIO
|
2
|
+
# Represents the IMAP settings for a given email address. The API
|
3
|
+
# documentation refers to this as 'discovery'. This process is hit-and-miss.
|
4
|
+
# If noting is found, many attributes are likely to be nil and blank. Check
|
5
|
+
# {EmailSettings#found? EmailSettings#found?}.
|
6
|
+
class EmailSettings
|
7
|
+
# (see ContextIO#api)
|
8
|
+
attr_reader :api
|
9
|
+
|
10
|
+
# @!attribute [r] type
|
11
|
+
# @return [String] The type og the provider (like 'gmail'). Will fetch from
|
12
|
+
# the API if necessary.
|
13
|
+
def type
|
14
|
+
return @type if instance_variable_defined?(:@type)
|
15
|
+
|
16
|
+
fetch_attributes
|
17
|
+
|
18
|
+
@type
|
19
|
+
end
|
20
|
+
|
21
|
+
# @!attribute [r] documentation
|
22
|
+
# @return [Array<String>] A list of documentation pages that pertain to the
|
23
|
+
# provider. Will fetch from the API if necessary.
|
24
|
+
def documentation
|
25
|
+
return @documentation if instance_variable_defined?(:@documentation)
|
26
|
+
|
27
|
+
fetch_attributes
|
28
|
+
|
29
|
+
@documentation
|
30
|
+
end
|
31
|
+
|
32
|
+
# @!attribute [r] email
|
33
|
+
# @return [String] The email address associated with these IMAP settings.
|
34
|
+
attr_reader :email, :source_type
|
35
|
+
|
36
|
+
# Not sure why, but the below comment has to be below the attr_reader call
|
37
|
+
# above.
|
38
|
+
|
39
|
+
# @!attribute [r] source_type
|
40
|
+
# @return [String] The only source type currently supported by the API is
|
41
|
+
# 'IMAP'.
|
42
|
+
|
43
|
+
# (see ContextIO::OAuthProviderCollection#initialize)
|
44
|
+
# @param [String] email The email address to fetch the settings for.
|
45
|
+
# @param [String] source_type The only source type currently supported by
|
46
|
+
# the API is 'IMAP'.
|
47
|
+
def initialize(api, email, source_type = 'IMAP')
|
48
|
+
@api = api
|
49
|
+
@email = email
|
50
|
+
@source_type = source_type
|
51
|
+
end
|
52
|
+
|
53
|
+
# @!attribute [r] resource_url
|
54
|
+
# @return [String] The path for discovering email settings.
|
55
|
+
def resource_url
|
56
|
+
'discovery'
|
57
|
+
end
|
58
|
+
|
59
|
+
# @!attribute [r] found?
|
60
|
+
# @return [Boolean] Whether the settings were able to be fetched. Will fetch
|
61
|
+
# from the API if necessary.
|
62
|
+
def found?
|
63
|
+
found
|
64
|
+
end
|
65
|
+
|
66
|
+
# @!attribute [r] server
|
67
|
+
# @return [String] FQDN of the IMAP server. Will fetch from the API if
|
68
|
+
# necessary.
|
69
|
+
def server
|
70
|
+
imap['server']
|
71
|
+
end
|
72
|
+
|
73
|
+
# @!attribute [r] username
|
74
|
+
# @return [String] The username for authentication purposes. Will fetch
|
75
|
+
# from the API if necessary.
|
76
|
+
def username
|
77
|
+
imap['username']
|
78
|
+
end
|
79
|
+
|
80
|
+
# @!attribute [r] port
|
81
|
+
# @return [Integer] The network port the IMAP server is listening on. Will
|
82
|
+
# fetch from the API if necessary.
|
83
|
+
def port
|
84
|
+
imap['port']
|
85
|
+
end
|
86
|
+
|
87
|
+
# @!attribute [r] oauth?
|
88
|
+
# @return [Boolean] Whether the IMAP server supports OAuth or not. Will
|
89
|
+
# fetch from the API if necessary.
|
90
|
+
def oauth?
|
91
|
+
!!imap['oauth']
|
92
|
+
end
|
93
|
+
|
94
|
+
# @!attribute [r] use_ssl?
|
95
|
+
# @return [Boolean] Whether the IMAP server uses SSL for connections. Will
|
96
|
+
# fetch from the API if necessary.
|
97
|
+
def use_ssl?
|
98
|
+
!!imap['use_ssl']
|
99
|
+
end
|
100
|
+
|
101
|
+
private
|
102
|
+
|
103
|
+
# @!attribute [r] imap
|
104
|
+
# @return [Hash{String => String, Boolean, Integer}] Attributes of the IMAP
|
105
|
+
# server in question.
|
106
|
+
def imap
|
107
|
+
return @imap if instance_variable_defined?(:@imap)
|
108
|
+
|
109
|
+
fetch_attributes
|
110
|
+
|
111
|
+
@imap
|
112
|
+
end
|
113
|
+
|
114
|
+
# @!attribute [r] found
|
115
|
+
# @return [Boolean] Whether the settings were able to be fetched. Will fetch
|
116
|
+
# from the API if necessary.
|
117
|
+
def found
|
118
|
+
return @found if instance_variable_defined?(:@found)
|
119
|
+
|
120
|
+
fetch_attributes
|
121
|
+
|
122
|
+
@found
|
123
|
+
end
|
124
|
+
|
125
|
+
# Fetches attributes from the API for the email settings.
|
126
|
+
#
|
127
|
+
# Defines getter methods for any attributes that come back and don't
|
128
|
+
# already have them. This way, if the API expands, the gem will still let
|
129
|
+
# users get attributes we didn't explicitly declare as lazy.
|
130
|
+
def fetch_attributes
|
131
|
+
attr_hashes = api.request(:get, resource_url, 'email' => email, 'source_type' => source_type)
|
132
|
+
|
133
|
+
attr_hashes.each do |key, value|
|
134
|
+
instance_variable_set("@#{key}", value)
|
135
|
+
|
136
|
+
unless respond_to?(key)
|
137
|
+
instance_eval <<-RUBY
|
138
|
+
def #{key}
|
139
|
+
@#{key}
|
140
|
+
end
|
141
|
+
RUBY
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'contextio/api/resource'
|
2
|
+
require 'contextio/source_sync_data'
|
3
|
+
|
4
|
+
class ContextIO
|
5
|
+
class File
|
6
|
+
include ContextIO::API::Resource
|
7
|
+
|
8
|
+
self.primary_key = :file_id
|
9
|
+
self.association_name = :file
|
10
|
+
|
11
|
+
belongs_to :account
|
12
|
+
|
13
|
+
lazy_attributes :size, :type, :subject, :date, :addresses, :personInfo,
|
14
|
+
:email_message_id, :message_id, :date_indexed,
|
15
|
+
:date_received, :file_name, :file_name_structure,
|
16
|
+
:body_section, :content_disposition, :file_id,
|
17
|
+
:is_tnef_part, :gmail_message_id, :gmail_thread_id,
|
18
|
+
:supports_preview, :is_embedded
|
19
|
+
private :date, :addresses, :message_id, :date_indexed, :date_received,
|
20
|
+
:is_tnef_part, :is_embedded, :personInfo
|
21
|
+
|
22
|
+
def received_at
|
23
|
+
@received_at ||= Time.at(date_received)
|
24
|
+
end
|
25
|
+
|
26
|
+
def indexed_at
|
27
|
+
@indexed_at ||= Time.at(indexed_at)
|
28
|
+
end
|
29
|
+
|
30
|
+
def to
|
31
|
+
addresses['to']
|
32
|
+
end
|
33
|
+
|
34
|
+
def from
|
35
|
+
addresses['from']
|
36
|
+
end
|
37
|
+
|
38
|
+
def person_info
|
39
|
+
personInfo
|
40
|
+
end
|
41
|
+
|
42
|
+
def tnef_part?
|
43
|
+
!!is_tnef_part
|
44
|
+
end
|
45
|
+
|
46
|
+
def embedded?
|
47
|
+
!!is_embedded
|
48
|
+
end
|
49
|
+
|
50
|
+
def content
|
51
|
+
@content ||= api.request(:get, "#{resource_url}/content")
|
52
|
+
end
|
53
|
+
|
54
|
+
def content_link
|
55
|
+
@content_link ||= api.raw_request(:get, "#{resource_url}/content", as_link: 1)
|
56
|
+
end
|
57
|
+
|
58
|
+
def related_files
|
59
|
+
return @related_files if @related_files
|
60
|
+
|
61
|
+
attribute_hashes = api.request(:get, "#{resource_url}/related")
|
62
|
+
|
63
|
+
@related_files = FileCollection.new(api, attribute_hashes: attribute_hashes, account: account)
|
64
|
+
|
65
|
+
return @related_files
|
66
|
+
end
|
67
|
+
|
68
|
+
def revisions
|
69
|
+
return @revisions if @revisions
|
70
|
+
|
71
|
+
attribute_hashes = api.request(:get, "#{resource_url}/revisions")
|
72
|
+
|
73
|
+
@revisions = FileCollection.new(api, attribute_hashes: attribute_hashes, account: account)
|
74
|
+
|
75
|
+
return @revisions
|
76
|
+
end
|
77
|
+
|
78
|
+
def sync_data
|
79
|
+
return @sync_data if @sync_data
|
80
|
+
|
81
|
+
sync_hashes = api.request(:get, "#{resource_url}/sync")
|
82
|
+
|
83
|
+
@sync_data = ContextIO::SourceSyncData.new(sync_hashes)
|
84
|
+
|
85
|
+
return @sync_data
|
86
|
+
end
|
87
|
+
|
88
|
+
def sync!
|
89
|
+
api.request(:post, "#{resource_url}/sync")['success']
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require_relative 'api/resource_collection'
|
2
|
+
require_relative 'file'
|
3
|
+
|
4
|
+
class ContextIO
|
5
|
+
class FileCollection
|
6
|
+
include ContextIO::API::ResourceCollection
|
7
|
+
|
8
|
+
self.resource_class = ContextIO::File
|
9
|
+
self.association_name = :files
|
10
|
+
|
11
|
+
belongs_to :account
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'contextio/api/association_helpers'
|
2
|
+
|
3
|
+
class ContextIO
|
4
|
+
class Folder
|
5
|
+
def self.association_name
|
6
|
+
:folder
|
7
|
+
end
|
8
|
+
ContextIO::API::AssociationHelpers.register_resource(self, :folder)
|
9
|
+
|
10
|
+
# (see ContextIO#api)
|
11
|
+
attr_reader :api, :source, :name, :attributes, :delim, :nb_messages,
|
12
|
+
:uidvalidity, :nb_unseen_messages
|
13
|
+
private :attributes
|
14
|
+
|
15
|
+
# @private
|
16
|
+
#
|
17
|
+
# For internal use only. Users of this gem shouldn't be calling this
|
18
|
+
# directly.
|
19
|
+
#
|
20
|
+
# @param [API] api A handle on the Context.IO API.
|
21
|
+
# @param [Hash{String, Symbol => String, Numeric, Boolean}] options A Hash
|
22
|
+
# of attributes describing the resource.
|
23
|
+
def initialize(api, options = {})
|
24
|
+
@api = api
|
25
|
+
@source = options.delete(:source) || options.delete('source')
|
26
|
+
|
27
|
+
options.each do |key, value|
|
28
|
+
instance_variable_set("@#{key}", value)
|
29
|
+
|
30
|
+
unless self.respond_to?(key)
|
31
|
+
define_singleton_method(key) do
|
32
|
+
instance_variable_get("@#{key}")
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def has_children?
|
39
|
+
attributes['HasChildren']
|
40
|
+
end
|
41
|
+
|
42
|
+
def marked?
|
43
|
+
attributes['Marked']
|
44
|
+
end
|
45
|
+
|
46
|
+
def imap_attributes
|
47
|
+
attributes
|
48
|
+
end
|
49
|
+
|
50
|
+
def messages
|
51
|
+
association_class = ContextIO::API::AssociationHelpers.class_for_association_name(:messages)
|
52
|
+
|
53
|
+
@messages ||= association_class.new(api, folder: self)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|