nylas 4.0.0.rc2 → 4.0.0.rc3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +5 -5
  2. data/lib/nylas.rb +48 -5
  3. data/lib/nylas/account.rb +31 -0
  4. data/lib/nylas/api.rb +86 -2
  5. data/lib/nylas/calendar.rb +27 -0
  6. data/lib/nylas/collection.rb +46 -19
  7. data/lib/nylas/constraints.rb +12 -4
  8. data/lib/nylas/contact.rb +10 -2
  9. data/lib/nylas/current_account.rb +1 -4
  10. data/lib/nylas/delta.rb +46 -0
  11. data/lib/nylas/deltas.rb +17 -0
  12. data/lib/nylas/deltas_collection.rb +36 -0
  13. data/lib/nylas/draft.rb +51 -0
  14. data/lib/nylas/email_address.rb +1 -5
  15. data/lib/nylas/errors.rb +13 -0
  16. data/lib/nylas/event.rb +42 -0
  17. data/lib/nylas/event_collection.rb +13 -0
  18. data/lib/nylas/file.rb +58 -0
  19. data/lib/nylas/folder.rb +22 -0
  20. data/lib/nylas/http_client.rb +38 -29
  21. data/lib/nylas/im_address.rb +0 -5
  22. data/lib/nylas/label.rb +22 -0
  23. data/lib/nylas/message.rb +45 -0
  24. data/lib/nylas/message_headers.rb +25 -0
  25. data/lib/nylas/message_tracking.rb +11 -0
  26. data/lib/nylas/model.rb +75 -24
  27. data/lib/nylas/model/attributable.rb +2 -2
  28. data/lib/nylas/model/attributes.rb +3 -1
  29. data/lib/nylas/native_authentication.rb +31 -0
  30. data/lib/nylas/new_message.rb +29 -0
  31. data/lib/nylas/nylas_date.rb +5 -2
  32. data/lib/nylas/participant.rb +10 -0
  33. data/lib/nylas/phone_number.rb +0 -5
  34. data/lib/nylas/physical_address.rb +0 -5
  35. data/lib/nylas/raw_message.rb +15 -0
  36. data/lib/nylas/recurrence.rb +9 -0
  37. data/lib/nylas/rsvp.rb +18 -0
  38. data/lib/nylas/search_collection.rb +8 -0
  39. data/lib/nylas/thread.rb +52 -0
  40. data/lib/nylas/timespan.rb +18 -0
  41. data/lib/nylas/types.rb +62 -9
  42. data/lib/nylas/version.rb +1 -1
  43. data/lib/nylas/web_page.rb +0 -5
  44. data/lib/nylas/webhook.rb +19 -0
  45. metadata +31 -7
@@ -3,10 +3,7 @@ module Nylas
3
3
  # @see https://docs.nylas.com/reference#account
4
4
  class CurrentAccount
5
5
  include Model
6
-
7
- self.read_only = true
8
- self.searchable = false
9
- self.collectionable = false
6
+ self.showable = true
10
7
 
11
8
  self.resources_path = "/account"
12
9
 
