desk_api 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (100) hide show
  1. checksums.yaml +7 -0
  2. data/.coveralls.yml +1 -0
  3. data/.rspec +1 -0
  4. data/.travis.yml +5 -0
  5. data/.yardopts +3 -0
  6. data/Gemfile +15 -0
  7. data/Guardfile +5 -0
  8. data/LICENSE +7 -0
  9. data/README.md +185 -0
  10. data/Rakefile +15 -0
  11. data/config.rb +7 -0
  12. data/desk_api.gemspec +36 -0
  13. data/lib/desk/action/create.rb +15 -0
  14. data/lib/desk/action/delete.rb +9 -0
  15. data/lib/desk/action/embedded.rb +12 -0
  16. data/lib/desk/action/field.rb +33 -0
  17. data/lib/desk/action/link.rb +29 -0
  18. data/lib/desk/action/resource.rb +14 -0
  19. data/lib/desk/action/search.rb +15 -0
  20. data/lib/desk/action/update.rb +12 -0
  21. data/lib/desk/client.rb +67 -0
  22. data/lib/desk/configuration.rb +132 -0
  23. data/lib/desk/default.rb +67 -0
  24. data/lib/desk/error/bad_gateway.rb +10 -0
  25. data/lib/desk/error/bad_request.rb +10 -0
  26. data/lib/desk/error/client_error.rb +9 -0
  27. data/lib/desk/error/configuration_error.rb +8 -0
  28. data/lib/desk/error/conflict.rb +10 -0
  29. data/lib/desk/error/forbidden.rb +10 -0
  30. data/lib/desk/error/gateway_timeout.rb +10 -0
  31. data/lib/desk/error/internal_server_error.rb +10 -0
  32. data/lib/desk/error/method_not_allowed.rb +10 -0
  33. data/lib/desk/error/method_not_supported.rb +9 -0
  34. data/lib/desk/error/not_acceptable.rb +10 -0
  35. data/lib/desk/error/not_found.rb +10 -0
  36. data/lib/desk/error/parse_error.rb +9 -0
  37. data/lib/desk/error/parser_error.rb +9 -0
  38. data/lib/desk/error/server_error.rb +9 -0
  39. data/lib/desk/error/service_unavailable.rb +10 -0
  40. data/lib/desk/error/too_many_requests.rb +10 -0
  41. data/lib/desk/error/unauthorized.rb +10 -0
  42. data/lib/desk/error/unprocessable_entity.rb +10 -0
  43. data/lib/desk/error/unsupported_media_type.rb +10 -0
  44. data/lib/desk/error.rb +61 -0
  45. data/lib/desk/rate_limit.rb +26 -0
  46. data/lib/desk/request/retry.rb +51 -0
  47. data/lib/desk/resource/article.rb +10 -0
  48. data/lib/desk/resource/article_translation.rb +8 -0
  49. data/lib/desk/resource/attachment.rb +8 -0
  50. data/lib/desk/resource/case.rb +9 -0
  51. data/lib/desk/resource/company.rb +8 -0
  52. data/lib/desk/resource/customer.rb +9 -0
  53. data/lib/desk/resource/integration_url.rb +9 -0
  54. data/lib/desk/resource/job.rb +7 -0
  55. data/lib/desk/resource/label.rb +9 -0
  56. data/lib/desk/resource/macro.rb +9 -0
  57. data/lib/desk/resource/macro_action.rb +7 -0
  58. data/lib/desk/resource/note.rb +7 -0
  59. data/lib/desk/resource/page.rb +81 -0
  60. data/lib/desk/resource/reply.rb +8 -0
  61. data/lib/desk/resource/topic.rb +9 -0
  62. data/lib/desk/resource/topic_translation.rb +9 -0
  63. data/lib/desk/resource/user_preference.rb +7 -0
  64. data/lib/desk/resource.rb +53 -0
  65. data/lib/desk/resources.json +76 -0
  66. data/lib/desk/response/raise_error.rb +34 -0
  67. data/lib/desk/version.rb +3 -0
  68. data/lib/desk.rb +35 -0
  69. data/spec/cassettes/Desk_Client/_delete/deletes_a_resource.yml +48 -0
  70. data/spec/cassettes/Desk_Client/_get/fetches_resources.yml +55 -0
  71. data/spec/cassettes/Desk_Client/_patch/updates_a_resource.yml +62 -0
  72. data/spec/cassettes/Desk_Client/_post/creates_a_resource.yml +58 -0
  73. data/spec/cassettes/Desk_Error/_from_response/can_be_created_from_a_faraday_response.yml +54 -0
  74. data/spec/cassettes/Desk_Error/_from_response/uses_the_body_message_if_present.yml +54 -0
  75. data/spec/cassettes/Desk_Resource/_by_url/finds_resources_by_url.yml +313 -0
  76. data/spec/cassettes/Desk_Resource/_create/creates_a_new_topic.yml +58 -0
  77. data/spec/cassettes/Desk_Resource/_delete/deletes_a_resource.yml +164 -0
  78. data/spec/cassettes/Desk_Resource/_delete/throws_an_error_deleting_a_non_deletalbe_resource.yml +56 -0
  79. data/spec/cassettes/Desk_Resource/_exec_/can_be_forced_to_reload.yml +313 -0
  80. data/spec/cassettes/Desk_Resource/_exec_/loads_the_current_resource.yml +313 -0
  81. data/spec/cassettes/Desk_Resource/_method_missing/loads_the_resource_to_find_a_suitable_method.yml +313 -0
  82. data/spec/cassettes/Desk_Resource/_method_missing/raises_an_error_if_method_does_not_exist.yml +313 -0
  83. data/spec/cassettes/Desk_Resource/_search/allows_searching_on_search_enabled_resources.yml +54 -0
  84. data/spec/cassettes/Desk_Resource/_update/throws_an_error_updating_a_user.yml +56 -0
  85. data/spec/cassettes/Desk_Resource/_update/updates_a_topic.yml +118 -0
  86. data/spec/cassettes/Desk_Resource_Page/_by_id/loads_the_requested_resource.yml +54 -0
  87. data/spec/cassettes/Desk_Resource_Page/_page/keeps_the_resource_as_loaded.yml +113 -0
  88. data/spec/cassettes/Desk_Resource_Page/_page/returns_the_current_page_and_loads_if_page_not_defined.yml +313 -0
  89. data/spec/cassettes/Desk_Resource_Page/_page/sets_the_resource_to_not_loaded.yml +113 -0
  90. data/spec/desk/client_spec.rb +133 -0
  91. data/spec/desk/configuration_spec.rb +183 -0
  92. data/spec/desk/default_spec.rb +69 -0
  93. data/spec/desk/error_spec.rb +23 -0
  94. data/spec/desk/rate_limit_spec.rb +42 -0
  95. data/spec/desk/request/retry_spec.rb +46 -0
  96. data/spec/desk/resource_spec.rb +119 -0
  97. data/spec/desk/resources/page_spec.rb +64 -0
  98. data/spec/desk_spec.rb +43 -0
  99. data/spec/spec_helper.rb +39 -0
  100. 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
