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
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dotloop
|
4
|
+
class Document
|
5
|
+
DOTLOOP_FILE_UPLOAD_BOUNDARY = "AaB03x"
|
6
|
+
|
7
|
+
attr_accessor :client
|
8
|
+
|
9
|
+
def initialize(client:)
|
10
|
+
@client = client
|
11
|
+
end
|
12
|
+
|
13
|
+
def all(profile_id:, loop_id:, folder_id:)
|
14
|
+
@client.get("/profile/#{profile_id.to_i}/loop/#{loop_id.to_i}/folder/#{folder_id.to_i}/document")[:data].map do |document_attrs|
|
15
|
+
doc = Dotloop::Models::Document.new(document_attrs)
|
16
|
+
doc.client = client
|
17
|
+
doc.profile_id = profile_id.to_i
|
18
|
+
doc.loop_id = loop_id.to_i
|
19
|
+
doc.folder_id = folder_id.to_i
|
20
|
+
doc
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def find(profile_id:, loop_id:, folder_id:, document_id:)
|
25
|
+
document_data = @client.get("/profile/#{profile_id.to_i}/loop/#{loop_id.to_i}/folder/#{folder_id.to_i}/document/#{document_id.to_i}")[:data]
|
26
|
+
document = Dotloop::Models::Document.new(document_data)
|
27
|
+
document.client = client
|
28
|
+
document.profile_id = profile_id.to_i
|
29
|
+
document.loop_id = loop_id.to_i
|
30
|
+
document.folder_id = folder_id.to_i
|
31
|
+
document
|
32
|
+
end
|
33
|
+
|
34
|
+
def get(profile_id:, loop_id:, folder_id:, document_id:)
|
35
|
+
sio = StringIO.new
|
36
|
+
sio.set_encoding(Encoding::ASCII_8BIT)
|
37
|
+
sio.write(
|
38
|
+
@client.download(
|
39
|
+
"/profile/#{profile_id.to_i}/loop/#{loop_id.to_i}/folder/#{folder_id.to_i}/document/#{document_id.to_i}"
|
40
|
+
)
|
41
|
+
)
|
42
|
+
sio.flush
|
43
|
+
sio.close
|
44
|
+
sio
|
45
|
+
end
|
46
|
+
|
47
|
+
def upload(profile_id:, loop_id:, folder_id:, params: {})
|
48
|
+
file_url = params["file_url"]
|
49
|
+
file_name = params["file_name"]
|
50
|
+
file_content = params["file_content"]
|
51
|
+
# url = relative_path_S3(file_url)
|
52
|
+
# file = Aws::S3::Object.new(S3_CREDENTIALS['bucket'], url, region: S3_CREDENTIALS["region"]).get.data.body.read
|
53
|
+
|
54
|
+
post_body = []
|
55
|
+
|
56
|
+
post_body << "--#{DOTLOOP_FILE_UPLOAD_BOUNDARY}\r\n"
|
57
|
+
post_body << "Content-Disposition: form-data; name=\"file\"; filename=\"#{file_name}\"\r\n"
|
58
|
+
post_body << "Content-Type: application/pdf\r\n"
|
59
|
+
post_body << "\r\n"
|
60
|
+
post_body << file_content
|
61
|
+
post_body << "\r\n--#{DOTLOOP_FILE_UPLOAD_BOUNDARY}--\r\n"
|
62
|
+
|
63
|
+
document_data = @client.upload("/profile/#{profile_id.to_i}/loop/#{loop_id.to_i}/folder/#{folder_id.to_i}/document/", post_body.join)[:data]
|
64
|
+
document = Dotloop::Models::Document.new(document_data)
|
65
|
+
document.client = client
|
66
|
+
document.profile_id = profile_id.to_i
|
67
|
+
document.loop_id = loop_id.to_i
|
68
|
+
document.folder_id = folder_id.to_i
|
69
|
+
document
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dotloop
|
4
|
+
class BadRequest < StandardError; end
|
5
|
+
class Forbidden < StandardError; end
|
6
|
+
class NotFound < StandardError; end
|
7
|
+
class TooManyRequest < StandardError; end
|
8
|
+
class Unauthorized < StandardError; end
|
9
|
+
class UnprocessableEntity < StandardError; end
|
10
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dotloop
|
4
|
+
class Folder
|
5
|
+
attr_accessor :client
|
6
|
+
|
7
|
+
FOLDER_FIELDS = %w[name].freeze
|
8
|
+
|
9
|
+
def initialize(client:)
|
10
|
+
@client = client
|
11
|
+
end
|
12
|
+
|
13
|
+
def all(profile_id:, loop_id:)
|
14
|
+
url = "/profile/#{profile_id.to_i}/loop/#{loop_id.to_i}/folder"
|
15
|
+
@client.get(url)[:data].map do |folder_attrs|
|
16
|
+
folder = Dotloop::Models::Folder.new(folder_attrs)
|
17
|
+
folder.client = client
|
18
|
+
folder.profile_id = profile_id.to_i
|
19
|
+
folder.loop_id = loop_id.to_i
|
20
|
+
folder
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def find(profile_id:, loop_id:, folder_id:)
|
25
|
+
folder_data = @client.get("/profile/#{profile_id.to_i}/loop/#{loop_id.to_i}/folder/#{folder_id.to_i}")[:data]
|
26
|
+
folder = Dotloop::Models::Folder.new(folder_data)
|
27
|
+
folder.client = client
|
28
|
+
folder.profile_id = profile_id.to_i
|
29
|
+
folder.loop_id = loop_id.to_i
|
30
|
+
folder
|
31
|
+
end
|
32
|
+
|
33
|
+
def create(profile_id:, loop_id:, params: {})
|
34
|
+
data = {
|
35
|
+
name: params['name']
|
36
|
+
}
|
37
|
+
|
38
|
+
folder_data = @client.post("/profile/#{profile_id.to_i}/loop/#{loop_id.to_i}/folder", data)[:data]
|
39
|
+
folder = Dotloop::Models::Folder.new(folder_data)
|
40
|
+
folder.client = client
|
41
|
+
folder.profile_id = profile_id.to_i
|
42
|
+
folder
|
43
|
+
end
|
44
|
+
|
45
|
+
def update(profile_id:, loop_id:, folder_id:, params: {})
|
46
|
+
data = {}
|
47
|
+
params.each do |key, value|
|
48
|
+
FOLDER_FIELDS.include?(key.to_s) || next
|
49
|
+
data[key] = value.to_s
|
50
|
+
end
|
51
|
+
|
52
|
+
folder_data = @client.patch("/profile/#{profile_id.to_i}/loop/#{loop_id.to_i}/folder/#{folder_id.to_i}", data)[:data]
|
53
|
+
folder = Dotloop::Models::Folder.new(folder_data)
|
54
|
+
folder.client = client
|
55
|
+
folder.profile_id = profile_id.to_i
|
56
|
+
folder.loop_id = loop_id.to_i
|
57
|
+
folder
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
data/lib/dotloop/loop.rb
ADDED
@@ -0,0 +1,157 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dotloop
|
4
|
+
class Loop
|
5
|
+
include Dotloop::QueryParamHelpers
|
6
|
+
attr_accessor :client
|
7
|
+
|
8
|
+
LOOP_FIELDS = %w[name status transactionType].freeze
|
9
|
+
|
10
|
+
LOOP_DETAILS_FIELDS = {
|
11
|
+
"Property Address" => [
|
12
|
+
"Country", "Street Number", "Street Name", "Unit Number", "City",
|
13
|
+
"State/Prov", "Zip/Postal Code", "County", "MLS Number", "Parcel/Tax ID"
|
14
|
+
],
|
15
|
+
"Financials" => [
|
16
|
+
"Purchase/Sale Price", "Sale Commission Rate",
|
17
|
+
"Sale Commission Split % - Buy Side", "Sale Commission Split % - Sell Side",
|
18
|
+
"Sale Commission Total", "Earnest Money Amount", "Earnest Money Held By",
|
19
|
+
"Sale Commission Split $ - Buy Side", "Sale Commission Split $ - Sell Side"
|
20
|
+
],
|
21
|
+
"Contract Dates" => [
|
22
|
+
"Contract Agreement Date", "Closing Date"
|
23
|
+
],
|
24
|
+
"Offer Dates" => [
|
25
|
+
"Inspection Date", "Offer Date", "Offer Expiration Date", "Occupancy Date"
|
26
|
+
],
|
27
|
+
"Contract Info" => [
|
28
|
+
"Transaction Number", "Class", "Type"
|
29
|
+
],
|
30
|
+
"Referral" => [
|
31
|
+
"Referral %", "Referral Source"
|
32
|
+
],
|
33
|
+
"Listing Information" => [
|
34
|
+
"Expiration Date", "Listing Date", "Original Price",
|
35
|
+
"Current Price", "1st Mortgage Balance", "2nd Mortgage Balance",
|
36
|
+
"Other Liens", "Description of Other Liens", "Homeowner's Association",
|
37
|
+
"Homeowner's Association Dues", "Total Encumbrances", "Property Includes",
|
38
|
+
"Property Excludes", "Remarks"
|
39
|
+
],
|
40
|
+
"Geographic Description" => [
|
41
|
+
"MLS Area", "Legal Description", "Map Grid", "Subdivision", "Lot",
|
42
|
+
"Deed Page", "Deed Book", "Section", "Addition", "Block"
|
43
|
+
],
|
44
|
+
"Property" => [
|
45
|
+
"Year Built", "Bedrooms", "Square Footage", "School District",
|
46
|
+
"Type", "Bathrooms", "Lot Size"
|
47
|
+
]
|
48
|
+
}.freeze
|
49
|
+
|
50
|
+
def initialize(client:)
|
51
|
+
@client = client
|
52
|
+
end
|
53
|
+
|
54
|
+
def all(options = {})
|
55
|
+
loops = []
|
56
|
+
options[:batch_size] = BATCH_SIZE
|
57
|
+
(1..MAX_LOOPS).each do |i|
|
58
|
+
options[:batch_number] = i
|
59
|
+
current_batch = batch(options)
|
60
|
+
loops += current_batch
|
61
|
+
break if current_batch.size < options[:batch_size]
|
62
|
+
end
|
63
|
+
loops
|
64
|
+
end
|
65
|
+
|
66
|
+
def batch(options = {})
|
67
|
+
@client.get("/profile/#{profile_id(options)}/loop", query_params(options))[:data].map do |attrs|
|
68
|
+
lp = Dotloop::Models::Loop.new(attrs)
|
69
|
+
lp.client = client
|
70
|
+
lp.profile_id = profile_id(options)
|
71
|
+
lp
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def find(profile_id:, loop_id:)
|
76
|
+
loop_data = @client.get("/profile/#{profile_id.to_i}/loop/#{loop_id.to_i}")[:data]
|
77
|
+
lp = Dotloop::Models::Loop.new(loop_data)
|
78
|
+
lp.client = client
|
79
|
+
lp.profile_id = profile_id.to_i
|
80
|
+
lp
|
81
|
+
end
|
82
|
+
|
83
|
+
def detail(profile_id:, loop_id:)
|
84
|
+
loop_detail = @client.get("/profile/#{profile_id.to_i}/loop/#{loop_id.to_i}/detail")[:data]
|
85
|
+
loop_detail = Dotloop::LoopDetail.new(loop_detail).details
|
86
|
+
ld = Dotloop::Models::LoopDetail.new(loop_detail)
|
87
|
+
ld.profile_id = profile_id.to_i
|
88
|
+
ld.loop_id = loop_id.to_i
|
89
|
+
ld
|
90
|
+
end
|
91
|
+
|
92
|
+
def create(profile_id:, params: {})
|
93
|
+
data = {
|
94
|
+
name: params['name'],
|
95
|
+
status: params['status'],
|
96
|
+
transactionType: params['transactionType']
|
97
|
+
}
|
98
|
+
|
99
|
+
loop_data = @client.post("/profile/#{profile_id.to_i}/loop", data)[:data]
|
100
|
+
lp = Dotloop::Models::Loop.new(loop_data)
|
101
|
+
lp.client = client
|
102
|
+
lp.profile_id = profile_id.to_i
|
103
|
+
lp
|
104
|
+
end
|
105
|
+
|
106
|
+
def loop_it(profile_id:, params: {})
|
107
|
+
loop_data = @client.post("/loop-it?profile_id=#{profile_id}", params)[:data]
|
108
|
+
lp = Dotloop::Models::Loop.new(loop_data)
|
109
|
+
lp.client = client
|
110
|
+
lp.profile_id = profile_id.to_i
|
111
|
+
lp
|
112
|
+
end
|
113
|
+
|
114
|
+
def update(profile_id:, loop_id:, params: {})
|
115
|
+
data = {}
|
116
|
+
params.each do |key, value|
|
117
|
+
LOOP_FIELDS.include?(key.to_s) || next
|
118
|
+
data[key] = value.to_s
|
119
|
+
end
|
120
|
+
|
121
|
+
loop_data = @client.patch("/profile/#{profile_id.to_i}/loop/#{loop_id.to_i}", data)[:data]
|
122
|
+
lp = Dotloop::Models::Loop.new(loop_data)
|
123
|
+
lp.client = client
|
124
|
+
lp.profile_id = profile_id.to_i
|
125
|
+
lp
|
126
|
+
end
|
127
|
+
|
128
|
+
def update_details(profile_id:, loop_id:, params: {})
|
129
|
+
data = {}
|
130
|
+
LOOP_DETAILS_FIELDS.keys.each do |key|
|
131
|
+
(detail_fields = params[key])
|
132
|
+
detail_fields.is_a?(Hash) || next
|
133
|
+
data[key] = {}
|
134
|
+
detail_fields.each do |k, v|
|
135
|
+
LOOP_DETAILS_FIELDS[key].include?(k.to_s) || next
|
136
|
+
data[key][k] = v.to_s
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
loop_detail = @client.patch("/profile/#{profile_id.to_i}/loop/#{loop_id.to_i}/detail", data)[:data]
|
141
|
+
loop_detail = Dotloop::LoopDetail.new(loop_detail).details
|
142
|
+
Dotloop::Models::LoopDetail.new(loop_detail)
|
143
|
+
end
|
144
|
+
|
145
|
+
private
|
146
|
+
|
147
|
+
def query_params(options)
|
148
|
+
{
|
149
|
+
batch_number: batch_number(options),
|
150
|
+
batch_size: batch_size(options),
|
151
|
+
sort: options[:sort],
|
152
|
+
filter: options[:filter],
|
153
|
+
include_details: options[:include_details]
|
154
|
+
}.delete_if { |_, v| should_delete(v) }
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dotloop
|
4
|
+
class LoopDetail
|
5
|
+
attr_accessor :details
|
6
|
+
FIXED_SECTIONS = %i[
|
7
|
+
contract_dates contract_info financials geographic_description
|
8
|
+
listing_information offer_dates property_address property referral
|
9
|
+
].freeze
|
10
|
+
|
11
|
+
def initialize(data)
|
12
|
+
@details = FIXED_SECTIONS.each_with_object({}) { |key, memo| memo[key] = {} }.merge(contacts: [])
|
13
|
+
parse_data(data)
|
14
|
+
end
|
15
|
+
|
16
|
+
def parse_data(data)
|
17
|
+
fix_hash_keys(data).each { |item| build_section(item[0], item[1]) }
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def build_section(key, section_data)
|
23
|
+
return unless FIXED_SECTIONS.include?(key)
|
24
|
+
values = fix_hash_keys(section_data)
|
25
|
+
@details[key] = values
|
26
|
+
end
|
27
|
+
|
28
|
+
def index_to_key(index)
|
29
|
+
index.to_s.downcase.delete(%(')).gsub(/%/, ' percent ').gsub(/\$/, ' doller ').gsub(/[^a-z]/, '_').squeeze('_').gsub(/^_*/, '').gsub(/_*$/, '').to_sym
|
30
|
+
end
|
31
|
+
|
32
|
+
def fix_hash_keys(bad_hash)
|
33
|
+
bad_hash.each_with_object({}) do |item, memo|
|
34
|
+
memo[index_to_key(item[0])] = item[1]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
Binary file
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dotloop
|
4
|
+
module Models
|
5
|
+
class Contact
|
6
|
+
include Virtus.model
|
7
|
+
attribute :address
|
8
|
+
attribute :city
|
9
|
+
attribute :country
|
10
|
+
attribute :email
|
11
|
+
attribute :fax
|
12
|
+
attribute :first_name
|
13
|
+
attribute :home
|
14
|
+
attribute :id, Integer
|
15
|
+
attribute :last_name
|
16
|
+
attribute :office
|
17
|
+
attribute :state
|
18
|
+
attribute :zip_code
|
19
|
+
attr_accessor :client
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dotloop
|
4
|
+
module Models
|
5
|
+
class Document
|
6
|
+
include Virtus.model
|
7
|
+
attribute :created, DateTime
|
8
|
+
attribute :id, Integer
|
9
|
+
attribute :name
|
10
|
+
attribute :updated, DateTime
|
11
|
+
|
12
|
+
attr_accessor :client
|
13
|
+
attr_accessor :profile_id
|
14
|
+
attr_accessor :loop_id
|
15
|
+
attr_accessor :folder_id
|
16
|
+
|
17
|
+
def download
|
18
|
+
client.Document.get(
|
19
|
+
profile_id: profile_id,
|
20
|
+
loop_id: loop_id,
|
21
|
+
folder_id: folder_id,
|
22
|
+
document_id: id
|
23
|
+
)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dotloop
|
4
|
+
module Models
|
5
|
+
class Folder
|
6
|
+
include Virtus.model
|
7
|
+
attribute :name
|
8
|
+
attribute :id, Integer
|
9
|
+
|
10
|
+
attr_accessor :client
|
11
|
+
attr_accessor :profile_id
|
12
|
+
attr_accessor :loop_id
|
13
|
+
|
14
|
+
def documents
|
15
|
+
client.Document.all(profile_id: profile_id, loop_id: loop_id, folder_id: id)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dotloop
|
4
|
+
module Models
|
5
|
+
class Loop
|
6
|
+
include Virtus.model
|
7
|
+
attribute :completed_task_count, Integer
|
8
|
+
attribute :id, Integer
|
9
|
+
attribute :name
|
10
|
+
attribute :loop_url, Integer
|
11
|
+
attribute :profile_id, Integer
|
12
|
+
attribute :status
|
13
|
+
attribute :transaction_type
|
14
|
+
attribute :total_task_count, Integer
|
15
|
+
attribute :updated, DateTime
|
16
|
+
|
17
|
+
attr_accessor :client
|
18
|
+
attr_accessor :profile_id
|
19
|
+
|
20
|
+
def detail
|
21
|
+
client.Loop.detail(profile_id: profile_id, loop_id: id)
|
22
|
+
end
|
23
|
+
|
24
|
+
def folders
|
25
|
+
client.Folder.all(profile_id: profile_id, loop_id: id)
|
26
|
+
end
|
27
|
+
|
28
|
+
def participants
|
29
|
+
client.Participant.all(profile_id: profile_id, loop_id: id)
|
30
|
+
end
|
31
|
+
|
32
|
+
def tasklists
|
33
|
+
client.Tasklist.all(profile_id: profile_id, loop_id: id)
|
34
|
+
end
|
35
|
+
|
36
|
+
def update(data)
|
37
|
+
client.Loop.update(profile_id: profile_id, loop_id: id, params: data)
|
38
|
+
end
|
39
|
+
|
40
|
+
def update_details(data)
|
41
|
+
client.Loop.update_details(profile_id: profile_id, loop_id: id, params: data)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|