@@ -0,0 +1,46 @@
1
+ module Nylas
2
+ # Ruby object to represent a single change. Used both when receiving a webhook, as well as the deltas API.
3
+ # @see https://docs.nylas.com/reference#receiving-notifications
4
+ # @see https://docs.nylas.com/reference#deltas
5
+ class Delta
6
+ include Model::Attributable
7
+ attribute :id, :string
8
+ attribute :type, :string
9
+ attribute :object, :string
10
+ attribute :event, :string
11
+ attribute :cursor, :string
12
+ attribute :namespace_id, :string
13
+ attribute :account_id, :string
14
+
15
+ attribute :date, :unix_timestamp
16
+ attribute :metadata, :hash
17
+ attribute :object_attributes, :hash
18
+
19
+ def model
20
+ return nil if object.nil?
21
+ @model ||= Types.registry[object.to_sym].cast(object_attributes_with_ids)
22
+ end
23
+
24
+ def object_attributes_with_ids
25
+ (object_attributes || {}).merge(id: id, account_id: account_id)
26
+ end
27
+ end
28
+
29
+ # Casts Delta data from either a webhook or a delta stream to a Delta
30
+ class DeltaType < Types::ModelType
31
+ def initialize
32
+ super(model: Delta)
33
+ end
34
+
35
+ def cast(data)
36
+ data = if data.key?(:object_data)
37
+ object_data = data.delete(:object_data)
38
+ data.merge(object_data)
39
+ else
40
+ data
41
+ end
42
+ data[:object_attributes] = data.delete(:attributes)
43
+ super(data)
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,17 @@
1
+ module Nylas
2
+ # Ruby object to represent a collection of changes. Used both when receiving a webhook, as well as the
3
+ # deltas API.
4
+ # @see https://docs.nylas.com/reference#receiving-notifications
5
+ # @see https://docs.nylas.com/reference#deltas
6
+ class Deltas
7
+ include Model
8
+ self.resources_path = "/delta"
9
+ allows_operations(filterable: true)
10
+ has_n_of_attribute :deltas, :delta
11
+ attribute :cursor_start, :string
12
+ attribute :cursor_end, :string
13
+
14
+ extend Forwardable
15
+ def_delegators :deltas, :count, :length, :each, :map, :first, :to_a, :empty?
16
+ end
17
+ end
@@ -0,0 +1,36 @@
1
+ module Nylas
2
+ # Special collection for delta objects
3
+ class DeltasCollection < Collection
4
+ attr_accessor :deltas
5
+ extend Forwardable
6
+ def_delegators :execute, :cursor_start, :cursor_end,
7
+ :count, :each, :to_h, :to_a, :empty?
8
+
9
+ def initialize(api:, constraints: nil, model: Deltas)
10
+ super(api: api, model: model, constraints: constraints)
11
+ end
12
+
13
+ def latest_cursor
14
+ api.execute(method: :post, path: "#{resources_path}/latest_cursor")[:cursor]
15
+ end
16
+
17
+ def latest
18
+ since(latest_cursor)
19
+ end
20
+
21
+ def since(cursor)
22
+ where(cursor: cursor)
23
+ end
24
+
25
+ def next_page(*)
26
+ return nil if empty?
27
+ where(cursor: cursor_end)
28
+ end
29
+
30
+ # Retrieves the data from the API for the particular constraints
31
+ # @return [Detlas]
32
+ def execute
33
+ self.deltas ||= Deltas.new(api.execute(to_be_executed))
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,51 @@
1
+ module Nylas
2
+ # Ruby representatin of a Nylas Draft object
3
+ # @see https://docs.nylas.com/reference#drafts
4
+ class Draft
5
+ include Model
6
+ self.resources_path = "/drafts"
7
+ allows_operations(creatable: true, showable: true, listable: true, updatable: true, destroyable: true)
8
+
9
+ attribute :id, :string
10
+ attribute :object, :string
11
+ attribute :version, :integer
12
+ attribute :account_id, :string
13
+ attribute :thread_id, :string
14
+ attribute :reply_to_message_id, :string
15
+
16
+ has_n_of_attribute :to, :email_address
17
+ has_n_of_attribute :from, :email_address
18
+ has_n_of_attribute :cc, :email_address
19
+ has_n_of_attribute :bcc, :email_address
20
+ has_n_of_attribute :reply_to, :email_address
21
+
22
+ attribute :date, :unix_timestamp
23
+ attribute :subject, :string
24
+ attribute :snippet, :string
25
+ attribute :body, :string
26
+ attribute :starred, :boolean
27
+ attribute :unread, :boolean
28
+
29
+ has_n_of_attribute :events, :event
30
+ has_n_of_attribute :files, :file
31
+ attribute :folder, :label
32
+ has_n_of_attribute :labels, :label
33
+
34
+ def send!
35
+ save
36
+ execute(method: :post, path: "/send", payload: JSON.dump(draft_id: id, version: version))
37
+ end
38
+
39
+ def starred?
40
+ starred
41
+ end
42
+
43
+ def unread?
44
+ unread
45
+ end
46
+
47
+ def destroy
48
+ execute(method: :delete, path: resource_path, payload: attributes.serialize(keys: [:version]))
49
+ end
50
+ end
51
+ end
@@ -5,10 +5,6 @@ module Nylas
5
5
  include Model::Attributable
6
6
  attribute :type, :string
7
7
  attribute :email, :string
8
- end
9
-
10
- # Serializes, Deserializes between {EmailAddress} objects and a {Hash}
11
- class EmailAddressType < Types::HashType
12
- casts_to EmailAddress
8
+ attribute :name, :string
13
9
  end
14
10
  end
data/lib/nylas/errors.rb CHANGED
@@ -1,6 +1,17 @@
1
1
  module Nylas
2
2
  Error = Class.new(::StandardError)
3
3
 
