desk_api 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.coveralls.yml +1 -0
- data/.rspec +1 -0
- data/.travis.yml +5 -0
- data/.yardopts +3 -0
- data/Gemfile +15 -0
- data/Guardfile +5 -0
- data/LICENSE +7 -0
- data/README.md +185 -0
- data/Rakefile +15 -0
- data/config.rb +7 -0
- data/desk_api.gemspec +36 -0
- data/lib/desk/action/create.rb +15 -0
- data/lib/desk/action/delete.rb +9 -0
- data/lib/desk/action/embedded.rb +12 -0
- data/lib/desk/action/field.rb +33 -0
- data/lib/desk/action/link.rb +29 -0
- data/lib/desk/action/resource.rb +14 -0
- data/lib/desk/action/search.rb +15 -0
- data/lib/desk/action/update.rb +12 -0
- data/lib/desk/client.rb +67 -0
- data/lib/desk/configuration.rb +132 -0
- data/lib/desk/default.rb +67 -0
- data/lib/desk/error/bad_gateway.rb +10 -0
- data/lib/desk/error/bad_request.rb +10 -0
- data/lib/desk/error/client_error.rb +9 -0
- data/lib/desk/error/configuration_error.rb +8 -0
- data/lib/desk/error/conflict.rb +10 -0
- data/lib/desk/error/forbidden.rb +10 -0
- data/lib/desk/error/gateway_timeout.rb +10 -0
- data/lib/desk/error/internal_server_error.rb +10 -0
- data/lib/desk/error/method_not_allowed.rb +10 -0
- data/lib/desk/error/method_not_supported.rb +9 -0
- data/lib/desk/error/not_acceptable.rb +10 -0
- data/lib/desk/error/not_found.rb +10 -0
- data/lib/desk/error/parse_error.rb +9 -0
- data/lib/desk/error/parser_error.rb +9 -0
- data/lib/desk/error/server_error.rb +9 -0
- data/lib/desk/error/service_unavailable.rb +10 -0
- data/lib/desk/error/too_many_requests.rb +10 -0
- data/lib/desk/error/unauthorized.rb +10 -0
- data/lib/desk/error/unprocessable_entity.rb +10 -0
- data/lib/desk/error/unsupported_media_type.rb +10 -0
- data/lib/desk/error.rb +61 -0
- data/lib/desk/rate_limit.rb +26 -0
- data/lib/desk/request/retry.rb +51 -0
- data/lib/desk/resource/article.rb +10 -0
- data/lib/desk/resource/article_translation.rb +8 -0
- data/lib/desk/resource/attachment.rb +8 -0
- data/lib/desk/resource/case.rb +9 -0
- data/lib/desk/resource/company.rb +8 -0
- data/lib/desk/resource/customer.rb +9 -0
- data/lib/desk/resource/integration_url.rb +9 -0
- data/lib/desk/resource/job.rb +7 -0
- data/lib/desk/resource/label.rb +9 -0
- data/lib/desk/resource/macro.rb +9 -0
- data/lib/desk/resource/macro_action.rb +7 -0
- data/lib/desk/resource/note.rb +7 -0
- data/lib/desk/resource/page.rb +81 -0
- data/lib/desk/resource/reply.rb +8 -0
- data/lib/desk/resource/topic.rb +9 -0
- data/lib/desk/resource/topic_translation.rb +9 -0
- data/lib/desk/resource/user_preference.rb +7 -0
- data/lib/desk/resource.rb +53 -0
- data/lib/desk/resources.json +76 -0
- data/lib/desk/response/raise_error.rb +34 -0
- data/lib/desk/version.rb +3 -0
- data/lib/desk.rb +35 -0
- data/spec/cassettes/Desk_Client/_delete/deletes_a_resource.yml +48 -0
- data/spec/cassettes/Desk_Client/_get/fetches_resources.yml +55 -0
- data/spec/cassettes/Desk_Client/_patch/updates_a_resource.yml +62 -0
- data/spec/cassettes/Desk_Client/_post/creates_a_resource.yml +58 -0
- data/spec/cassettes/Desk_Error/_from_response/can_be_created_from_a_faraday_response.yml +54 -0
- data/spec/cassettes/Desk_Error/_from_response/uses_the_body_message_if_present.yml +54 -0
- data/spec/cassettes/Desk_Resource/_by_url/finds_resources_by_url.yml +313 -0
- data/spec/cassettes/Desk_Resource/_create/creates_a_new_topic.yml +58 -0
- data/spec/cassettes/Desk_Resource/_delete/deletes_a_resource.yml +164 -0
- data/spec/cassettes/Desk_Resource/_delete/throws_an_error_deleting_a_non_deletalbe_resource.yml +56 -0
- data/spec/cassettes/Desk_Resource/_exec_/can_be_forced_to_reload.yml +313 -0
- data/spec/cassettes/Desk_Resource/_exec_/loads_the_current_resource.yml +313 -0
- data/spec/cassettes/Desk_Resource/_method_missing/loads_the_resource_to_find_a_suitable_method.yml +313 -0
- data/spec/cassettes/Desk_Resource/_method_missing/raises_an_error_if_method_does_not_exist.yml +313 -0
- data/spec/cassettes/Desk_Resource/_search/allows_searching_on_search_enabled_resources.yml +54 -0
- data/spec/cassettes/Desk_Resource/_update/throws_an_error_updating_a_user.yml +56 -0
- data/spec/cassettes/Desk_Resource/_update/updates_a_topic.yml +118 -0
- data/spec/cassettes/Desk_Resource_Page/_by_id/loads_the_requested_resource.yml +54 -0
- data/spec/cassettes/Desk_Resource_Page/_page/keeps_the_resource_as_loaded.yml +113 -0
- data/spec/cassettes/Desk_Resource_Page/_page/returns_the_current_page_and_loads_if_page_not_defined.yml +313 -0
- data/spec/cassettes/Desk_Resource_Page/_page/sets_the_resource_to_not_loaded.yml +113 -0
- data/spec/desk/client_spec.rb +133 -0
- data/spec/desk/configuration_spec.rb +183 -0
- data/spec/desk/default_spec.rb +69 -0
- data/spec/desk/error_spec.rb +23 -0
- data/spec/desk/rate_limit_spec.rb +42 -0
- data/spec/desk/request/retry_spec.rb +46 -0
- data/spec/desk/resource_spec.rb +119 -0
- data/spec/desk/resources/page_spec.rb +64 -0
- data/spec/desk_spec.rb +43 -0
- data/spec/spec_helper.rb +39 -0
- metadata +347 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 6b7e54656d496540b4dca275b4288ee34341b3c6
|
4
|
+
data.tar.gz: fadb4549ca2e30c77e92fa0f74aece67f9176ce6
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a14e59d2749dab344223176662a5d20b262d12e010247a76dd04e1a4964dd311fb58e419b1d358eeb5386dd0e11a163df1c5874f84b97591c0a6e2f9fe65ff42
|
7
|
+
data.tar.gz: 2be5d5583bfc5735e010444a06e4b1d220dc12da470c498240a29c0ac9ed8ffeeec64cc249f06227787d53e9fdaff8ad02a01193569d54f0414ca707ecc3030b
|
data/.coveralls.yml
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
service_name: travis-ci
|
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color --order rand --format documentation
|
data/.travis.yml
ADDED
data/.yardopts
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
group :development, :test do |variable|
|
4
|
+
# Testing infrastructure
|
5
|
+
gem 'guard'
|
6
|
+
gem 'guard-rspec'
|
7
|
+
|
8
|
+
if RUBY_PLATFORM =~ /darwin/
|
9
|
+
# OS X integration
|
10
|
+
gem 'ruby_gntp'
|
11
|
+
gem 'rb-fsevent'
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
gemspec
|
data/Guardfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
Copyright (c) 2013 Thomas Stachl <tom@desk.com>
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
4
|
+
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
6
|
+
|
7
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,185 @@
|
|
1
|
+
# desk.com APIv2 [![Build Status](https://secure.travis-ci.org/tstachl/desk.png)](http://travis-ci.org/tstachl/desk) [![Coverage Status](https://coveralls.io/repos/tstachl/desk/badge.png)](https://coveralls.io/r/tstachl/desk) [![Dependency Status](https://gemnasium.com/tstachl/desk.png)](https://gemnasium.com/tstachl/desk)
|
2
|
+
|
3
|
+
desk.com has released v2 of their REST API a few months ago and provides a lot more functionality. You should read up on the current progress of the [API](http://dev.desk.com/API/changelog). This library wraps all of it into an easy to use ruby module. We'll try to keep up with the changes of the API but things might still break unexpectedly.
|
4
|
+
|
5
|
+
## Example
|
6
|
+
This example shows you how to create a new client and establish a connection to the API. It shows the four request methods supported by the desk.com API (`GET`, `POST`, `PATCH` and `DELETE`).
|
7
|
+
|
8
|
+
```ruby
|
9
|
+
client = Desk::Client.new username: 'thomas@example.com', password: 'somepassword', subdomain: 'devel'
|
10
|
+
|
11
|
+
response = client.get '/api/v2/topics'
|
12
|
+
response = client.post '/api/v2/topics', name: 'My new Topic', allow_questions: true
|
13
|
+
response = client.patch '/api/v2/topics/1', name: 'Changed the Topic Name'
|
14
|
+
response = client.delete '/api/v2/topics/1'
|
15
|
+
```
|
16
|
+
|
17
|
+
For ease of use and if you only create one connection to the desk.com API you can use `Desk` directly:
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
Desk.configure do |config|
|
21
|
+
config.username = 'thomas@example.com'
|
22
|
+
config.password = 'somepassword'
|
23
|
+
config.subdomain = 'devel'
|
24
|
+
end
|
25
|
+
|
26
|
+
Desk.get '/api/v2/topics'
|
27
|
+
Desk.post '/api/v2/topics', name: 'My new Topic', allow_questions: true
|
28
|
+
Desk.patch '/api/v2/topics/1', name: 'Changed the Topic Name'
|
29
|
+
Desk.delete '/api/v2/topics/1'
|
30
|
+
```
|
31
|
+
|
32
|
+
## Working with Resources and Collections
|
33
|
+
|
34
|
+
The API supports RESTful resources and so does this wrapper. Those resources are automatically discovered, meaning you can navigate around without having to worry about anything. We also support two finder methods `by_url` and `by_id`, the former works with any resource, the latter needs a collection.
|
35
|
+
|
36
|
+
### Finders
|
37
|
+
```ruby
|
38
|
+
# get the first user and find a case by url.
|
39
|
+
found_case = Desk.users.first.by_url '/api/v2/cases/1'
|
40
|
+
|
41
|
+
# find a case by case number
|
42
|
+
found_case = Desk.cases.by_id 1
|
43
|
+
```
|
44
|
+
|
45
|
+
### Pagination
|
46
|
+
|
47
|
+
As mentioned above you can also navigate between resources and mainly pages of collections.
|
48
|
+
|
49
|
+
```ruby
|
50
|
+
cases = Desk.cases
|
51
|
+
cases.each do |my_case|
|
52
|
+
# do something with the case
|
53
|
+
end
|
54
|
+
# now move on to the next page
|
55
|
+
next_page = cases.next_page
|
56
|
+
next_page.each do |my_case|
|
57
|
+
# do something with the case
|
58
|
+
end
|
59
|
+
# go back to the previous page
|
60
|
+
previous_page = next_page.previous_page
|
61
|
+
# or go to the last page
|
62
|
+
last_page = previous_page.last_page
|
63
|
+
# or go to the first page
|
64
|
+
first_page = last_page.first_page
|
65
|
+
```
|
66
|
+
|
67
|
+
### Links
|
68
|
+
|
69
|
+
Pagination is pretty obvious but the cool part about pagination or rather resources is the auto-linking. As soon as the resource has a link defined, it'll be navigatable:
|
70
|
+
|
71
|
+
```ruby
|
72
|
+
# get the customer of the first case of the first page
|
73
|
+
customer = Desk.cases.first.customer
|
74
|
+
# who sent the first outbound reply of the first email
|
75
|
+
user_name = Desk.cases.select{ |my_case|
|
76
|
+
my_case.type == 'email'
|
77
|
+
}.first.replies.select{ |reply|
|
78
|
+
reply.direction == 'out'
|
79
|
+
}.first.sent_by.name
|
80
|
+
```
|
81
|
+
|
82
|
+
### Lazy loading
|
83
|
+
|
84
|
+
Collections and resources in general are lazily loaded, meaning if you request the cases `Desk.cases` no actual request will be set off until you actually request data. This makes sure only necessary requests are fired and which keeps the overall requests low and spares the [desk.com rate limit](http://dev.desk.com/API/using-the-api/#rate-limits).
|
85
|
+
|
86
|
+
```ruby
|
87
|
+
Desk.cases.page(10).per_page(50).each do |my_case|
|
88
|
+
# in this method chain `.each' is the first method that acutally sends a request
|
89
|
+
end
|
90
|
+
|
91
|
+
# however if you request the current page numer and the resource is not loaded
|
92
|
+
# it'll send a request
|
93
|
+
Desk.cases.page == 1
|
94
|
+
```
|
95
|
+
|
96
|
+
### Create, Update and Delete
|
97
|
+
|
98
|
+
Of course we support creating, updating and deleting resources but not all resources can be deleted or updated or created, if that's the case for the resource you're trying to update, it'll throw a `Desk::Error::MethodNotSupported` error. The specific method won't be defined on the resource either `Desk.cases.first.respond_to?(:delete) == false`.
|
99
|
+
|
100
|
+
```ruby
|
101
|
+
# let's create an article
|
102
|
+
new_article = Desk.articles.create({
|
103
|
+
subject: 'Some Subject',
|
104
|
+
body: 'Some Body',
|
105
|
+
_links: {
|
106
|
+
topic: Desk.topics.first.get_self
|
107
|
+
}
|
108
|
+
})
|
109
|
+
|
110
|
+
# as you can see here a `Resource' always includes a method `.get_self'
|
111
|
+
# which will return the link object to build relationships
|
112
|
+
|
113
|
+
# updating the article
|
114
|
+
updated_article = new_article.update subject: 'Updated Subject'
|
115
|
+
|
116
|
+
# deleting the article
|
117
|
+
if updated_article.delete == true
|
118
|
+
# article has been deleted
|
119
|
+
end
|
120
|
+
|
121
|
+
# ATTENTION: Cases can not be deleted!
|
122
|
+
begin
|
123
|
+
Desk.cases.first.delete
|
124
|
+
rescue Desk::Error::MethodNotSupported => e
|
125
|
+
# too bad
|
126
|
+
end
|
127
|
+
```
|
128
|
+
|
129
|
+
### Getters & Setters
|
130
|
+
|
131
|
+
As you have seen in prior examples for each field on the resource we create a getter and setter. Be careful if a resource is not updatable it won't have a setter specified.
|
132
|
+
|
133
|
+
```ruby
|
134
|
+
customer = Desk.customers.by_id(1)
|
135
|
+
|
136
|
+
puts customer.first_name
|
137
|
+
puts customer.last_name
|
138
|
+
puts customer.title
|
139
|
+
|
140
|
+
# for updates you can either use the setter or a hash
|
141
|
+
|
142
|
+
customer.first_name = 'John'
|
143
|
+
customer.last_name = 'Doe'
|
144
|
+
|
145
|
+
updated_customer = customer.update title: 'Master of the Universe'
|
146
|
+
|
147
|
+
# users are not updatable
|
148
|
+
begin
|
149
|
+
user = Desk.users.first
|
150
|
+
user.name = 'Not updateable'
|
151
|
+
rescue Desk::Error::MethodNotSupported
|
152
|
+
# too bad
|
153
|
+
end
|
154
|
+
```
|
155
|
+
|
156
|
+
### API Errors
|
157
|
+
|
158
|
+
Sometimes the API is going to return errors, eg. Validation Error. In these cases we wrap the API error into a `Desk::Error`. Here are the common errors:
|
159
|
+
|
160
|
+
```ruby
|
161
|
+
Desk::Error::BadRequest #=> 400 Status
|
162
|
+
Desk::Error::Unauthorized #=> 401 Status
|
163
|
+
Desk::Error::Forbidden #=> 403 Status
|
164
|
+
Desk::Error::NotFound #=> 404 Status
|
165
|
+
Desk::Error::MethodNotAllowed #=> 405 Status
|
166
|
+
Desk::Error::NotAcceptable #=> 406 Status
|
167
|
+
Desk::Error::Conflict #=> 409 Status
|
168
|
+
Desk::Error::UnsupportedMediaType #=> 415 Status
|
169
|
+
Desk::Error::UnprocessableEntity #=> 422 Status
|
170
|
+
Desk::Error::TooManyRequests #=> 429 Status
|
171
|
+
```
|
172
|
+
|
173
|
+
Please also have a look at all [desk.com API errors](http://dev.desk.com/API/using-the-api/#status-codes) and their respective meanings.
|
174
|
+
|
175
|
+
## License
|
176
|
+
|
177
|
+
(The MIT License)
|
178
|
+
|
179
|
+
Copyright (c) 2013 Thomas Stachl <tom@desk.com>
|
180
|
+
|
181
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
182
|
+
|
183
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
184
|
+
|
185
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'rake'
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'bundler/setup'
|
5
|
+
Bundler::GemHelper.install_tasks
|
6
|
+
rescue
|
7
|
+
puts 'although not required, bundler is recommened for running the tests'
|
8
|
+
end
|
9
|
+
|
10
|
+
task default: :spec
|
11
|
+
|
12
|
+
require 'rspec/core/rake_task'
|
13
|
+
RSpec::Core::RakeTask.new do |t|
|
14
|
+
t.rspec_opts = ["--color", '--format doc']
|
15
|
+
end
|
data/config.rb
ADDED
data/desk_api.gemspec
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$LOAD_PATH.unshift File.expand_path("../lib", __FILE__)
|
3
|
+
require 'desk/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |gem|
|
6
|
+
gem.name = 'desk_api'
|
7
|
+
gem.summary = 'A lightweight, flexible wrapper for the desk.com REST API.'
|
8
|
+
gem.description = 'This is a lightweight, flexible ruby gem to interact with the desk.com REST API. It allows to create, read and delete resources available through the API endpoints. It can be used either with OAuth or HTTP Basic Authentication.'
|
9
|
+
gem.homepage = 'http://github.com/tstachl/desk.rb'
|
10
|
+
gem.version = Desk::VERSION
|
11
|
+
|
12
|
+
gem.authors = ['Thomas Stachl']
|
13
|
+
gem.email = 'tom@desk.com'
|
14
|
+
|
15
|
+
gem.require_paths = ['lib']
|
16
|
+
gem.files = `git ls-files`.split("\n")
|
17
|
+
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
|
20
|
+
gem.extra_rdoc_files = ['README.md']
|
21
|
+
gem.rdoc_options = ['--line-numbers', '--inline--source', '--title', 'desk.rb']
|
22
|
+
|
23
|
+
gem.add_runtime_dependency('multi_json')
|
24
|
+
gem.add_runtime_dependency('faraday')
|
25
|
+
gem.add_runtime_dependency('faraday_middleware')
|
26
|
+
gem.add_runtime_dependency('typhoeus')
|
27
|
+
gem.add_runtime_dependency('addressable')
|
28
|
+
gem.add_runtime_dependency('activesupport')
|
29
|
+
gem.add_runtime_dependency('hashie')
|
30
|
+
|
31
|
+
gem.add_development_dependency('rspec')
|
32
|
+
gem.add_development_dependency('rake')
|
33
|
+
gem.add_development_dependency('vcr')
|
34
|
+
gem.add_development_dependency('simplecov')
|
35
|
+
gem.add_development_dependency('coveralls')
|
36
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Desk
|
2
|
+
module Action
|
3
|
+
module Field
|
4
|
+
protected
|
5
|
+
# Handles the _links part of the HAL response and sets up the associations.
|
6
|
+
# It is used by the client to setup the initial resources like `users`, `cases` and
|
7
|
+
# so on, as well as by the resource object itself for sub resources (`cases.replies`).
|
8
|
+
#
|
9
|
+
# @api private
|
10
|
+
def setup_fields(definition)
|
11
|
+
definition.each_pair do |key, value|
|
12
|
+
next if key[0] == '_'
|
13
|
+
# set the instance variable
|
14
|
+
instance_variable_set(:"@#{key}", value)
|
15
|
+
# create getter
|
16
|
+
(class << self; self; end).send(:define_method, "#{key}") do
|
17
|
+
return @_changed[key] if kind_of?(Desk::Action::Update) and @_changed[key]
|
18
|
+
instance_variable_get(:"@#{key}")
|
19
|
+
end
|
20
|
+
# create setter
|
21
|
+
if kind_of?(Desk::Action::Update)
|
22
|
+
(class << self; self; end).send(:define_method, "#{key}=") do |value|
|
23
|
+
if instance_variable_get(:"@#{key}") != value
|
24
|
+
@_changed[key] = value
|
25
|
+
end
|
26
|
+
self
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Desk
|
2
|
+
module Action
|
3
|
+
module Link
|
4
|
+
protected
|
5
|
+
attr_accessor :_links
|
6
|
+
# Handles the _links part of the HAL response and sets up the associations.
|
7
|
+
# It is used by the client to setup the initial resources like `users`, `cases` and
|
8
|
+
# so on, as well as by the resource object itself for sub resources (`cases.replies`).
|
9
|
+
#
|
10
|
+
# @api private
|
11
|
+
def setup_links(links)
|
12
|
+
@_links = links
|
13
|
+
@_links.each_pair do |method, definition|
|
14
|
+
(class << self; self; end).send(:define_method, ['first', 'last', 'next', 'previous'].include?(method) ? "#{method}_page" : method) do
|
15
|
+
return nil unless definition
|
16
|
+
# return the stored resource if we already loaded it
|
17
|
+
return @_links[method]['resource'] if @_links[method].key?('resource')
|
18
|
+
# this is a really ugly hack but necessary for sub resources which aren't declared consistently
|
19
|
+
definition['class'] = 'page' if method.pluralize == method
|
20
|
+
# get the client
|
21
|
+
client = self.instance_of?(Desk::Client) ? self : self.client
|
22
|
+
# create the new resource
|
23
|
+
@_links[method]['resource'] = resource(definition['class']).new client, Hashie::Mash.new({ _links: { self: definition } })
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Desk
|
2
|
+
module Action
|
3
|
+
module Search
|
4
|
+
module ClassMethods
|
5
|
+
def search(client, path)
|
6
|
+
client.send(:resource, 'page').new(client, Hashie::Mash.new({ _links: { self: { href: path }}}))
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.included(base)
|
11
|
+
base.extend(ClassMethods)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/lib/desk/client.rb
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
|
3
|
+
require 'desk/configuration'
|
4
|
+
require 'desk/action/link'
|
5
|
+
require 'desk/action/resource'
|
6
|
+
require 'desk/error/client_error'
|
7
|
+
require 'desk/error/parse_error'
|
8
|
+
|
9
|
+
require 'desk/resource'
|
10
|
+
|
11
|
+
module Desk
|
12
|
+
class Client
|
13
|
+
include Desk::Configuration
|
14
|
+
include Desk::Action::Link
|
15
|
+
include Desk::Action::Resource
|
16
|
+
|
17
|
+
# Initializes a new Client object
|
18
|
+
#
|
19
|
+
# @param options [Hash]
|
20
|
+
# @return [Desk::Client]
|
21
|
+
def initialize(options = {})
|
22
|
+
Desk::Configuration.keys.each do |key|
|
23
|
+
instance_variable_set(:"@#{key}", options[key] || Desk.instance_variable_get(:"@#{key}"))
|
24
|
+
end
|
25
|
+
|
26
|
+
# load the initial resources (should actually be a call to /api/v2 but not yet)
|
27
|
+
resources = File.open(File.expand_path('resources.json', File.dirname(__FILE__))).read
|
28
|
+
definition = Hashie::Mash.new(JSON.parse(resources))
|
29
|
+
# on the client we only have links
|
30
|
+
setup_links(definition._links)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Perform an HTTP DELETE request
|
34
|
+
def delete(path, params = {})
|
35
|
+
request(:delete, path, params)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Perform an HTTP GET request
|
39
|
+
def get(path, params = {})
|
40
|
+
request(:get, path, params)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Perform an HTTP POST request
|
44
|
+
def post(path, params = {})
|
45
|
+
request(:post, path, params)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Perform an HTTP PATCH request
|
49
|
+
def patch(path, params = {})
|
50
|
+
request(:patch, path, params)
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def request(method, path, params = {})
|
56
|
+
connection.send(method, path, params)
|
57
|
+
rescue Faraday::Error::ClientError
|
58
|
+
raise Desk::Error::ClientError
|
59
|
+
rescue JSON::ParserError
|
60
|
+
raise Desk::Error::ParserError
|
61
|
+
end
|
62
|
+
|
63
|
+
def connection
|
64
|
+
@connection ||= Faraday.new endpoint, connection_options, &middleware
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
require 'faraday_middleware'
|
3
|
+
|
4
|
+
require 'typhoeus'
|
5
|
+
require 'typhoeus/adapters/faraday'
|
6
|
+
|
7
|
+
require 'desk/default'
|
8
|
+
require 'desk/request/retry'
|
9
|
+
require 'desk/response/raise_error'
|
10
|
+
require 'desk/error/configuration_error'
|
11
|
+
require 'desk/error/client_error'
|
12
|
+
require 'desk/error/server_error'
|
13
|
+
|
14
|
+
module Desk
|
15
|
+
module Configuration
|
16
|
+
extend Forwardable
|
17
|
+
attr_writer :consumer_secret, :token, :token_secret, :password
|
18
|
+
attr_accessor :consumer_key, :username, :endpoint, :subdomain, :connection_options, :middleware
|
19
|
+
def_delegator :options, :hash
|
20
|
+
|
21
|
+
class << self
|
22
|
+
def keys
|
23
|
+
@keys ||= [
|
24
|
+
:consumer_key,
|
25
|
+
:consumer_secret,
|
26
|
+
:token,
|
27
|
+
:token_secret,
|
28
|
+
:username,
|
29
|
+
:password,
|
30
|
+
:subdomain,
|
31
|
+
:endpoint,
|
32
|
+
:connection_options
|
33
|
+
]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# if subdomain is set make sure endpoint is correct
|
38
|
+
def endpoint
|
39
|
+
@endpoint ||= "https://#{@subdomain}.desk.com"
|
40
|
+
end
|
41
|
+
|
42
|
+
def middleware
|
43
|
+
@middleware ||= Proc.new do |builder|
|
44
|
+
builder.request :basic_auth, @username, @password if basic_auth.values.all?
|
45
|
+
builder.request :oauth, oauth if oauth.values.all?
|
46
|
+
builder.request :json
|
47
|
+
builder.request :retry
|
48
|
+
|
49
|
+
builder.response :mashify
|
50
|
+
builder.response :dates
|
51
|
+
builder.response :raise_error, Desk::Error::ClientError
|
52
|
+
builder.response :raise_error, Desk::Error::ServerError
|
53
|
+
builder.response :json, content_type: /application\/json/
|
54
|
+
|
55
|
+
builder.adapter :typhoeus
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def configure
|
60
|
+
yield self
|
61
|
+
validate_credentials!
|
62
|
+
validate_endpoint!
|
63
|
+
self
|
64
|
+
end
|
65
|
+
|
66
|
+
def reset!
|
67
|
+
Desk::Configuration.keys.each do |key|
|
68
|
+
send("#{key}=", Desk::Default.options[key])
|
69
|
+
end
|
70
|
+
self
|
71
|
+
end
|
72
|
+
alias setup reset!
|
73
|
+
|
74
|
+
def credentials?
|
75
|
+
oauth.values.all? || basic_auth.values.all?
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
# @return [Hash]
|
80
|
+
def options
|
81
|
+
Hash[Desk::Configuration.keys.map{|key| [key, instance_variable_get(:"@#{key}")]}]
|
82
|
+
end
|
83
|
+
|
84
|
+
def oauth
|
85
|
+
{
|
86
|
+
consumer_key: @consumer_key,
|
87
|
+
consumer_secret: @consumer_secret,
|
88
|
+
token: @token,
|
89
|
+
token_secret: @token_secret
|
90
|
+
}
|
91
|
+
end
|
92
|
+
|
93
|
+
def basic_auth
|
94
|
+
{
|
95
|
+
username: @username,
|
96
|
+
password: @password
|
97
|
+
}
|
98
|
+
end
|
99
|
+
|
100
|
+
def validate_credentials!
|
101
|
+
unless credentials?
|
102
|
+
raise(Error::ConfigurationError, "Invalid credentials: Either username/password or OAuth credentials must be specified.")
|
103
|
+
end
|
104
|
+
|
105
|
+
if oauth.values.all?
|
106
|
+
oauth.each do |credential, value|
|
107
|
+
next if value.nil?
|
108
|
+
|
109
|
+
unless value.is_a?(String) || value.is_a?(Symbol)
|
110
|
+
raise(Error::ConfigurationError, "Invalid #{credential} specified: #{value} must be a string or symbol.")
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
if basic_auth.values.all?
|
116
|
+
basic_auth.each do |credential, value|
|
117
|
+
next if value.nil?
|
118
|
+
|
119
|
+
unless value.is_a?(String) || value.is_a?(Symbol)
|
120
|
+
raise(Error::ConfigurationError, "Invalid #{credential} specified: #{value} must be a string or symbol.")
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def validate_endpoint!
|
127
|
+
unless endpoint =~ /^#{URI::regexp}$/
|
128
|
+
raise(Error::ConfigurationError, "Invalid endpoint specified: `#{endpoint}` must be a valid url.")
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|