dotloop-ruby 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/.gitignore +12 -0
- data/.rspec +2 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +386 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/dotloop-ruby.gemspec +46 -0
- data/lib/.DS_Store +0 -0
- data/lib/dotloop-ruby.rb +45 -0
- data/lib/dotloop/.DS_Store +0 -0
- data/lib/dotloop/authenticate.rb +92 -0
- data/lib/dotloop/client.rb +160 -0
- data/lib/dotloop/contact.rb +70 -0
- data/lib/dotloop/document.rb +72 -0
- data/lib/dotloop/exceptions.rb +10 -0
- data/lib/dotloop/folder.rb +60 -0
- data/lib/dotloop/loop.rb +157 -0
- data/lib/dotloop/loop_detail.rb +38 -0
- data/lib/dotloop/models/.DS_Store +0 -0
- data/lib/dotloop/models/contact.rb +22 -0
- data/lib/dotloop/models/document.rb +27 -0
- data/lib/dotloop/models/folder.rb +19 -0
- data/lib/dotloop/models/loop.rb +45 -0
- data/lib/dotloop/models/loop_detail.rb +22 -0
- data/lib/dotloop/models/loop_details/contact.rb +28 -0
- data/lib/dotloop/models/loop_details/contract_dates.rb +13 -0
- data/lib/dotloop/models/loop_details/contract_info.rb +13 -0
- data/lib/dotloop/models/loop_details/financials.rb +20 -0
- data/lib/dotloop/models/loop_details/geographic_description.rb +22 -0
- data/lib/dotloop/models/loop_details/listing_information.rb +25 -0
- data/lib/dotloop/models/loop_details/offer_dates.rb +15 -0
- data/lib/dotloop/models/loop_details/property.rb +18 -0
- data/lib/dotloop/models/loop_details/property_address.rb +21 -0
- data/lib/dotloop/models/loop_details/referral.rb +13 -0
- data/lib/dotloop/models/participant.rb +17 -0
- data/lib/dotloop/models/profile.rb +36 -0
- data/lib/dotloop/models/task.rb +18 -0
- data/lib/dotloop/models/tasklist.rb +31 -0
- data/lib/dotloop/models/template.rb +18 -0
- data/lib/dotloop/participant.rb +66 -0
- data/lib/dotloop/profile.rb +26 -0
- data/lib/dotloop/query_param_helpers.rb +36 -0
- data/lib/dotloop/task.rb +32 -0
- data/lib/dotloop/tasklist.rb +30 -0
- data/lib/dotloop/template.rb +28 -0
- data/lib/dotloop/version.rb +3 -0
- metadata +276 -0
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "dotloop"
|
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
@@ -0,0 +1,46 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'dotloop/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "dotloop-ruby"
|
8
|
+
spec.version = Dotloop::VERSION
|
9
|
+
spec.authors = ["sampatbadhe"]
|
10
|
+
spec.email = ["sampat.badhe@kiprosh.com"]
|
11
|
+
|
12
|
+
spec.summary = %(Dotloop library)
|
13
|
+
spec.description = %(dotloop-ruby id Ruby library for Dotloop API v2.)
|
14
|
+
spec.homepage = "https://github.com/sampatbadhe/dotloop-ruby"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
|
18
|
+
# to allow pushing to a single host or delete this section to allow pushing to any host.
|
19
|
+
if spec.respond_to?(:metadata)
|
20
|
+
spec.metadata['allowed_push_host'] = "https://rubygems.org"
|
21
|
+
else
|
22
|
+
raise "RubyGems 2.0 or newer is required to protect against " \
|
23
|
+
"public gem pushes."
|
24
|
+
end
|
25
|
+
|
26
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
27
|
+
f.match(%r{^(test|spec|features)/})
|
28
|
+
end
|
29
|
+
spec.bindir = "exe"
|
30
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
31
|
+
spec.require_paths = ["lib"]
|
32
|
+
|
33
|
+
spec.add_runtime_dependency 'simplecov'
|
34
|
+
spec.add_runtime_dependency 'coveralls'
|
35
|
+
spec.add_runtime_dependency 'httparty', '~> 0.13'
|
36
|
+
spec.add_runtime_dependency 'virtus', '~> 1.0'
|
37
|
+
spec.add_runtime_dependency 'plissken', '~> 0.2'
|
38
|
+
spec.add_development_dependency 'bundler', '~> 1.14'
|
39
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
40
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
41
|
+
spec.add_development_dependency 'pry', '~> 0.10'
|
42
|
+
spec.add_development_dependency 'byebug', '~> 9.0'
|
43
|
+
spec.add_development_dependency 'rubocop', '~> 0.49.0'
|
44
|
+
spec.add_development_dependency 'webmock', '~> 2.1'
|
45
|
+
spec.add_development_dependency 'travis', '~> 1.8'
|
46
|
+
end
|
data/lib/.DS_Store
ADDED
Binary file
|
data/lib/dotloop-ruby.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'plissken'
|
2
|
+
require 'httparty'
|
3
|
+
require 'virtus'
|
4
|
+
|
5
|
+
require 'dotloop/version'
|
6
|
+
require 'dotloop/authenticate'
|
7
|
+
require 'dotloop/client'
|
8
|
+
require 'dotloop/query_param_helpers'
|
9
|
+
require 'dotloop/contact'
|
10
|
+
require 'dotloop/document'
|
11
|
+
require 'dotloop/exceptions'
|
12
|
+
require 'dotloop/folder'
|
13
|
+
require 'dotloop/loop'
|
14
|
+
require 'dotloop/loop_detail'
|
15
|
+
require 'dotloop/participant'
|
16
|
+
require 'dotloop/profile'
|
17
|
+
require 'dotloop/task'
|
18
|
+
require 'dotloop/tasklist'
|
19
|
+
require 'dotloop/template'
|
20
|
+
|
21
|
+
require 'dotloop/models/contact'
|
22
|
+
require 'dotloop/models/document'
|
23
|
+
require 'dotloop/models/folder'
|
24
|
+
require 'dotloop/models/loop'
|
25
|
+
require 'dotloop/models/participant'
|
26
|
+
require 'dotloop/models/profile'
|
27
|
+
require 'dotloop/models/task'
|
28
|
+
require 'dotloop/models/tasklist'
|
29
|
+
require 'dotloop/models/template'
|
30
|
+
|
31
|
+
require 'dotloop/models/loop_details/contact'
|
32
|
+
require 'dotloop/models/loop_details/contract_dates'
|
33
|
+
require 'dotloop/models/loop_details/contract_info'
|
34
|
+
require 'dotloop/models/loop_details/financials'
|
35
|
+
require 'dotloop/models/loop_details/geographic_description'
|
36
|
+
require 'dotloop/models/loop_details/listing_information'
|
37
|
+
require 'dotloop/models/loop_details/offer_dates'
|
38
|
+
require 'dotloop/models/loop_details/property_address'
|
39
|
+
require 'dotloop/models/loop_details/property'
|
40
|
+
require 'dotloop/models/loop_details/referral'
|
41
|
+
require 'dotloop/models/loop_detail'
|
42
|
+
|
43
|
+
module Dotloop
|
44
|
+
# Your code goes here...
|
45
|
+
end
|
Binary file
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dotloop
|
4
|
+
class Authenticate
|
5
|
+
include HTTParty
|
6
|
+
|
7
|
+
base_uri 'https://auth.dotloop.com/oauth/'
|
8
|
+
|
9
|
+
attr_accessor :app_id
|
10
|
+
attr_accessor :app_secret
|
11
|
+
attr_accessor :application
|
12
|
+
|
13
|
+
def initialize(app_id:, app_secret:, application: 'dotloop')
|
14
|
+
@app_id = app_id
|
15
|
+
@app_secret = app_secret
|
16
|
+
@application = application
|
17
|
+
raise 'Please enter an APP id' unless @app_id
|
18
|
+
raise 'Please enter an APP secret' unless @app_secret
|
19
|
+
end
|
20
|
+
|
21
|
+
def acquire_access_and_refresh_token(code, options = {})
|
22
|
+
params = {
|
23
|
+
grant_type: 'authorization_code',
|
24
|
+
code: code,
|
25
|
+
redirect_uri: options[:redirect_uri]
|
26
|
+
}
|
27
|
+
|
28
|
+
raw('/token', params)
|
29
|
+
end
|
30
|
+
|
31
|
+
def refresh_access_token(refresh_token)
|
32
|
+
params = {
|
33
|
+
grant_type: 'refresh_token',
|
34
|
+
refresh_token: refresh_token
|
35
|
+
}
|
36
|
+
|
37
|
+
raw('/token', params)
|
38
|
+
end
|
39
|
+
|
40
|
+
def revoke_access(access_token)
|
41
|
+
params = {
|
42
|
+
token: access_token
|
43
|
+
}
|
44
|
+
|
45
|
+
raw('/token/revoke', params)
|
46
|
+
end
|
47
|
+
|
48
|
+
def raw(page, params = {})
|
49
|
+
response = self.class.post(page, query: params, headers: headers, timeout: 60)
|
50
|
+
handle_dotloop_error(response.code) if response.code != 200
|
51
|
+
response.parsed_response
|
52
|
+
end
|
53
|
+
|
54
|
+
def handle_dotloop_error(response_code)
|
55
|
+
error = case response_code
|
56
|
+
when 400
|
57
|
+
Services::Dotloop::BadRequest
|
58
|
+
when 401
|
59
|
+
Dotloop::Unauthorized
|
60
|
+
when 403
|
61
|
+
Dotloop::Forbidden
|
62
|
+
else
|
63
|
+
StandardError
|
64
|
+
end
|
65
|
+
raise error, "Error communicating: Response code #{response_code}"
|
66
|
+
end
|
67
|
+
|
68
|
+
def url_for_authentication(redirect_uri, options = {})
|
69
|
+
params = {
|
70
|
+
client_id: @app_id,
|
71
|
+
response_type: 'code',
|
72
|
+
redirect_uri: redirect_uri
|
73
|
+
}
|
74
|
+
|
75
|
+
options.key?(:state) && params[:state] = options[:state]
|
76
|
+
options.key?(:redirect_on_deny) && params[:redirect_on_deny] = options[:redirect_on_deny]
|
77
|
+
|
78
|
+
"https://auth.dotloop.com/oauth/authorize?#{params.to_query}"
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
def headers
|
84
|
+
encode = Base64.encode64("#{app_id}:#{app_secret}").gsub(/\n/, '')
|
85
|
+
{
|
86
|
+
'Authorization' => "Basic #{encode}",
|
87
|
+
'User-Agent' => @application,
|
88
|
+
'Accept' => '*/*'
|
89
|
+
}
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,160 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dotloop
|
4
|
+
class Client
|
5
|
+
include HTTParty
|
6
|
+
|
7
|
+
base_uri 'https://api-gateway.dotloop.com/public/v2/'
|
8
|
+
|
9
|
+
DOTLOOP_FILE_UPLOAD_BOUNDARY = "AaB03x"
|
10
|
+
|
11
|
+
attr_accessor :access_token
|
12
|
+
attr_accessor :application
|
13
|
+
|
14
|
+
def initialize(access_token:, application: 'dotloop')
|
15
|
+
@access_token = access_token
|
16
|
+
@application = application
|
17
|
+
raise 'Please enter an Access Token' unless @access_token
|
18
|
+
end
|
19
|
+
|
20
|
+
def delete(page)
|
21
|
+
response = self.class.delete(page, headers: headers, timeout: 60)
|
22
|
+
handle_dotloop_error(response) if response.code != 204
|
23
|
+
self.class.snakify(response.parsed_response)
|
24
|
+
end
|
25
|
+
|
26
|
+
def get(page, params = {})
|
27
|
+
response = self.class.get(page, query: params, headers: headers, timeout: 60)
|
28
|
+
handle_dotloop_error(response) if response.code != 200
|
29
|
+
self.class.snakify(response.parsed_response)
|
30
|
+
end
|
31
|
+
|
32
|
+
def post(page, params = {})
|
33
|
+
response = self.class.post(page, body: params.to_json, headers: post_headers, timeout: 60)
|
34
|
+
handle_dotloop_error(response) if response.code != 201
|
35
|
+
self.class.snakify(response.parsed_response)
|
36
|
+
end
|
37
|
+
|
38
|
+
def patch(page, params = {})
|
39
|
+
response = self.class.patch(page, body: params.to_json, headers: post_headers, timeout: 60)
|
40
|
+
handle_dotloop_error(response) if response.code != 200
|
41
|
+
self.class.snakify(response.parsed_response)
|
42
|
+
end
|
43
|
+
|
44
|
+
def download(page, params = {})
|
45
|
+
response = self.class.get(page, query: params, headers: download_headers, timeout: 60)
|
46
|
+
handle_dotloop_error(response) if response.code != 200
|
47
|
+
response.parsed_response
|
48
|
+
end
|
49
|
+
|
50
|
+
def upload(page, body)
|
51
|
+
response = self.class.post(page, body: body, headers: upload_headers, timeout: 600)
|
52
|
+
handle_dotloop_error(response) if response.code != 201
|
53
|
+
self.class.snakify(response.parsed_response)
|
54
|
+
end
|
55
|
+
|
56
|
+
def handle_dotloop_error(response)
|
57
|
+
response_code = response.code
|
58
|
+
error = case response_code
|
59
|
+
when 400
|
60
|
+
Dotloop::BadRequest
|
61
|
+
when 401
|
62
|
+
Dotloop::Unauthorized
|
63
|
+
when 403
|
64
|
+
Dotloop::Forbidden
|
65
|
+
when 404
|
66
|
+
Dotloop::NotFound
|
67
|
+
when 422
|
68
|
+
Dotloop::UnprocessableEntity
|
69
|
+
when 429
|
70
|
+
Dotloop::TooManyRequest
|
71
|
+
else
|
72
|
+
StandardError
|
73
|
+
end
|
74
|
+
raise error, "Error communicating: Response code #{response_code}"
|
75
|
+
end
|
76
|
+
|
77
|
+
def account
|
78
|
+
get('/account', {})
|
79
|
+
end
|
80
|
+
|
81
|
+
def Document
|
82
|
+
@document ||= Dotloop::Document.new(client: self)
|
83
|
+
end
|
84
|
+
|
85
|
+
def Folder
|
86
|
+
@folder ||= Dotloop::Folder.new(client: self)
|
87
|
+
end
|
88
|
+
|
89
|
+
def Profile
|
90
|
+
@profile ||= Dotloop::Profile.new(client: self)
|
91
|
+
end
|
92
|
+
|
93
|
+
def Loop
|
94
|
+
@loop ||= Dotloop::Loop.new(client: self)
|
95
|
+
end
|
96
|
+
|
97
|
+
def Participant
|
98
|
+
@participant ||= Dotloop::Participant.new(client: self)
|
99
|
+
end
|
100
|
+
|
101
|
+
def Tasklist
|
102
|
+
@tasklist ||= Dotloop::Tasklist.new(client: self)
|
103
|
+
end
|
104
|
+
|
105
|
+
def Task
|
106
|
+
@task ||= Dotloop::Task.new(client: self)
|
107
|
+
end
|
108
|
+
|
109
|
+
def Template
|
110
|
+
@template ||= Dotloop::Template.new(client: self)
|
111
|
+
end
|
112
|
+
|
113
|
+
def Contact
|
114
|
+
@person ||= Dotloop::Contact.new(client: self)
|
115
|
+
end
|
116
|
+
|
117
|
+
def self.snakify(hash)
|
118
|
+
if hash.is_a? Array
|
119
|
+
hash.map(&:to_snake_keys)
|
120
|
+
else
|
121
|
+
hash.to_snake_keys
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
private
|
126
|
+
|
127
|
+
def download_headers
|
128
|
+
{
|
129
|
+
'Authorization' => "Bearer #{@access_token}",
|
130
|
+
'User-Agent' => @application,
|
131
|
+
'Accept' => 'application/pdf'
|
132
|
+
}
|
133
|
+
end
|
134
|
+
|
135
|
+
def upload_headers
|
136
|
+
{
|
137
|
+
'Authorization' => "Bearer #{@access_token}",
|
138
|
+
'User-Agent' => @application,
|
139
|
+
'Content-Type' => "multipart/form-data\; boundary=#{DOTLOOP_FILE_UPLOAD_BOUNDARY}"
|
140
|
+
}
|
141
|
+
end
|
142
|
+
|
143
|
+
def headers
|
144
|
+
{
|
145
|
+
'Authorization' => "Bearer #{@access_token}",
|
146
|
+
'User-Agent' => @application,
|
147
|
+
'Accept' => '*/*'
|
148
|
+
}
|
149
|
+
end
|
150
|
+
|
151
|
+
def post_headers
|
152
|
+
{
|
153
|
+
'Authorization' => "Bearer #{@access_token}",
|
154
|
+
'User-Agent' => @application,
|
155
|
+
'Accept' => '*/*',
|
156
|
+
'Content-Type' => 'application/json'
|
157
|
+
}
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dotloop
|
4
|
+
class Contact
|
5
|
+
include Dotloop::QueryParamHelpers
|
6
|
+
attr_accessor :client
|
7
|
+
|
8
|
+
CONTACT_FIELDS = %w[firstName lastName email home office
|
9
|
+
fax address city zipCode state country].freeze
|
10
|
+
|
11
|
+
def initialize(client:)
|
12
|
+
@client = client
|
13
|
+
end
|
14
|
+
|
15
|
+
def all(options = {})
|
16
|
+
contacts = []
|
17
|
+
url = '/contact'
|
18
|
+
(1..MAX_CONTACTS).each do |i|
|
19
|
+
options[:batch_number] = i
|
20
|
+
current_contacts = @client.get(url, query_params(options))[:data].map do |contact_attrs|
|
21
|
+
Dotloop::Models::Contact.new(contact_attrs)
|
22
|
+
end
|
23
|
+
contacts += current_contacts
|
24
|
+
break if current_contacts.size < BATCH_SIZE
|
25
|
+
end
|
26
|
+
contacts
|
27
|
+
end
|
28
|
+
|
29
|
+
def find(contact_id:)
|
30
|
+
contact_data = @client.get("/contact/#{contact_id.to_i}")[:data]
|
31
|
+
Dotloop::Models::Contact.new(contact_data)
|
32
|
+
end
|
33
|
+
|
34
|
+
def create(params: {})
|
35
|
+
data = {}
|
36
|
+
params.each do |key, value|
|
37
|
+
CONTACT_FIELDS.include?(key.to_s) || next
|
38
|
+
data[key] = value
|
39
|
+
end
|
40
|
+
|
41
|
+
contact_data = @client.post("/contact", data)[:data]
|
42
|
+
Dotloop::Models::Contact.new(contact_data)
|
43
|
+
end
|
44
|
+
|
45
|
+
def update(contact_id:, params: {})
|
46
|
+
data = {}
|
47
|
+
params.each do |key, value|
|
48
|
+
CONTACT_FIELDS.include?(key.to_s) || next
|
49
|
+
data[key] = value
|
50
|
+
end
|
51
|
+
|
52
|
+
contact_data = @client.patch("/contact/#{contact_id.to_i}", data)[:data]
|
53
|
+
Dotloop::Models::Contact.new(contact_data)
|
54
|
+
end
|
55
|
+
|
56
|
+
def delete(contact_id:)
|
57
|
+
@client.delete("/contact/#{contact_id.to_i}")
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def query_params(options)
|
63
|
+
{
|
64
|
+
batch_number: batch_number(options),
|
65
|
+
batch_size: batch_size(options),
|
66
|
+
filter: options[:filter]
|
67
|
+
}.delete_if { |_, v| should_delete(v) }
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|