camper 0.0.5
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 +7 -0
- data/.editorconfig +8 -0
- data/.github/dependabot.yml +6 -0
- data/.github/workflows/ci.yml +43 -0
- data/.github/workflows/ci_changelog.yml +29 -0
- data/.github/workflows/release.yml +91 -0
- data/.gitignore +60 -0
- data/.rspec +3 -0
- data/.rubocop.yml +47 -0
- data/.rubocop_todo.yml +249 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +69 -0
- data/CONTRIBUTING.md +183 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +103 -0
- data/LICENSE +21 -0
- data/README.md +113 -0
- data/Rakefile +11 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/camper.gemspec +34 -0
- data/examples/comments.rb +35 -0
- data/examples/messages.rb +24 -0
- data/examples/oauth.rb +22 -0
- data/examples/obtain_acces_token.rb +13 -0
- data/examples/todos.rb +27 -0
- data/lib/camper.rb +32 -0
- data/lib/camper/api/comment.rb +13 -0
- data/lib/camper/api/message.rb +9 -0
- data/lib/camper/api/project.rb +20 -0
- data/lib/camper/api/resource.rb +11 -0
- data/lib/camper/api/todo.rb +14 -0
- data/lib/camper/authorization.rb +64 -0
- data/lib/camper/client.rb +86 -0
- data/lib/camper/configuration.rb +75 -0
- data/lib/camper/error.rb +146 -0
- data/lib/camper/logging.rb +28 -0
- data/lib/camper/paginated_response.rb +67 -0
- data/lib/camper/pagination_data.rb +47 -0
- data/lib/camper/request.rb +116 -0
- data/lib/camper/resource.rb +83 -0
- data/lib/camper/resources/project.rb +14 -0
- data/lib/camper/version.rb +5 -0
- metadata +143 -0
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "camper"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/camper.gemspec
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'lib/camper/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = 'camper'
|
7
|
+
spec.version = Camper::VERSION
|
8
|
+
spec.authors = ['renehernandez']
|
9
|
+
|
10
|
+
spec.summary = 'Ruby client for Basecamp 3 API'
|
11
|
+
spec.homepage = 'https://github.com/renehernandez/camper'
|
12
|
+
spec.required_ruby_version = Gem::Requirement.new('>= 2.5.0')
|
13
|
+
spec.license = 'MIT'
|
14
|
+
|
15
|
+
spec.metadata['allowed_push_host'] = 'https://rubygems.org'
|
16
|
+
|
17
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
18
|
+
spec.metadata['source_code_uri'] = spec.homepage
|
19
|
+
|
20
|
+
# Specify which files should be added to the gem when it is released.
|
21
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
22
|
+
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
23
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
24
|
+
end
|
25
|
+
spec.bindir = 'exe'
|
26
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
27
|
+
spec.require_paths = ['lib']
|
28
|
+
|
29
|
+
spec.add_dependency 'httparty', '~> 0.18'
|
30
|
+
spec.add_dependency 'rack-oauth2', '~> 1.14'
|
31
|
+
|
32
|
+
spec.add_development_dependency 'rake', '~> 13.0'
|
33
|
+
spec.add_development_dependency 'rspec', '~> 3.9'
|
34
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'camper'
|
4
|
+
|
5
|
+
client = Camper.configure do |config|
|
6
|
+
config.client_id = ENV['BASECAMP3_CLIENT_ID']
|
7
|
+
config.client_secret = ENV['BASECAMP3_CLIENT_SECRET']
|
8
|
+
config.account_number = ENV['BASECAMP3_ACCOUNT_NUMBER']
|
9
|
+
config.refresh_token = ENV['BASECAMP3_REFRESH_TOKEN']
|
10
|
+
config.access_token = ENV['BASECAMP3_ACCESS_TOKEN']
|
11
|
+
end
|
12
|
+
|
13
|
+
projects = client.projects
|
14
|
+
|
15
|
+
projects.auto_paginate do |p|
|
16
|
+
puts "Project: #{p.name}"
|
17
|
+
|
18
|
+
message_board = client.message_board(p)
|
19
|
+
todoset = client.todoset(p)
|
20
|
+
|
21
|
+
# Message board and Todo set are example resources that doesn't have comments
|
22
|
+
puts "Message Board: #{message_board.title}, can be commented on: #{message_board.can_be_commented?}"
|
23
|
+
puts "Todo set: #{todoset.title}, can be commented on: #{todoset.can_be_commented?}"
|
24
|
+
|
25
|
+
# Adds a comment on the first todolist
|
26
|
+
list = client.todolists(todoset).first
|
27
|
+
puts "Todolist: #{list.title}, can be commented on: #{list.can_be_commented?}"
|
28
|
+
client.add_comment(list, 'New <b>comment</b> with <i>HTML support</i>')
|
29
|
+
comments = client.comments(list)
|
30
|
+
idx = 0
|
31
|
+
comments.auto_paginate do |c|
|
32
|
+
puts "Comment #{idx} content: #{c.content}"
|
33
|
+
idx += 1
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'camper'
|
2
|
+
|
3
|
+
client = Camper.configure do |config|
|
4
|
+
config.client_id = ENV['BASECAMP3_CLIENT_ID']
|
5
|
+
config.client_secret = ENV['BASECAMP3_CLIENT_SECRET']
|
6
|
+
config.account_number = ENV['BASECAMP3_ACCOUNT_NUMBER']
|
7
|
+
config.refresh_token = ENV['BASECAMP3_REFRESH_TOKEN']
|
8
|
+
config.access_token = ENV['BASECAMP3_ACCESS_TOKEN']
|
9
|
+
end
|
10
|
+
|
11
|
+
projects = client.projects
|
12
|
+
|
13
|
+
projects.auto_paginate do |p|
|
14
|
+
puts "Project: #{p.name}"
|
15
|
+
|
16
|
+
message_board = client.message_board(p)
|
17
|
+
puts "Message Board: #{message_board.title}"
|
18
|
+
|
19
|
+
messages = client.messages(message_board)
|
20
|
+
|
21
|
+
messages.auto_paginate do |msg|
|
22
|
+
puts msg.inspect
|
23
|
+
end
|
24
|
+
end
|
data/examples/oauth.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'camper'
|
2
|
+
|
3
|
+
client = Camper.configure do |config|
|
4
|
+
config.client_id = ENV['BASECAMP3_CLIENT_ID']
|
5
|
+
config.client_secret = ENV['BASECAMP3_CLIENT_SECRET']
|
6
|
+
config.redirect_uri = ENV['BASECAMP3_REDIRECT_URI']
|
7
|
+
end
|
8
|
+
|
9
|
+
# Get the authorization uri
|
10
|
+
puts client.authorization_uri
|
11
|
+
|
12
|
+
# Once you have received the auth code from basecamp, you can proceed with the following step
|
13
|
+
# This will update the configuration with the corresponding tokens
|
14
|
+
# Store the tokens in a safe place
|
15
|
+
token = client.authorize! ENV['BASECAMP3_ACCESS_CODE']
|
16
|
+
|
17
|
+
# Print refresh token
|
18
|
+
puts token.refresh_token
|
19
|
+
|
20
|
+
# Print access token
|
21
|
+
puts token.access_token
|
22
|
+
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'camper'
|
2
|
+
|
3
|
+
client = Camper.configure do |config|
|
4
|
+
config.client_id = ENV['BASECAMP3_CLIENT_ID']
|
5
|
+
config.client_secret = ENV['BASECAMP3_CLIENT_SECRET']
|
6
|
+
config.account_number = ENV['BASECAMP3_ACCOUNT_NUMBER']
|
7
|
+
config.refresh_token = ENV['BASECAMP3_REFRESH_TOKEN']
|
8
|
+
end
|
9
|
+
|
10
|
+
tokens = client.update_access_token!
|
11
|
+
|
12
|
+
# Prints the new access token
|
13
|
+
puts tokens.access_token
|
data/examples/todos.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'camper'
|
2
|
+
|
3
|
+
client = Camper.configure do |config|
|
4
|
+
config.client_id = ENV['BASECAMP3_CLIENT_ID']
|
5
|
+
config.client_secret = ENV['BASECAMP3_CLIENT_SECRET']
|
6
|
+
config.account_number = ENV['BASECAMP3_ACCOUNT_NUMBER']
|
7
|
+
config.refresh_token = ENV['BASECAMP3_REFRESH_TOKEN']
|
8
|
+
config.access_token = ENV['BASECAMP3_ACCESS_TOKEN']
|
9
|
+
end
|
10
|
+
|
11
|
+
projects = client.projects
|
12
|
+
|
13
|
+
projects.auto_paginate do |p|
|
14
|
+
puts "Project: #{p.inspect}"
|
15
|
+
|
16
|
+
puts "Todo set: #{p.todoset.inspect}"
|
17
|
+
|
18
|
+
todoset = client.todoset(p)
|
19
|
+
|
20
|
+
client.todolists(todoset).auto_paginate(5) do |list|
|
21
|
+
puts "Todolist: #{list.title}"
|
22
|
+
|
23
|
+
client.todos(list).auto_paginate do |todo|
|
24
|
+
puts todo.inspect
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/lib/camper.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'forwardable'
|
4
|
+
require 'rack/oauth2'
|
5
|
+
|
6
|
+
require 'camper/version'
|
7
|
+
require 'camper/logging'
|
8
|
+
require 'camper/error'
|
9
|
+
require 'camper/configuration'
|
10
|
+
require 'camper/authorization'
|
11
|
+
require 'camper/resource'
|
12
|
+
require 'camper/pagination_data'
|
13
|
+
require 'camper/paginated_response'
|
14
|
+
require 'camper/request'
|
15
|
+
require 'camper/client'
|
16
|
+
|
17
|
+
module Camper
|
18
|
+
# Alias for Camper::Client.new
|
19
|
+
#
|
20
|
+
# @return [Camper::Client]
|
21
|
+
def self.client(options = {})
|
22
|
+
Camper::Client.new(options)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Delegates to Camper::Client configure method
|
26
|
+
#
|
27
|
+
# @return [Camper::Client]
|
28
|
+
def self.configure(&block)
|
29
|
+
client.configure(&block)
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Camper::Client
|
4
|
+
module CommentAPI
|
5
|
+
def add_comment(resource, content)
|
6
|
+
post(resource.comments_url, override_path: true, body: { content: content }.to_json)
|
7
|
+
end
|
8
|
+
|
9
|
+
def comments(resource)
|
10
|
+
get(resource.comments_url, override_path: true)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Camper::Client
|
4
|
+
module ProjectAPI
|
5
|
+
|
6
|
+
def projects(options = {})
|
7
|
+
get("/projects", options)
|
8
|
+
end
|
9
|
+
|
10
|
+
def message_board(project)
|
11
|
+
board = project.message_board
|
12
|
+
get(board.url, override_path: true)
|
13
|
+
end
|
14
|
+
|
15
|
+
def todoset(project)
|
16
|
+
todoset = project.todoset
|
17
|
+
get(todoset.url, override_path: true)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Camper::Client
|
4
|
+
module TodoAPI
|
5
|
+
|
6
|
+
def todolists(todoset)
|
7
|
+
get(todoset.todolists_url, override_path: true)
|
8
|
+
end
|
9
|
+
|
10
|
+
def todos(todolist, options={})
|
11
|
+
get(todolist.todos_url, options.merge(override_path: true))
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Camper
|
4
|
+
module Authorization
|
5
|
+
|
6
|
+
def authorization_uri
|
7
|
+
client = authz_client
|
8
|
+
|
9
|
+
client.authorization_uri type: :web_server
|
10
|
+
end
|
11
|
+
|
12
|
+
def authz_client
|
13
|
+
Rack::OAuth2::Client.new(
|
14
|
+
identifier: @config.client_id,
|
15
|
+
secret: @config.client_secret,
|
16
|
+
redirect_uri: @config.redirect_uri,
|
17
|
+
authorization_endpoint: @config.authz_endpoint,
|
18
|
+
token_endpoint: @config.token_endpoint,
|
19
|
+
)
|
20
|
+
end
|
21
|
+
|
22
|
+
def authorize!(auth_code)
|
23
|
+
client = authz_client
|
24
|
+
client.authorization_code = auth_code
|
25
|
+
|
26
|
+
# Passing secrets as query string
|
27
|
+
token = client.access_token!(
|
28
|
+
client_auth_method: nil,
|
29
|
+
client_id: @config.client_id,
|
30
|
+
client_secret: @config.client_secret,
|
31
|
+
type: :web_server
|
32
|
+
)
|
33
|
+
|
34
|
+
store_tokens(token)
|
35
|
+
|
36
|
+
token
|
37
|
+
end
|
38
|
+
|
39
|
+
def update_access_token!
|
40
|
+
logger.debug "Update access token using refresh token"
|
41
|
+
|
42
|
+
client = authz_client
|
43
|
+
client.refresh_token = @config.refresh_token
|
44
|
+
|
45
|
+
token = client.access_token!(
|
46
|
+
client_auth_method: nil,
|
47
|
+
client_id: @config.client_id,
|
48
|
+
client_secret: @config.client_secret,
|
49
|
+
type: :refresh
|
50
|
+
)
|
51
|
+
|
52
|
+
store_tokens(token)
|
53
|
+
|
54
|
+
token
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def store_tokens(token)
|
60
|
+
@config.access_token = token.access_token
|
61
|
+
@config.refresh_token = token.refresh_token
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Camper
|
4
|
+
# Wrapper for the Camper REST API.
|
5
|
+
class Client
|
6
|
+
Dir[File.expand_path('api/*.rb', __dir__)].each { |f| require f }
|
7
|
+
|
8
|
+
extend Forwardable
|
9
|
+
|
10
|
+
def_delegators :@config, *(Configuration::VALID_OPTIONS_KEYS)
|
11
|
+
def_delegators :@config, :authz_endpoint, :token_endpoint, :api_endpoint, :base_api_endpoint
|
12
|
+
|
13
|
+
# Keep in alphabetical order
|
14
|
+
include Authorization
|
15
|
+
include CommentAPI
|
16
|
+
include Logging
|
17
|
+
include MessageAPI
|
18
|
+
include ProjectAPI
|
19
|
+
include ResourceAPI
|
20
|
+
include TodoAPI
|
21
|
+
|
22
|
+
# Creates a new API.
|
23
|
+
# @raise [Error:MissingCredentials]
|
24
|
+
def initialize(options = {})
|
25
|
+
@config = Configuration.new(options)
|
26
|
+
end
|
27
|
+
|
28
|
+
%w[get post put delete].each do |method|
|
29
|
+
define_method method do |path, options = {}|
|
30
|
+
response, result = new_request.send(method, path, options)
|
31
|
+
return response unless result == Request::Result::AccessTokenExpired
|
32
|
+
|
33
|
+
update_access_token!
|
34
|
+
|
35
|
+
response, = new_request.send(method, path, options)
|
36
|
+
response
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Allows setting configuration values for this client
|
41
|
+
# returns the client instance being configured
|
42
|
+
def configure
|
43
|
+
yield @config
|
44
|
+
|
45
|
+
self
|
46
|
+
end
|
47
|
+
|
48
|
+
# Text representation of the client, masking private token.
|
49
|
+
#
|
50
|
+
# @return [String]
|
51
|
+
def inspect
|
52
|
+
inspected = super
|
53
|
+
inspected.sub! @config.access_token, only_show_last_four_chars(@config.access_token) if @config.access_token
|
54
|
+
inspected
|
55
|
+
end
|
56
|
+
|
57
|
+
# Utility method for URL encoding of a string.
|
58
|
+
# Copied from https://ruby-doc.org/stdlib-2.7.0/libdoc/erb/rdoc/ERB/Util.html
|
59
|
+
#
|
60
|
+
# @return [String]
|
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
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def new_request
|
68
|
+
Request.new(@config.access_token, @config.user_agent, self)
|
69
|
+
end
|
70
|
+
|
71
|
+
def only_show_last_four_chars(token)
|
72
|
+
"#{'*' * (token.size - 4)}#{token[-4..-1]}"
|
73
|
+
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
|
+
end
|
86
|
+
end
|