4
+ class ModelActionError < Error; end
5
+ class ModelNotFilterableError < ModelActionError; end
6
+ class ModelNotCreatableError < ModelActionError; end
7
+ class ModelNotShowableError < ModelActionError; end
8
+ class ModelNotAvailableAsRawError < ModelActionError; end
9
+ class ModelNotListableError < ModelActionError; end
10
+ class ModelNotFilterableError < ModelActionError; end
11
+ class ModelNotSearchableError < ModelActionError; end
12
+ class ModelNotUpdatableError < ModelActionError; end
13
+ class ModelNotDestroyableError < ModelActionError; end
14
+
4
15
  # Indicates that a given method needs an access token to work.
5
16
  class NoAuthToken < Error
6
17
  def initialize(method_name)
@@ -26,11 +37,13 @@ module Nylas
26
37
  end
27
38
  AccessDenied = Class.new(APIError)
28
39
  ResourceNotFound = Class.new(APIError)
40
+ MethodNotAllowed = Class.new(APIError)
29
41
  InvalidRequest = Class.new(APIError)
30
42
  MessageRejected = Class.new(APIError)
31
43
  SendingQuotaExceeded = Class.new(APIError)
32
44
  ServiceUnavailable = Class.new(APIError)
33
45
  BadGateway = Class.new(APIError)
34
46
  InternalError = Class.new(APIError)
47
+ EndpointNotYetImplemented = Class.new(APIError)
35
48
  MailProviderError = Class.new(APIError)
36
49
  end
@@ -0,0 +1,42 @@
1
+ module Nylas
2
+ # Structure to represent a the Event Schema.
3
+ # @see https://docs.nylas.com/reference#events
4
+ class Event
5
+ include Model
6
+ self.resources_path = "/events"
7
+ allows_operations(creatable: true, listable: true, filterable: true, showable: true, updatable: true,
8
+ destroyable: true)
9
+
10
+ attribute :id, :string
11
+ attribute :object, :string
12
+ attribute :account_id, :string
13
+ attribute :calendar_id, :string
14
+ attribute :master_event_id, :string
15
+ attribute :message_id, :string
16
+
17
+ attribute :busy, :boolean
18
+ attribute :description, :string
19
+ attribute :location, :string
20
+ attribute :owner, :string
21
+ attribute :recurrence, :recurrence
22
+ has_n_of_attribute :participants, :participant
23
+ attribute :read_only, :boolean
24
+ attribute :status, :string
25
+ attribute :title, :string
26
+ attribute :when, :timespan
27
+
28
+ def busy?
29
+ busy
30
+ end
31
+
32
+ def read_only?
33
+ read_only
34
+ end
35
+
36
+ def rsvp(status, notify_participants:)
37
+ rsvp = Rsvp.new(api: api, status: status, notify_participants: notify_participants,
38
+ event_id: id, account_id: account_id)
39
+ rsvp.save
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,13 @@
1
+ module Nylas
2
+ # Syntactical sugar methods for some of the Event's filters
3
+ # @see https://docs.nylas.com/reference#get-events
4
+ class EventCollection < Collection
5
+ def expand_recurring
6
+ where(expand_recurring: true)
7
+ end
8
+
9
+ def show_cancelled
10
+ where(show_cancelled: true)
11
+ end
12
+ end
13
+ end
data/lib/nylas/file.rb ADDED
@@ -0,0 +1,58 @@
1
+ module Nylas
2
+ # Structure to represent a the File Schema.
3
+ # @see https://docs.nylas.com/reference#events
4
+ class File
5
+ include Model
6
+ self.resources_path = "/files"
7
+ allows_operations(listable: true, showable: true, filterable: true, creatable: true)
8
+
9
+ attribute :id, :string
10
+ attribute :account_id, :string
11
+ attribute :content_id, :string
12
+ has_n_of_attribute :message_ids, :string
13
+
14
+ attribute :object, :string
15
+
16
+ attribute :content_type, :string
17
+ attribute :filename, :string
18
+ attribute :size, :integer
19
+
20
+ attr_accessor :file
21
+ # Downloads and caches a local copy of the file.
22
+ # @return [Tempfile] - Local copy of the file
23
+ def download
24
+ return file if file
25
+ self.file = retrieve_file
26
+ end
27
+
28
+ # Redownloads a file even if it's been cached. Closes and unlinks the tempfile to help memory usage.
29
+ def download!
30
+ return download if file.nil?
31
+ file.close
32
+ file.unlink
33
+ self.file = nil
34
+ download
35
+ end
36
+
37
+ def create
38
+ save
39
+ end
40
+
41
+ def save
42
+ raise ModelNotUpdatableError if persisted?
43
+ response = api.execute(path: "/files", method: :post, headers: { multipart: true },
44
+ payload: { file: file })
45
+ attributes.merge(response.first)
46
+ true
47
+ end
48
+
49
+ private def retrieve_file
50
+ response = api.get(path: "#{resource_path}/download")
51
+ filename = response.headers.fetch(:content_disposition, "").gsub("attachment; filename=", "")
52
+ temp_file = Tempfile.new(filename)
53
+ temp_file.write(response.body)
54
+ temp_file.seek(0)
55
+ temp_file
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,22 @@
1
+ module Nylas
2
+ # Structure to represent the Folder Schema
3
+ # @see https://docs.nylas.com/reference#folders
4
+ class Folder
5
+ include Model
6
+ self.resources_path = "/folders"
7
+ self.creatable = true
8
+ self.listable = true
9
+ self.showable = true
10
+ self.filterable = false
11
+ self.updatable = true
12
+ self.destroyable = true
13
+
14
+ attribute :id, :string
15
+ attribute :account_id, :string
16
+
17
+ attribute :object, :string
18
+
19
+ attribute :name, :string
20
+ attribute :display_name, :string
21
+ end
22
+ end
@@ -6,15 +6,18 @@ module Nylas
6
6
  402 => MessageRejected,
