dotloop-ruby 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +12 -0
  3. data/.rspec +2 -0
  4. data/CODE_OF_CONDUCT.md +74 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE.txt +21 -0
  7. data/README.md +386 -0
  8. data/Rakefile +6 -0
  9. data/bin/console +14 -0
  10. data/bin/setup +8 -0
  11. data/dotloop-ruby.gemspec +46 -0
  12. data/lib/.DS_Store +0 -0
  13. data/lib/dotloop-ruby.rb +45 -0
  14. data/lib/dotloop/.DS_Store +0 -0
  15. data/lib/dotloop/authenticate.rb +92 -0
  16. data/lib/dotloop/client.rb +160 -0
  17. data/lib/dotloop/contact.rb +70 -0
  18. data/lib/dotloop/document.rb +72 -0
  19. data/lib/dotloop/exceptions.rb +10 -0
  20. data/lib/dotloop/folder.rb +60 -0
  21. data/lib/dotloop/loop.rb +157 -0
  22. data/lib/dotloop/loop_detail.rb +38 -0
  23. data/lib/dotloop/models/.DS_Store +0 -0
  24. data/lib/dotloop/models/contact.rb +22 -0
  25. data/lib/dotloop/models/document.rb +27 -0
  26. data/lib/dotloop/models/folder.rb +19 -0
  27. data/lib/dotloop/models/loop.rb +45 -0
  28. data/lib/dotloop/models/loop_detail.rb +22 -0
  29. data/lib/dotloop/models/loop_details/contact.rb +28 -0
  30. data/lib/dotloop/models/loop_details/contract_dates.rb +13 -0
  31. data/lib/dotloop/models/loop_details/contract_info.rb +13 -0
  32. data/lib/dotloop/models/loop_details/financials.rb +20 -0
  33. data/lib/dotloop/models/loop_details/geographic_description.rb +22 -0
  34. data/lib/dotloop/models/loop_details/listing_information.rb +25 -0
  35. data/lib/dotloop/models/loop_details/offer_dates.rb +15 -0
  36. data/lib/dotloop/models/loop_details/property.rb +18 -0
  37. data/lib/dotloop/models/loop_details/property_address.rb +21 -0
  38. data/lib/dotloop/models/loop_details/referral.rb +13 -0
  39. data/lib/dotloop/models/participant.rb +17 -0
  40. data/lib/dotloop/models/profile.rb +36 -0
  41. data/lib/dotloop/models/task.rb +18 -0
  42. data/lib/dotloop/models/tasklist.rb +31 -0
  43. data/lib/dotloop/models/template.rb +18 -0
  44. data/lib/dotloop/participant.rb +66 -0
  45. data/lib/dotloop/profile.rb +26 -0
  46. data/lib/dotloop/query_param_helpers.rb +36 -0
  47. data/lib/dotloop/task.rb +32 -0
  48. data/lib/dotloop/tasklist.rb +30 -0
  49. data/lib/dotloop/template.rb +28 -0
  50. data/lib/dotloop/version.rb +3 -0
  51. 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
@@ -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
@@ -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