dotloop-ruby 0.1.0
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/.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
|