7
7
  403 => AccessDenied,
8
8
  404 => ResourceNotFound,
9
+ 405 => MethodNotAllowed,
9
10
  422 => MailProviderError,
10
11
  429 => SendingQuotaExceeded,
11
12
  500 => InternalError,
13
+ 501 => EndpointNotYetImplemented,
12
14
  502 => BadGateway,
13
15
  503 => ServiceUnavailable
14
16
  }.freeze
15
17
 
16
18
  include Logging
17
- attr_accessor :api_server, :default_headers
19
+ attr_accessor :api_server, :service_domain
20
+ attr_writer :default_headers
18
21
  attr_reader :access_token
19
22
  attr_reader :app_id
20
23
  attr_reader :app_secret
@@ -25,7 +28,7 @@ module Nylas
25
28
  # @param api_server [String] (Optional) Which Nylas API Server to connect to. Only change this if
26
29
  # you're using a self-hosted Nylas instance.
27
30
  # @param service_domain [String] (Optional) Host you are authenticating OAuth against.
28
- # @return [Nylas::API]
31
+ # @return [Nylas::HttpClient]
29
32
  def initialize(app_id:, app_secret:, access_token: nil, api_server: "https://api.nylas.com",
30
33
  service_domain: "api.nylas.com")
31
34
  unless api_server.include?("://")
@@ -38,10 +41,14 @@ module Nylas
38
41
  @service_domain = service_domain
39
42
  end
40
43
 
44
+ # @return [Nylas::HttpClient[]
45
+ def as(access_token)
46
+ HttpClient.new(app_id: app_id, access_token: access_token,
47
+ app_secret: app_secret, api_server: api_server, service_domain: service_domain)
48
+ end
49
+
41
50
  # Sends a request to the Nylas API and rai
42
51
  # @param method [Symbol] HTTP method for the API call. Either :get, :post, :delete, or :patch
43
- # @param url [String] (Optional, defaults to nil) - Full URL to access. Deprecated and will be removed in
44
- # 5.0.
45
52
  # @param path [String] (Optional, defaults to nil) - Relative path from the API Base. Preferred way to
46
53
  # execute arbitrary or-not-yet-SDK-ified API commands.
47
54
  # @param headers [Hash] (Optional, defaults to {}) - Additional HTTP headers to include in the payload.
@@ -49,45 +56,47 @@ module Nylas
49
56
  # section of the URI fragment
50
57
  # @param payload [String,Hash] (Optional, defaults to nil) - Body to send with the request.
51
58
  # @return [Array Hash Stringn]
52
- # rubocop:disable Metrics/ParameterLists
53
- def execute(method:, url: nil, path: nil, headers: {}, query: {}, payload: nil)
54
- headers[:params] = query
55
- url ||= url_for_path(path)
56
- resulting_headers = default_headers.merge(headers)
57
- rest_client_execute(method: method, url: url, payload: payload,
58
- headers: resulting_headers) do |response, _request, result|
59
-
59
+ def execute(method:, path: nil, headers: {}, query: {}, payload: nil)
60
+ request = build_request(method: method, path: path, headers: headers, query: query, payload: payload)
61
+ rest_client_execute(**request) do |response, _request, result|
60
62
  response = parse_response(response)
