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