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
@@ -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
|