camper 0.0.5 → 0.0.6
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.
- checksums.yaml +4 -4
- data/.rubocop.yml +4 -1
- data/.yardopts +4 -0
- data/CHANGELOG.md +23 -6
- data/Gemfile.lock +11 -7
- data/README.md +40 -22
- data/camper.gemspec +1 -0
- data/examples/comments.rb +1 -1
- data/examples/create_and_complete_todo.rb +24 -0
- data/lib/camper/api/comment.rb +2 -2
- data/lib/camper/api/project.rb +4 -0
- data/lib/camper/api/resource.rb +1 -3
- data/lib/camper/api/todo.rb +68 -2
- data/lib/camper/authorization.rb +1 -1
- data/lib/camper/client.rb +31 -29
- data/lib/camper/configuration.rb +4 -6
- data/lib/camper/error.rb +9 -4
- data/lib/camper/pagination_data.rb +0 -3
- data/lib/camper/request.rb +60 -20
- data/lib/camper/resource.rb +0 -2
- data/lib/camper/version.rb +1 -1
- metadata +18 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b7d0cdad6f722b4f3c02ba3da38147000e371f57860ce203fb1c64eb007797de
|
4
|
+
data.tar.gz: db77821cf2cb77070b049dce6cbf4bc62430c4948101aadf7d2c87b4e67eaa42
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '027558808a97c4ef3c04c6ec895c86a8975e32f7a03397e882513db25f0cfb29978135c23a77a87081cfa845834c31419e7813c24a3bf43aeaf030b777e31c28'
|
7
|
+
data.tar.gz: 7aa221c2f13009488cc57d46fc86e1daeb21e2051b70e33dc6780032b8091bfdcb9f9efd54286b774b2f9406bd78c889add255e47ef674870aae07887c190ac5
|
data/.rubocop.yml
CHANGED
@@ -9,7 +9,7 @@ AllCops:
|
|
9
9
|
- 'camper.gemspec'
|
10
10
|
|
11
11
|
Layout/LineLength:
|
12
|
-
Max:
|
12
|
+
Max: 120
|
13
13
|
Exclude:
|
14
14
|
- 'lib/camper/client/*'
|
15
15
|
- 'spec/**/*'
|
@@ -18,6 +18,9 @@ Metrics/BlockLength:
|
|
18
18
|
Exclude:
|
19
19
|
- 'spec/**/*'
|
20
20
|
|
21
|
+
Metrics/AbcSize:
|
22
|
+
Max: 20
|
23
|
+
|
21
24
|
Style/Documentation:
|
22
25
|
Enabled: false
|
23
26
|
|
data/.yardopts
ADDED
data/CHANGELOG.md
CHANGED
@@ -1,16 +1,36 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
-
## [
|
3
|
+
## [v0.0.6](https://github.com/renehernandez/camper/tree/v0.0.6) (2020-10-01)
|
4
|
+
|
5
|
+
**Implemented enhancements:**
|
6
|
+
|
7
|
+
- Implement handling error according to Basecamp 3 API specifications [\#21](https://github.com/renehernandez/camper/issues/21)
|
8
|
+
- Add ability to complete a Todo [\#12](https://github.com/renehernandez/camper/issues/12)
|
9
|
+
- Multiple improvements [\#38](https://github.com/renehernandez/camper/pull/38)
|
10
|
+
- Error handling improvements [\#37](https://github.com/renehernandez/camper/pull/37)
|
11
|
+
|
12
|
+
**Documentation:**
|
13
|
+
|
14
|
+
- Enable documentation in RubyDoc [\#6](https://github.com/renehernandez/camper/issues/6)
|
15
|
+
- Add yard documentation [\#41](https://github.com/renehernandez/camper/pull/41)
|
16
|
+
- Fix gem badge [\#39](https://github.com/renehernandez/camper/pull/39)
|
17
|
+
|
18
|
+
**Merged pull requests:**
|
19
|
+
|
20
|
+
- Rename add\_comment to create\_comment [\#40](https://github.com/renehernandez/camper/pull/40)
|
21
|
+
- Bump rubocop from 0.91.0 to 0.92.0 [\#36](https://github.com/renehernandez/camper/pull/36)
|
22
|
+
|
23
|
+
## [v0.0.5](https://github.com/renehernandez/camper/tree/v0.0.5) (2020-09-22)
|
4
24
|
|
5
25
|
**Implemented enhancements:**
|
6
26
|
|
7
27
|
- Enable dependabot [\#22](https://github.com/renehernandez/camper/pull/22)
|
8
|
-
- Retry for new access token [\#16](https://github.com/renehernandez/camper/pull/16)
|
9
28
|
|
10
29
|
**Fixed bugs:**
|
11
30
|
|
12
31
|
- Implement pagination according to basecamp 3 API [\#20](https://github.com/renehernandez/camper/issues/20)
|
13
32
|
- Implement pagination according to Basecamp 3 API [\#26](https://github.com/renehernandez/camper/pull/26)
|
33
|
+
- Remove unreleasedLabel field [\#15](https://github.com/renehernandez/camper/pull/15)
|
14
34
|
|
15
35
|
**Merged pull requests:**
|
16
36
|
|
@@ -37,10 +57,7 @@
|
|
37
57
|
**Implemented enhancements:**
|
38
58
|
|
39
59
|
- Request a new access token once it expires [\#13](https://github.com/renehernandez/camper/issues/13)
|
40
|
-
|
41
|
-
**Fixed bugs:**
|
42
|
-
|
43
|
-
- Remove unreleasedLabel field [\#15](https://github.com/renehernandez/camper/pull/15)
|
60
|
+
- Retry for new access token [\#16](https://github.com/renehernandez/camper/pull/16)
|
44
61
|
|
45
62
|
**Documentation:**
|
46
63
|
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
camper (0.0.
|
4
|
+
camper (0.0.5)
|
5
5
|
httparty (~> 0.18)
|
6
6
|
rack-oauth2 (~> 1.14)
|
7
7
|
|
@@ -38,7 +38,7 @@ GEM
|
|
38
38
|
minitest (5.14.2)
|
39
39
|
multi_xml (0.6.0)
|
40
40
|
parallel (1.19.2)
|
41
|
-
parser (2.7.1.
|
41
|
+
parser (2.7.1.5)
|
42
42
|
ast (~> 2.4.1)
|
43
43
|
pry (0.13.1)
|
44
44
|
coderay (~> 1.1)
|
@@ -67,25 +67,28 @@ GEM
|
|
67
67
|
diff-lcs (>= 1.2.0, < 2.0)
|
68
68
|
rspec-support (~> 3.9.0)
|
69
69
|
rspec-support (3.9.3)
|
70
|
-
rubocop (0.
|
70
|
+
rubocop (0.92.0)
|
71
71
|
parallel (~> 1.10)
|
72
|
-
parser (>= 2.7.1.
|
72
|
+
parser (>= 2.7.1.5)
|
73
73
|
rainbow (>= 2.2.2, < 4.0)
|
74
74
|
regexp_parser (>= 1.7)
|
75
75
|
rexml
|
76
|
-
rubocop-ast (>= 0.
|
76
|
+
rubocop-ast (>= 0.5.0)
|
77
77
|
ruby-progressbar (~> 1.7)
|
78
78
|
unicode-display_width (>= 1.4.0, < 2.0)
|
79
|
-
rubocop-ast (0.
|
80
|
-
parser (>= 2.7.1.
|
79
|
+
rubocop-ast (0.7.0)
|
80
|
+
parser (>= 2.7.1.5)
|
81
|
+
strscan (>= 1.0.0)
|
81
82
|
rubocop-performance (1.8.1)
|
82
83
|
rubocop (>= 0.87.0)
|
83
84
|
rubocop-ast (>= 0.4.0)
|
84
85
|
ruby-progressbar (1.10.1)
|
86
|
+
strscan (1.0.3)
|
85
87
|
thread_safe (0.3.6)
|
86
88
|
tzinfo (1.2.7)
|
87
89
|
thread_safe (~> 0.1)
|
88
90
|
unicode-display_width (1.7.0)
|
91
|
+
yard (0.9.25)
|
89
92
|
zeitwerk (2.4.0)
|
90
93
|
|
91
94
|
PLATFORMS
|
@@ -98,6 +101,7 @@ DEPENDENCIES
|
|
98
101
|
rspec (~> 3.9)
|
99
102
|
rubocop
|
100
103
|
rubocop-performance
|
104
|
+
yard (~> 0.9)
|
101
105
|
|
102
106
|
BUNDLED WITH
|
103
107
|
2.1.4
|
data/README.md
CHANGED
@@ -1,7 +1,9 @@
|
|
1
|
-
# camper  [ [](https://badge.fury.io/rb/camper)
|
2
2
|
|
3
3
|
Camper is a Ruby wrapper for the [Basecamp 3 API](https://github.com/basecamp/bc3-api).
|
4
4
|
|
5
|
+
You can check out the gem documentation at [https://www.rubydoc.org/gems/camper](https://www.rubydoc.org/gems/camper)
|
6
|
+
|
5
7
|
## Installation
|
6
8
|
|
7
9
|
Add this line to your application's Gemfile:
|
@@ -24,7 +26,9 @@ $ gem install camper
|
|
24
26
|
|
25
27
|
## Usage
|
26
28
|
|
27
|
-
|
29
|
+
### Configuration
|
30
|
+
|
31
|
+
Getting a `client` and configuring it:
|
28
32
|
|
29
33
|
```ruby
|
30
34
|
require 'camper'
|
@@ -32,44 +36,58 @@ require 'camper'
|
|
32
36
|
client = Camper.client
|
33
37
|
|
34
38
|
client.configure do |config|
|
35
|
-
config.client_id =
|
36
|
-
config.client_secret =
|
37
|
-
config.account_number =
|
38
|
-
config.refresh_token =
|
39
|
-
config.access_token =
|
39
|
+
config.client_id = 'client_id'
|
40
|
+
config.client_secret = 'client_secret'
|
41
|
+
config.account_number = 'account_number'
|
42
|
+
config.refresh_token = 'refresh_token'
|
43
|
+
config.access_token = 'access_token'
|
40
44
|
end
|
41
|
-
|
42
|
-
projects = client.projects
|
43
45
|
```
|
44
46
|
|
45
|
-
Alternatively, it is possible to invoke the top-level `#configure` method to get a client
|
47
|
+
Alternatively, it is possible to invoke the top-level `#configure` method to get a `client`:
|
46
48
|
|
47
49
|
```ruby
|
48
50
|
require 'camper'
|
49
51
|
|
50
52
|
client = Camper.configure do |config|
|
51
|
-
config.client_id =
|
52
|
-
config.client_secret =
|
53
|
-
config.account_number =
|
54
|
-
config.refresh_token =
|
55
|
-
config.access_token =
|
53
|
+
config.client_id = 'client_id'
|
54
|
+
config.client_secret = 'client_secret'
|
55
|
+
config.account_number = 'account_number'
|
56
|
+
config.refresh_token = 'refresh_token'
|
57
|
+
config.access_token = 'access_token'
|
56
58
|
end
|
59
|
+
```
|
57
60
|
|
58
|
-
|
59
|
-
|
61
|
+
Also, the `client` can read directly the following environment variables:
|
62
|
+
|
63
|
+
* `BASECAMP_CLIENT_ID`
|
64
|
+
* `BASECAMP_CLIENT_SECRET`
|
65
|
+
* `BASECAMP_ACCOUNT_NUMBER`
|
66
|
+
* `BASECAMP_REFRESH_TOKEN`
|
67
|
+
* `BASECAMP_ACCESS_TOKEN`
|
68
|
+
|
69
|
+
then the code would look like:
|
70
|
+
|
71
|
+
```ruby
|
72
|
+
require 'camper'
|
73
|
+
|
74
|
+
client = Camper.client
|
60
75
|
```
|
61
76
|
|
77
|
+
|
78
|
+
### Examples
|
79
|
+
|
62
80
|
Example getting list of TODOs:
|
63
81
|
|
64
82
|
```ruby
|
65
83
|
require 'camper'
|
66
84
|
|
67
85
|
client = Camper.configure do |config|
|
68
|
-
config.client_id = ENV['
|
69
|
-
config.client_secret = ENV['
|
70
|
-
config.account_number = ENV['
|
71
|
-
config.refresh_token = ENV['
|
72
|
-
config.access_token = ENV['
|
86
|
+
config.client_id = ENV['BASECAMP_CLIENT_ID']
|
87
|
+
config.client_secret = ENV['BASECAMP_CLIENT_SECRET']
|
88
|
+
config.account_number = ENV['BASECAMP_ACCOUNT_NUMBER']
|
89
|
+
config.refresh_token = ENV['BASECAMP_REFRESH_TOKEN']
|
90
|
+
config.access_token = ENV['BASECAMP_ACCESS_TOKEN']
|
73
91
|
end
|
74
92
|
|
75
93
|
# gets a paginated response
|
data/camper.gemspec
CHANGED
data/examples/comments.rb
CHANGED
@@ -25,7 +25,7 @@ projects.auto_paginate do |p|
|
|
25
25
|
# Adds a comment on the first todolist
|
26
26
|
list = client.todolists(todoset).first
|
27
27
|
puts "Todolist: #{list.title}, can be commented on: #{list.can_be_commented?}"
|
28
|
-
client.
|
28
|
+
client.create_comment(list, 'New <b>comment</b> with <i>HTML support</i>')
|
29
29
|
comments = client.comments(list)
|
30
30
|
idx = 0
|
31
31
|
comments.auto_paginate do |c|
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'camper'
|
4
|
+
|
5
|
+
# It will configure the client using the basecamp environment variables
|
6
|
+
client = Camper.client
|
7
|
+
|
8
|
+
project = client.project(ENV['PROJECT_ID'])
|
9
|
+
|
10
|
+
todoset = client.todoset(project)
|
11
|
+
|
12
|
+
todolist = client.todolist(todoset, ENV['TODOLIST_ID'])
|
13
|
+
|
14
|
+
puts todolist.title
|
15
|
+
|
16
|
+
client.todos(todolist).auto_paginate do |todo|
|
17
|
+
puts todo.title
|
18
|
+
end
|
19
|
+
|
20
|
+
todo = client.create_todo(todolist, 'TODO from camper', description: 'This is a todo created with camper')
|
21
|
+
|
22
|
+
puts todo.title
|
23
|
+
|
24
|
+
client.complete_todo(todo)
|
data/lib/camper/api/comment.rb
CHANGED
@@ -2,8 +2,8 @@
|
|
2
2
|
|
3
3
|
class Camper::Client
|
4
4
|
module CommentAPI
|
5
|
-
def
|
6
|
-
post(resource.comments_url, override_path: true, body: { content: content }
|
5
|
+
def create_comment(resource, content)
|
6
|
+
post(resource.comments_url, override_path: true, body: { content: content })
|
7
7
|
end
|
8
8
|
|
9
9
|
def comments(resource)
|
data/lib/camper/api/project.rb
CHANGED
data/lib/camper/api/resource.rb
CHANGED
data/lib/camper/api/todo.rb
CHANGED
@@ -3,12 +3,78 @@
|
|
3
3
|
class Camper::Client
|
4
4
|
module TodoAPI
|
5
5
|
|
6
|
-
|
7
|
-
|
6
|
+
# Get the todolists associated with the todoset
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# client.todolists(todoset)
|
10
|
+
# @example
|
11
|
+
# client.todolists(todoset, status: 'archived')
|
12
|
+
#
|
13
|
+
# @param todoset [Resource] the parent todoset resource
|
14
|
+
# @param options [Hash] extra options to filter the list of todolist
|
15
|
+
# @return [Array<Resource>]
|
16
|
+
# @see https://github.com/basecamp/bc3-api/blob/master/sections/todolists.md#get-to-do-lists
|
17
|
+
def todolists(todoset, options={})
|
18
|
+
get(todoset.todolists_url, options.merge(override_path: true))
|
8
19
|
end
|
9
20
|
|
21
|
+
# Get a todolist with a given id
|
22
|
+
#
|
23
|
+
# @example
|
24
|
+
# client.todolist(todoset, '2345')
|
25
|
+
#
|
26
|
+
# @param todoset [Resource] the parent todoset resource
|
27
|
+
# @param id [Integer, String] the id of the todolist to get
|
28
|
+
# @return [Resource]
|
29
|
+
# @see https://github.com/basecamp/bc3-api/blob/master/sections/todolists.md#get-a-to-do-list
|
30
|
+
def todolist(todoset, id)
|
31
|
+
get("/buckets/#{todoset.bucket.id}/todolists/#{id}")
|
32
|
+
end
|
33
|
+
|
34
|
+
# Get the todos in a todolist
|
35
|
+
#
|
36
|
+
# @example
|
37
|
+
# client.todos(todolist)
|
38
|
+
# @example
|
39
|
+
# client.todos(todolist, completed: true)
|
40
|
+
#
|
41
|
+
# @param todolist [Resource] the parent todoset resource
|
42
|
+
# @param options [Hash] options to filter the list of todos
|
43
|
+
# @return [Resource]
|
44
|
+
# @see https://github.com/basecamp/bc3-api/blob/master/sections/todos.md#get-to-dos
|
10
45
|
def todos(todolist, options={})
|
11
46
|
get(todolist.todos_url, options.merge(override_path: true))
|
12
47
|
end
|
48
|
+
|
49
|
+
# Create a todo within a todolist
|
50
|
+
#
|
51
|
+
# @example
|
52
|
+
# client.create_todo(todolist, 'First Todo')
|
53
|
+
# @example
|
54
|
+
# client.create_todo(
|
55
|
+
# todolist,
|
56
|
+
# 'Program it',
|
57
|
+
# description: "<div><em>Try that new language!</em></div>, due_on: "2016-05-01"
|
58
|
+
# )
|
59
|
+
#
|
60
|
+
# @param todolist [Resource] the todolist where the todo is going to be created
|
61
|
+
# @param content [String] what the to-do is for
|
62
|
+
# @param options [Hash] extra configuration for the todo such as due_date and description
|
63
|
+
# @return [Resource]
|
64
|
+
# @see https://github.com/basecamp/bc3-api/blob/master/sections/todos.md#create-a-to-do
|
65
|
+
def create_todo(todolist, content, options={})
|
66
|
+
post(todolist.todos_url, body: { content: content, **options }, override_path: true)
|
67
|
+
end
|
68
|
+
|
69
|
+
# Complete a todo
|
70
|
+
#
|
71
|
+
# @example
|
72
|
+
# client.complete_todo(todo)
|
73
|
+
#
|
74
|
+
# @param todo [Resource] the todo to be marked as completed
|
75
|
+
# @see https://github.com/basecamp/bc3-api/blob/master/sections/todos.md#complete-a-to-do
|
76
|
+
def complete_todo(todo)
|
77
|
+
post("#{todo.url}/completion", override_path: true)
|
78
|
+
end
|
13
79
|
end
|
14
80
|
end
|
data/lib/camper/authorization.rb
CHANGED
data/lib/camper/client.rb
CHANGED
@@ -6,7 +6,7 @@ module Camper
|
|
6
6
|
Dir[File.expand_path('api/*.rb', __dir__)].each { |f| require f }
|
7
7
|
|
8
8
|
extend Forwardable
|
9
|
-
|
9
|
+
|
10
10
|
def_delegators :@config, *(Configuration::VALID_OPTIONS_KEYS)
|
11
11
|
def_delegators :@config, :authz_endpoint, :token_endpoint, :api_endpoint, :base_api_endpoint
|
12
12
|
|
@@ -19,7 +19,7 @@ module Camper
|
|
19
19
|
include ResourceAPI
|
20
20
|
include TodoAPI
|
21
21
|
|
22
|
-
# Creates a new
|
22
|
+
# Creates a new Client instance.
|
23
23
|
# @raise [Error:MissingCredentials]
|
24
24
|
def initialize(options = {})
|
25
25
|
@config = Configuration.new(options)
|
@@ -27,18 +27,19 @@ module Camper
|
|
27
27
|
|
28
28
|
%w[get post put delete].each do |method|
|
29
29
|
define_method method do |path, options = {}|
|
30
|
-
|
31
|
-
return response unless result == Request::Result::AccessTokenExpired
|
32
|
-
|
33
|
-
update_access_token!
|
30
|
+
request = new_request(method, path, options)
|
34
31
|
|
35
|
-
|
36
|
-
|
32
|
+
loop do
|
33
|
+
response, result = request.execute
|
34
|
+
logger.debug("Request result: #{result}; Attempt: #{request.attempts}")
|
35
|
+
return response unless retry_request?(response, result)
|
36
|
+
end
|
37
37
|
end
|
38
38
|
end
|
39
39
|
|
40
40
|
# Allows setting configuration values for this client
|
41
|
-
#
|
41
|
+
# by yielding the config object to the block
|
42
|
+
# @return [Camper::Client] the client instance being configured
|
42
43
|
def configure
|
43
44
|
yield @config
|
44
45
|
|
@@ -54,33 +55,34 @@ module Camper
|
|
54
55
|
inspected
|
55
56
|
end
|
56
57
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
def url_encode(url)
|
62
|
-
url.to_s.b.gsub(/[^a-zA-Z0-9_\-.~]/n) { |m| sprintf('%%%02X', m.unpack1('C')) } # rubocop:disable Style/FormatString, Style/FormatStringToken
|
58
|
+
private
|
59
|
+
|
60
|
+
def new_request(method, path, options)
|
61
|
+
Request.new(self, method, path, options)
|
63
62
|
end
|
64
63
|
|
65
|
-
|
64
|
+
def retry_request?(response, result)
|
65
|
+
case result
|
66
|
+
when Request::Result::ACCESS_TOKEN_EXPIRED
|
67
|
+
update_access_token!
|
68
|
+
true
|
69
|
+
when Request::Result::TOO_MANY_REQUESTS
|
70
|
+
sleep_before_retrying(response)
|
71
|
+
true
|
72
|
+
else
|
73
|
+
false
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def sleep_before_retrying(response)
|
78
|
+
time = response.headers['Retry-After'].to_i
|
79
|
+
logger.debug("Sleeping for #{time} seconds before retrying request")
|
66
80
|
|
67
|
-
|
68
|
-
Request.new(@config.access_token, @config.user_agent, self)
|
81
|
+
sleep(time)
|
69
82
|
end
|
70
83
|
|
71
84
|
def only_show_last_four_chars(token)
|
72
85
|
"#{'*' * (token.size - 4)}#{token[-4..-1]}"
|
73
86
|
end
|
74
|
-
|
75
|
-
# Utility method for transforming Basecamp Web URLs into API URIs
|
76
|
-
# e.g 'https://3.basecamp.com/1/buckets/2/todos/3' will be
|
77
|
-
# converted into 'https://3.basecampapi.com/1/buckets/2/todos/3.json'
|
78
|
-
#
|
79
|
-
# @return [String]
|
80
|
-
def url_transform(url)
|
81
|
-
api_uri = url.gsub('3.basecamp.com', '3.basecampapi.com')
|
82
|
-
api_uri += '.json' unless url.end_with? '.json'
|
83
|
-
api_uri
|
84
|
-
end
|
85
87
|
end
|
86
88
|
end
|
data/lib/camper/configuration.rb
CHANGED
@@ -23,7 +23,7 @@ module Camper
|
|
23
23
|
attr_accessor(*VALID_OPTIONS_KEYS)
|
24
24
|
|
25
25
|
def initialize(options = {})
|
26
|
-
|
26
|
+
default_from_environment
|
27
27
|
VALID_OPTIONS_KEYS.each do |key|
|
28
28
|
send("#{key}=", options[key]) if options[key]
|
29
29
|
end
|
@@ -36,10 +36,9 @@ module Camper
|
|
36
36
|
end
|
37
37
|
end
|
38
38
|
|
39
|
-
# rubocop:disable Metrics/AbcSize
|
40
39
|
# Resets all configuration options to the defaults.
|
41
|
-
def
|
42
|
-
logger.debug '
|
40
|
+
def default_from_environment
|
41
|
+
logger.debug 'Setting attributes to default environment values'
|
43
42
|
self.client_id = ENV['BASECAMP3_CLIENT_ID']
|
44
43
|
self.client_secret = ENV['BASECAMP3_CLIENT_SECRET']
|
45
44
|
self.redirect_uri = ENV['BASECAMP3_REDIRECT_URI']
|
@@ -48,7 +47,6 @@ module Camper
|
|
48
47
|
self.access_token = ENV['BASECAMP3_ACCESS_TOKEN']
|
49
48
|
self.user_agent = ENV['BASECAMP3_USER_AGENT'] || DEFAULT_USER_AGENT
|
50
49
|
end
|
51
|
-
# rubocop:enable Metrics/AbcSize
|
52
50
|
|
53
51
|
def authz_endpoint
|
54
52
|
'https://launchpad.37signals.com/authorization/new'
|
@@ -60,7 +58,7 @@ module Camper
|
|
60
58
|
|
61
59
|
def api_endpoint
|
62
60
|
raise Camper::Error::InvalidConfiguration, "missing basecamp account" unless self.account_number
|
63
|
-
|
61
|
+
|
64
62
|
"#{self.base_api_endpoint}/#{self.account_number}"
|
65
63
|
end
|
66
64
|
|
data/lib/camper/error.rb
CHANGED
@@ -11,9 +11,14 @@ module Camper
|
|
11
11
|
# Raised when API endpoint credentials not configured.
|
12
12
|
class MissingCredentials < Error; end
|
13
13
|
|
14
|
+
class MissingBody < Error; end
|
15
|
+
|
14
16
|
# Raised when impossible to parse response body.
|
15
17
|
class Parsing < Error; end
|
16
18
|
|
19
|
+
# Raised when too many attempts for the same request
|
20
|
+
class TooManyRetries < Error; end
|
21
|
+
|
17
22
|
# Custom error class for rescuing from HTTP response errors.
|
18
23
|
class ResponseError < Error
|
19
24
|
POSSIBLE_MESSAGE_KEYS = %i[message error_description error].freeze
|
@@ -70,9 +75,6 @@ module Camper
|
|
70
75
|
# Return stringified response when receiving a
|
71
76
|
# parsing error to avoid obfuscation of the
|
72
77
|
# api error.
|
73
|
-
#
|
74
|
-
# note: The Camper API does not always return valid
|
75
|
-
# JSON when there are errors.
|
76
78
|
@response.to_s
|
77
79
|
end
|
78
80
|
|
@@ -127,6 +129,8 @@ module Camper
|
|
127
129
|
# Raised when API endpoint returns the HTTP status code 503.
|
128
130
|
class ServiceUnavailable < ResponseError; end
|
129
131
|
|
132
|
+
class GatewayTimeout < ResponseError; end
|
133
|
+
|
130
134
|
# HTTP status codes mapped to error classes.
|
131
135
|
STATUS_MAPPINGS = {
|
132
136
|
400 => BadRequest,
|
@@ -140,7 +144,8 @@ module Camper
|
|
140
144
|
429 => TooManyRequests,
|
141
145
|
500 => InternalServerError,
|
142
146
|
502 => BadGateway,
|
143
|
-
503 => ServiceUnavailable
|
147
|
+
503 => ServiceUnavailable,
|
148
|
+
504 => GatewayTimeout
|
144
149
|
}.freeze
|
145
150
|
end
|
146
151
|
end
|
data/lib/camper/request.rb
CHANGED
@@ -11,17 +11,26 @@ module Camper
|
|
11
11
|
headers 'Accept' => 'application/json', 'Content-Type' => 'application/json'
|
12
12
|
parser(proc { |body, _| parse(body) })
|
13
13
|
|
14
|
+
attr_reader :attempts
|
15
|
+
|
16
|
+
MAX_RETRY_ATTEMPTS = 5
|
17
|
+
|
14
18
|
module Result
|
15
19
|
ACCESS_TOKEN_EXPIRED = 'AccessTokenExpired'
|
16
20
|
|
21
|
+
TOO_MANY_REQUESTS = 'TooManyRequests'
|
22
|
+
|
17
23
|
VALID = 'Valid'
|
18
24
|
end
|
19
25
|
|
20
|
-
def initialize(
|
21
|
-
@access_token = access_token
|
26
|
+
def initialize(client, method, path, options = {})
|
22
27
|
@client = client
|
28
|
+
@path = path
|
29
|
+
@options = options
|
30
|
+
@attempts = 0
|
31
|
+
@method = method
|
23
32
|
|
24
|
-
self.class.headers 'User-Agent' => user_agent
|
33
|
+
self.class.headers 'User-Agent' => @client.user_agent
|
25
34
|
end
|
26
35
|
|
27
36
|
# Converts the response body to a Resource.
|
@@ -50,32 +59,43 @@ module Camper
|
|
50
59
|
raise Error::Parsing, 'The response is not a valid JSON'
|
51
60
|
end
|
52
61
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
override_path = params.delete(:override_path)
|
62
|
+
# Executes the request
|
63
|
+
def execute
|
64
|
+
endpoint, params = prepare_request_data
|
57
65
|
|
58
|
-
|
66
|
+
raise Error::TooManyRetries, endpoint if maxed_attempts?
|
59
67
|
|
60
|
-
|
68
|
+
@attempts += 1
|
61
69
|
|
62
|
-
|
63
|
-
|
70
|
+
logger.debug("Method: #{@method}; URL: #{endpoint}")
|
71
|
+
|
72
|
+
response, result = validate self.class.send(@method, endpoint, params)
|
73
|
+
response = extract_parsed(response) if result == Result::VALID
|
74
|
+
|
75
|
+
return response, result
|
76
|
+
end
|
77
|
+
|
78
|
+
def maxed_attempts?
|
79
|
+
@attempts >= MAX_RETRY_ATTEMPTS
|
64
80
|
end
|
65
81
|
|
66
82
|
private
|
67
83
|
|
68
|
-
|
69
|
-
|
84
|
+
def prepare_request_data
|
85
|
+
params = @options.dup
|
86
|
+
override_path = params.delete(:override_path)
|
87
|
+
|
88
|
+
params[:body] = params[:body].to_json if body_to_json?(params)
|
89
|
+
|
90
|
+
params[:headers] ||= {}
|
70
91
|
params[:headers].merge!(self.class.headers)
|
71
92
|
params[:headers].merge!(authorization_header)
|
72
93
|
|
73
|
-
|
74
|
-
response, result = validate self.class.send(method, endpoint, params)
|
94
|
+
full_endpoint = override_path ? @path : @client.api_endpoint + @path
|
75
95
|
|
76
|
-
|
96
|
+
full_endpoint = url_transform(full_endpoint)
|
77
97
|
|
78
|
-
return
|
98
|
+
return full_endpoint, params
|
79
99
|
end
|
80
100
|
|
81
101
|
# Checks the response code for common errors.
|
@@ -90,9 +110,14 @@ module Camper
|
|
90
110
|
return response, Result::ACCESS_TOKEN_EXPIRED
|
91
111
|
end
|
92
112
|
|
113
|
+
if error_klass == Error::TooManyRequests
|
114
|
+
logger.debug('Too many request. Please check the Retry-After header for subsequent requests')
|
115
|
+
return response, Result::TOO_MANY_REQUESTS
|
116
|
+
end
|
117
|
+
|
93
118
|
raise error_klass, response if error_klass
|
94
119
|
|
95
|
-
return response, Result::
|
120
|
+
return response, Result::VALID
|
96
121
|
end
|
97
122
|
|
98
123
|
def extract_parsed(response)
|
@@ -108,9 +133,24 @@ module Camper
|
|
108
133
|
#
|
109
134
|
# @raise [Error::MissingCredentials] if access_token and auth_token are not set.
|
110
135
|
def authorization_header
|
111
|
-
raise Error::MissingCredentials, 'Please provide a access_token' if @access_token.to_s.empty?
|
136
|
+
raise Error::MissingCredentials, 'Please provide a access_token' if @client.access_token.to_s.empty?
|
137
|
+
|
138
|
+
{ 'Authorization' => "Bearer #{@client.access_token}" }
|
139
|
+
end
|
140
|
+
|
141
|
+
# Utility method for transforming Basecamp Web URLs into API URIs
|
142
|
+
# e.g 'https://3.basecamp.com/1/buckets/2/todos/3' will be
|
143
|
+
# converted into 'https://3.basecampapi.com/1/buckets/2/todos/3.json'
|
144
|
+
#
|
145
|
+
# @return [String]
|
146
|
+
def url_transform(url)
|
147
|
+
api_url = url.gsub('3.basecamp.com', '3.basecampapi.com')
|
148
|
+
api_url.gsub!('.json', '')
|
149
|
+
"#{api_url}.json"
|
150
|
+
end
|
112
151
|
|
113
|
-
|
152
|
+
def body_to_json?(params)
|
153
|
+
@method == 'post' && params.key?(:body)
|
114
154
|
end
|
115
155
|
end
|
116
156
|
end
|
data/lib/camper/resource.rb
CHANGED
@@ -63,11 +63,9 @@ module Camper
|
|
63
63
|
@data.key?(method_name.to_s) ? @data[method_name.to_s] : super
|
64
64
|
end
|
65
65
|
|
66
|
-
# rubocop:disable Style/OptionalBooleanParameter
|
67
66
|
def respond_to_missing?(method_name, include_private = false)
|
68
67
|
@hash.keys.map(&:to_sym).include?(method_name.to_sym) || super
|
69
68
|
end
|
70
|
-
# rubocop:enable Style/OptionalBooleanParameter
|
71
69
|
|
72
70
|
def self.detect_type(url)
|
73
71
|
case url
|
data/lib/camper/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: camper
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- renehernandez
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-10-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: httparty
|
@@ -66,6 +66,20 @@ dependencies:
|
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '3.9'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: yard
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0.9'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0.9'
|
69
83
|
description:
|
70
84
|
email:
|
71
85
|
executables: []
|
@@ -82,6 +96,7 @@ files:
|
|
82
96
|
- ".rubocop.yml"
|
83
97
|
- ".rubocop_todo.yml"
|
84
98
|
- ".ruby-version"
|
99
|
+
- ".yardopts"
|
85
100
|
- CHANGELOG.md
|
86
101
|
- CONTRIBUTING.md
|
87
102
|
- Gemfile
|
@@ -93,6 +108,7 @@ files:
|
|
93
108
|
- bin/setup
|
94
109
|
- camper.gemspec
|
95
110
|
- examples/comments.rb
|
111
|
+
- examples/create_and_complete_todo.rb
|
96
112
|
- examples/messages.rb
|
97
113
|
- examples/oauth.rb
|
98
114
|
- examples/obtain_acces_token.rb
|