61
63
  handle_failed_response(result: result, response: response)
62
64
  response
63
65
  end
64
66
  end
65
- # rubocop:enable Metrics/ParameterLists
66
67
  inform_on :execute, level: :debug,
67
- also_log: { result: true, values: %i(method url path headers query payload) }
68
+ also_log: { result: true, values: %i[method url path headers query payload] }
69
+
70
+ def build_request(method:, path: nil, headers: {}, query: {}, payload: nil)
71
+ headers[:params] = query
72
+ url ||= url_for_path(path)
73
+ resulting_headers = default_headers.merge(headers)
74
+ { method: method, url: url, payload: payload, headers: resulting_headers }
75
+ end
76
+ # rubocop:enable Metrics/ParameterLists
68
77
 
69
78
  # Syntactical sugar for making GET requests via the API.
70
79
  # @see #execute
71
- def get(path: nil, url: nil, headers: {}, query: {})
72
- execute(method: :get, path: path, query: query, url: url, headers: headers)
80
+ def get(path: nil, headers: {}, query: {})
81
+ execute(method: :get, path: path, query: query, headers: headers)
73
82
  end
74
83
 
75
84
  # Syntactical sugar for making POST requests via the API.
76
85
  # @see #execute
77
- def post(path: nil, url: nil, payload: nil, headers: {}, query: {})
78
- execute(method: :post, path: path, url: url, headers: headers, query: query, payload: payload)
86
+ def post(path: nil, payload: nil, headers: {}, query: {})
87
+ execute(method: :post, path: path, headers: headers, query: query, payload: payload)
79
88
  end
80
89
 
81
90
  # Syntactical sugar for making PUT requests via the API.
82
91
  # @see #execute
83
- def put(path: nil, url: nil, payload:, headers: {}, query: {})
84
- execute(method: :put, path: path, url: url, headers: headers, query: query, payload: payload)
92
+ def put(path: nil, payload:, headers: {}, query: {})
93
+ execute(method: :put, path: path, headers: headers, query: query, payload: payload)
85
94
  end
86
95
 
87
96
  # Syntactical sugar for making DELETE requests via the API.
88
97
  # @see #execute
89
- def delete(path: nil, url: nil, payload: nil, headers: {}, query: {})
90
- execute(method: :delete, path: path, url: url, headers: headers, query: query, payload: payload)
98
+ def delete(path: nil, payload: nil, headers: {}, query: {})
99
+ execute(method: :delete, path: path, headers: headers, query: query, payload: payload)
91
100
  end
92
101
  # rubocop:enable Metrics/ParameterList
93
102
 
@@ -96,7 +105,7 @@ module Nylas
96
105
  headers: headers, &block)
97
106
  end
98
107
  inform_on :rest_client_execute, level: :debug,
99
- also_log: { result: true, values: %i(method url headers payload) }
108
+ also_log: { result: true, values: %i[method url headers payload] }
100
109
 
101
110
  def default_headers
102
111
  @default_headers ||= {
@@ -106,16 +115,16 @@ module Nylas
106
115
  }
107
116
  end
108
117
 
109
- private def parse_response(response)
118
+ def parse_response(response)
110
119
  response.is_a?(Enumerable) ? response : JSON.parse(response, symbolize_names: true)
111
120
  rescue JSON::ParserError
112
121
  response
113
122
  end
123
+ inform_on :parse_response, level: :debug, also_log: { result: true }
114
124
 
115
- private def url_for_path(path)
116
- raise NoAuthToken if @access_token.nil? && (!@app_secret.nil? || !@app_id.nil?)
117
- protocol, domain = @api_server.split("//")
118
- "#{protocol}//#{@access_token}:@#{domain}#{path}"
125
+ def url_for_path(path)
126
+ protocol, domain = api_server.split("//")
127
+ "#{protocol}//#{access_token}:@#{domain}#{path}"
119
128
  end
120
129
 
121
130
  private def handle_failed_response(result:, response:)
@@ -129,7 +138,7 @@ module Nylas
129
138
  return if http_code == 200
130
139
  return unless response.is_a?(Hash)
131
140
  exception = HTTP_CODE_TO_EXCEPTIONS.fetch(http_code, APIError)
132
- raise exception.new(response["type"], response["message"], response.fetch("server_error", nil))
141
+ raise exception.new(response[:type], response[:message], response.fetch(:server_error, nil))
133
142
  end
134
143
  end
135
144
  end