@@ -0,0 +1,5 @@
1
+ rvm:
2
+ - 1.9.3
3
+ - 2.0.0
4
+ - jruby-19mode # JRuby in 1.9 mode
5
+ - rbx-19mode
data/.yardopts ADDED
@@ -0,0 +1,3 @@
1
+ --readme README.md
2
+ --title "desk.rb Documentation"
3
+ --no-private
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
@@ -0,0 +1,5 @@
1
+ guard 'rspec', :version => 2 do
2
+ watch(%r{^spec/.+_spec\.rb$})
3
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
4
+ watch('spec/spec_helper.rb') { "spec" }
5
+ end
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 &lt;tom@desk.com&gt;
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
@@ -0,0 +1,7 @@
1
+ module Desk
2
+ CONFIG = {
3
+ username: 'tstachl@salesforce.com',
4
+ password: '253e6745',
5
+ subdomain: 'devel'
6
+ }
7
+ end
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,15 @@
1
+ module Desk
2
+ module Action
3
+ module Create
4
+ module ClassMethods
5
+ def create(client, path, params)
6
+ self.new(client, client.post(path, params).body)
7
+ end
8
+ end
9
+
10
+ def self.included(base)
11
+ base.extend(ClassMethods)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,9 @@
1
+ module Desk
2
+ module Action
3
+ module Delete
4
+ def delete
5
+ client.delete(@_links.self.href).status === 204
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,12 @@
1
+ module Desk
2
+ module Action
3
+ module Embedded
4
+ protected
5
+ def setup_embedded(entries)
6
+ @records = entries.map do |record|
7
+ resource(record._links.self['class']).new(client, record, true)
8
+ end
9
+ end
10
+ end
11
+ end
12
+ 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,14 @@
1
+ module Desk
2
+ module Action
3
+ module Resource
4
+ protected
5
+ def resource(name)
6
+ require "desk/resource/#{name}"
7
+ "Desk::Resource::#{name.classify}".constantize
8
+ rescue NameError
9
+ rescue LoadError
10
+ Desk::Resource
11
+ end
12
+ end
13
+ end
14
+ 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
@@ -0,0 +1,12 @@
1
+ module Desk
2
+ module Action
3
+ module Update
4
+ def update(params)
5
+ params.each_pair do |key, value|
6
+ send("#{key}=", value) if respond_to?("#{key}=")
7
+ end
8
+ setup(client.patch(@_links.self.href, @_changed).body)
9
+ end
10
+ end
11
+ end
12
+ end
@@ -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