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