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,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -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__)
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -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
Binary file
@@ -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