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.
@@ -0,0 +1,11 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
7
+
8
+ require 'rubocop/rake_task'
9
+ RuboCop::RakeTask.new(:rubocop) do |task|
10
+ task.options = ['-D', '--parallel']
11
+ end
@@ -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__)
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -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
@@ -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
@@ -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
@@ -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,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Camper::Client
4
+ module MessageAPI
5
+ def messages(message_board)
6
+ get(message_board.messages_url, override_path: true)
7
+ end
8
+ end
9
+ 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,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Camper::Client
4
+ module ResourceAPI
5
+
6
+ def resource(url)
7
+ get(url_transform(url), override_path: true)
8
+ end
9
+
10
+ end
11
+ 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