dotloop_api 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. checksums.yaml.gz.sig +0 -0
  3. data/.gitignore +9 -0
  4. data/.rspec +2 -0
  5. data/.rubocop.yml +34 -0
  6. data/.travis.yml +8 -0
  7. data/CODE_OF_CONDUCT.md +74 -0
  8. data/Gemfile +4 -0
  9. data/LICENSE.txt +21 -0
  10. data/README.md +77 -0
  11. data/Rakefile +6 -0
  12. data/bin/console +7 -0
  13. data/bin/setup +8 -0
  14. data/certs/shanedavies.pem +21 -0
  15. data/dotloop_api.gemspec +43 -0
  16. data/lib/dotloop_api/auth.rb +120 -0
  17. data/lib/dotloop_api/client.rb +89 -0
  18. data/lib/dotloop_api/end_points/account.rb +19 -0
  19. data/lib/dotloop_api/end_points/activity.rb +22 -0
  20. data/lib/dotloop_api/end_points/base.rb +37 -0
  21. data/lib/dotloop_api/end_points/batch.rb +34 -0
  22. data/lib/dotloop_api/end_points/contact.rb +9 -0
  23. data/lib/dotloop_api/end_points/detail.rb +28 -0
  24. data/lib/dotloop_api/end_points/document.rb +25 -0
  25. data/lib/dotloop_api/end_points/folder.rb +32 -0
  26. data/lib/dotloop_api/end_points/loop.rb +17 -0
  27. data/lib/dotloop_api/end_points/loop_template.rb +19 -0
  28. data/lib/dotloop_api/end_points/model_builder.rb +65 -0
  29. data/lib/dotloop_api/end_points/param_helper.rb +60 -0
  30. data/lib/dotloop_api/end_points/participant.rb +22 -0
  31. data/lib/dotloop_api/end_points/profile.rb +10 -0
  32. data/lib/dotloop_api/end_points/task.rb +23 -0
  33. data/lib/dotloop_api/end_points/task_list.rb +21 -0
  34. data/lib/dotloop_api/exceptions.rb +25 -0
  35. data/lib/dotloop_api/models/config.rb +18 -0
  36. data/lib/dotloop_api/models/contact.rb +38 -0
  37. data/lib/dotloop_api/models/profile/loop/activity.rb +13 -0
  38. data/lib/dotloop_api/models/profile/loop/detail.rb +129 -0
  39. data/lib/dotloop_api/models/profile/loop/folder/document.rb +27 -0
  40. data/lib/dotloop_api/models/profile/loop/folder.rb +25 -0
  41. data/lib/dotloop_api/models/profile/loop/participant.rb +9 -0
  42. data/lib/dotloop_api/models/profile/loop/tasklist/task.rb +16 -0
  43. data/lib/dotloop_api/models/profile/loop/tasklist.rb +22 -0
  44. data/lib/dotloop_api/models/profile/loop.rb +58 -0
  45. data/lib/dotloop_api/models/profile/loop_template.rb +16 -0
  46. data/lib/dotloop_api/models/profile.rb +63 -0
  47. data/lib/dotloop_api/version.rb +3 -0
  48. data/lib/dotloop_api.rb +47 -0
  49. data.tar.gz.sig +0 -0
  50. metadata +309 -0
  51. metadata.gz.sig +0 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: c95d6eda3b19be7d8ce6f25b452fe623df80a4214fca74df58a0cffb0b4c8f94
4
+ data.tar.gz: 43ccfdad8dad844821427ac3f5d6320a10dd7526d0751f8b5b98e09a0296bf3c
5
+ SHA512:
6
+ metadata.gz: 7a884aed23a6bf9f786ff4718624454a67b373ea29047daec34a332a15ba1e2309a2fd5f94a4ef44936c3c2b0f48b6012d6bfcee8165c31c7a32bd94b38a5470
7
+ data.tar.gz: 5b679e7fe2b102ea2e0d4b5c8d68b640017a98864258414376fdc9154c13848219956fc0cdb7059793aa5de3db918f07fb8030553b19f9d736a83f2a34a7a767
checksums.yaml.gz.sig ADDED
Binary file
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.rubocop.yml ADDED
@@ -0,0 +1,34 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.5
3
+ Exclude:
4
+ - '**/Guardfile'
5
+ - 'db/**/*'
6
+ - 'tmp/**/*'
7
+ - 'vendor/**/*'
8
+ - 'bin/**/*'
9
+ - 'log/**/*'
10
+ - 'dotloop.gemspec'
11
+ Documentation:
12
+ Enabled: false
13
+ Metrics/BlockLength:
14
+ Exclude:
15
+ - 'spec/**/*.rb'
16
+ Metrics/LineLength:
17
+ Max: 120
18
+ Exclude:
19
+ - 'spec/**/*.rb'
20
+ AllowURI: true
21
+ URISchemes:
22
+ - http
23
+ - https
24
+ Metrics/MethodLength:
25
+ CountComments: false
26
+ Max: 15
27
+ Style/RaiseArgs:
28
+ Enabled: false
29
+ Style/FrozenStringLiteralComment:
30
+ Enabled: false
31
+ Style/Alias:
32
+ EnforcedStyle: prefer_alias_method
33
+ Layout/EmptyLineAfterGuardClause:
34
+ Enabled: False
data/.travis.yml ADDED
@@ -0,0 +1,8 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.5.3
5
+ before_install: gem install bundler -v 1.17.1
6
+ notifications:
7
+ slack:
8
+ secure: JabcnJlmrkhrrCtDTaqoylYn2dmyUP9h9SO/IyVKrIZ0YvTukaKzU2RSHP6PXjv1WC/9zfrmrWiODBZmg3HciOq4C6g+LdPEGtujx4ULSpztyIm8MlC+uODKtNLR7Q4j9cWTNOi6JKmBWJmo00i7OXLiB3njpEza3or3585vL9ffLJTG/9bi61u/ccuDXBKjqOg2Mxa5sWycgl/kAZ1qTgG76RARWFr6f2Cvxu9LG0BeUq0KpMHKToPjTllmYIKoXKStAX8bLnTU0M1JI5O4uc5W/QOaRhrNgSNyn2LVzHos/8+LO8I4kj+ptMYK037pM3ojByYmQOyE8WL/6kCAJbRN5njyENRUpXrGyWjfF7yDBGA5zMoYgC6MyUWiOj66p12tNTrUVkeFc53uYzlfAg8n331aHj+j+zWuT+oOrn0H9Nx1X4QtJleY6qkPHZO2aF9o7rZIeA7Fn9fGrECUvOQyskBHgHyyWnZ1SpZsRBXKCqlPMyf//RUO9Cx580VSTqgjTCcHa5gAhsoiTl5S9NjsTJCrshRxkj0t1tv2tiCbbTLJ9n309wsI2kj+53S4Q4lXgTV4SQEqrqoO7PldQKPiOZDF2ZOZO0Ou6EBEGcIqL54tWjoKXHpPxMuXW0UrXgl1wl+jhiQTzKBlW4raHLYfaL6u1Yjvo1ZKKTlLseg=
@@ -0,0 +1,74 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ In the interest of fostering an open and welcoming environment, we as
6
+ contributors and maintainers pledge to making participation in our project and
7
+ our community a harassment-free experience for everyone, regardless of age, body
8
+ size, disability, ethnicity, gender identity and expression, level of experience,
9
+ nationality, personal appearance, race, religion, or sexual identity and
10
+ orientation.
11
+
12
+ ## Our Standards
13
+
14
+ Examples of behavior that contributes to creating a positive environment
15
+ include:
16
+
17
+ * Using welcoming and inclusive language
18
+ * Being respectful of differing viewpoints and experiences
19
+ * Gracefully accepting constructive criticism
20
+ * Focusing on what is best for the community
21
+ * Showing empathy towards other community members
22
+
23
+ Examples of unacceptable behavior by participants include:
24
+
25
+ * The use of sexualized language or imagery and unwelcome sexual attention or
26
+ advances
27
+ * Trolling, insulting/derogatory comments, and personal or political attacks
28
+ * Public or private harassment
29
+ * Publishing others' private information, such as a physical or electronic
30
+ address, without explicit permission
31
+ * Other conduct which could reasonably be considered inappropriate in a
32
+ professional setting
33
+
34
+ ## Our Responsibilities
35
+
36
+ Project maintainers are responsible for clarifying the standards of acceptable
37
+ behavior and are expected to take appropriate and fair corrective action in
38
+ response to any instances of unacceptable behavior.
39
+
40
+ Project maintainers have the right and responsibility to remove, edit, or
41
+ reject comments, commits, code, wiki edits, issues, and other contributions
42
+ that are not aligned to this Code of Conduct, or to ban temporarily or
43
+ permanently any contributor for other behaviors that they deem inappropriate,
44
+ threatening, offensive, or harmful.
45
+
46
+ ## Scope
47
+
48
+ This Code of Conduct applies both within project spaces and in public spaces
49
+ when an individual is representing the project or its community. Examples of
50
+ representing a project or community include using an official project e-mail
51
+ address, posting via an official social media account, or acting as an appointed
52
+ representative at an online or offline event. Representation of a project may be
53
+ further defined and clarified by project maintainers.
54
+
55
+ ## Enforcement
56
+
57
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
+ reported by contacting the project team at shane.davies@gmail.com. All
59
+ complaints will be reviewed and investigated and will result in a response that
60
+ is deemed necessary and appropriate to the circumstances. The project team is
61
+ obligated to maintain confidentiality with regard to the reporter of an incident.
62
+ Further details of specific enforcement policies may be posted separately.
63
+
64
+ Project maintainers who do not follow or enforce the Code of Conduct in good
65
+ faith may face temporary or permanent repercussions as determined by other
66
+ members of the project's leadership.
67
+
68
+ ## Attribution
69
+
70
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
+ available at [http://contributor-covenant.org/version/1/4][version]
72
+
73
+ [homepage]: http://contributor-covenant.org
74
+ [version]: http://contributor-covenant.org/version/1/4/
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in dotloop_api.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2018 Loft 47
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,77 @@
1
+ # DotLoop API Library
2
+ [![Build Status](https://travis-ci.org/Loft47/dotloop_api.svg?branch=master)](https://travis-ci.org/Loft47/dotloop_api)
3
+ [![Coverage Status](https://coveralls.io/repos/github/Loft47/dotloop_api/badge.svg?branch=master&renew=true)](https://coveralls.io/github/Loft47/dotloop_api?branch=master)
4
+
5
+ This library is designed to help ruby applications consume the DotLoop API v2.
6
+
7
+ You can read more about the api from the official documentation at [https://dotloop.github.io/public-api](https://dotloop.github.io/public-api)
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ ```ruby
14
+ gem 'dotloop_api'
15
+ ```
16
+
17
+ And then execute:
18
+
19
+ $ bundle
20
+
21
+ Or install it yourself as:
22
+
23
+ $ gem install dotloop_api
24
+
25
+ ## Usage
26
+
27
+ Optional parameters are prefixed with a __'*'__'
28
+
29
+ ```ruby
30
+ grant_response_url = ''
31
+
32
+ auth = DotloopApi::Auth.new(
33
+ application: 'dotloop',
34
+ client_id: '00000000-ffff-eeee-1111-fff111fff111',
35
+ client_secret: '11111111-eeee-ffff-0000-111fff111fff',
36
+ redirect_uri: 'https://www.google.com',
37
+ )
38
+ puts "Initially you must visit this grant url an authenticate your Dotloop Account :#{auth.grant_uri}"
39
+ puts "now set the grant_response_url to the response you get from the line above"
40
+
41
+ if grant_response_url != ''
42
+ auth.request_from_url(url)
43
+ else
44
+ auth.config.attributes = {
45
+ access_token: '55555555-dddd-7777-0101-eeffeeffeeff',
46
+ refresh_token: '77777777-cccc-5555-1010-ffeeffeeffee'
47
+ }
48
+ end
49
+
50
+ client = DotloopApi::Client.new(access_token: auth.config.access_token)
51
+
52
+ client.account #=>
53
+
54
+ client.Profile.all #=> get all profiles
55
+ myprofile = client.Profile.all.last #=> get a profile
56
+ myprofile.loops #=> get a profiles loops
57
+ myloop = all_loops.last #=> get a single loop
58
+ myloop.activity #=> get loop activity
59
+ myloop.folders #=> get loop folders
60
+ myloop.folders.last.documents #=> get folder documents
61
+
62
+ ```
63
+
64
+ ## Development
65
+
66
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
67
+
68
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
69
+
70
+ ## Contributing
71
+
72
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/dotloop_api.
73
+
74
+
75
+ ## License
76
+
77
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -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
data/bin/console ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'dotloop_api'
5
+
6
+ require 'pry'
7
+ Pry.start
data/bin/setup ADDED
@@ -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,21 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIDcDCCAligAwIBAgIBATANBgkqhkiG9w0BAQUFADA/MRAwDgYDVQQDDAdzZGF2
3
+ aWVzMRYwFAYKCZImiZPyLGQBGRYGbG9mdDQ3MRMwEQYKCZImiZPyLGQBGRYDY29t
4
+ MB4XDTE3MTExNDE2Mjk1NFoXDTE4MTExNDE2Mjk1NFowPzEQMA4GA1UEAwwHc2Rh
5
+ dmllczEWMBQGCgmSJomT8ixkARkWBmxvZnQ0NzETMBEGCgmSJomT8ixkARkWA2Nv
6
+ bTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMmnC3pm+VvzYArTzzEJ
7
+ r3FNDkGtv9grWLFGMG+uD1uNtSvyoB2gJvYvYXw0IWBPnGGOlnG3mbJN39uu/Mw5
8
+ wxd8qZo2Jbht3oz/o2P8ymiR6D7CBL26sdQlTLd9vQbe0Ee7h/9iOIziNTnpPf3S
9
+ mjjKxFw65Db6cCF4nA7QN7wa5VjHDkF+y34Tn3QalpRAEcL7e0AcvUSj4zaPZwQc
10
+ NMQ1anPAc1SyFSlyjFOpJO8/DGW7svgw+HE7ht+wUwJU86pmVsacolUILyggAQnz
11
+ J8zOqCYx2gAkuPgZSN6blAgy+MaMq5g7JCQrpGyZ2B56Y4OvEpZo85MU0z6SQH8b
12
+ 6G8CAwEAAaN3MHUwCQYDVR0TBAIwADALBgNVHQ8EBAMCBLAwHQYDVR0OBBYEFJz2
13
+ HNGO7UZ6Lb7vfP5OlcUexZU/MB0GA1UdEQQWMBSBEnNkYXZpZXNAbG9mdDQ3LmNv
14
+ bTAdBgNVHRIEFjAUgRJzZGF2aWVzQGxvZnQ0Ny5jb20wDQYJKoZIhvcNAQEFBQAD
15
+ ggEBAFV+1gvsCyfx7a0Rxx4j5lIWp7j3IcBl08M50bUfqvGk0tVQQoqmwL9NvV/j
16
+ gxTc1WWkXwieCpq7syR8s5XYATXKtKxDraMjUvt4I6RCbyPIKA/26eEBllqzQ6ne
17
+ XatCbJMh2gIgkvmzJTjxX8st66KCYNLwzNUNTbjhlKaKZGkKFd314VTYfR7/6kfl
18
+ 6j39ofZdlo2GEwGRtgsjZa4aCqdAXcGJ9SvM2yJslIbXm0/SYhQ2tlJwr5jQ9vGL
19
+ UJ5WQPTBtyrpUNA3NZDQW/W0bEm41+2evyAwvcBkahBAWsfM+vpPFP+EZ6ymugVT
20
+ hGNADh27PqHgBxcLvQ9JSHtcxvE=
21
+ -----END CERTIFICATE-----
@@ -0,0 +1,43 @@
1
+ lib = File.expand_path('lib', __dir__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require 'dotloop_api/version'
4
+
5
+ Gem::Specification.new do |spec| # rubocop:disable Metrics/BlockLength
6
+ spec.name = 'dotloop_api'
7
+ spec.version = DotloopApi::VERSION
8
+ spec.authors = ['Loft47']
9
+ spec.email = ['support@loft47.com']
10
+
11
+ spec.summary = %(DotloopApi library)
12
+ spec.description = %(Ruby library for Dotloop API V2.)
13
+ spec.homepage = %(http://github.com/Loft47/dotloop_api)
14
+ spec.license = 'MIT'
15
+ spec.cert_chain = ['certs/shanedavies.pem']
16
+ spec.signing_key = File.expand_path('~/.ssh/dotloop-private_key.pem') if $PROGRAM_NAME.end_with?('gem')
17
+ spec.required_ruby_version = '~> 2.5'
18
+
19
+ # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
20
+ # to allow pushing to a single host or delete this section to allow pushing to any host.
21
+ raise 'RubyGems 2.0 or newer is required to protect against public gem pushes.' unless spec.respond_to?(:metadata)
22
+ spec.metadata['allowed_push_host'] = 'https://rubygems.org'
23
+
24
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
25
+ spec.bindir = 'exe'
26
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
27
+ spec.require_paths = ['lib']
28
+
29
+ spec.add_runtime_dependency 'activesupport', '~> 5'
30
+ spec.add_runtime_dependency 'coveralls', '~> 0.8'
31
+ spec.add_runtime_dependency 'httparty', '~> 0.16'
32
+ spec.add_runtime_dependency 'plissken', '~> 1.2'
33
+ spec.add_runtime_dependency 'simplecov', '~> 0.16'
34
+ spec.add_runtime_dependency 'virtus', '~> 1.0'
35
+ spec.add_development_dependency 'bundler', '~> 1.16'
36
+ spec.add_development_dependency 'byebug', '~> 10.0'
37
+ spec.add_development_dependency 'pry', '~> 0.11'
38
+ spec.add_development_dependency 'rake', '~> 12.3'
39
+ spec.add_development_dependency 'rspec', '~> 3.8'
40
+ spec.add_development_dependency 'rubocop', '~> 0.58'
41
+ spec.add_development_dependency 'travis', '~> 1.8'
42
+ spec.add_development_dependency 'webmock', '~> 3.4'
43
+ end
@@ -0,0 +1,120 @@
1
+ module DotloopApi
2
+ class Auth
3
+ include HTTParty
4
+ attr_accessor :config
5
+
6
+ def initialize(client_id:, client_secret:, redirect_uri:, redirect_on_deny: true, application: 'dotloop')
7
+ @config = DotloopApi::Models::Config.new(
8
+ application: application,
9
+ client_id: client_id,
10
+ client_secret: client_secret,
11
+ redirect_on_deny: redirect_on_deny,
12
+ redirect_uri: redirect_uri,
13
+ state: SecureRandom.uuid
14
+ )
15
+ end
16
+
17
+ def grant_uri
18
+ uri = URI("#{base_uri}authorize")
19
+ uri.query = URI.encode_www_form(grant_params)
20
+ uri.to_s
21
+ end
22
+
23
+ def request(code:, state: nil)
24
+ check_state(state)
25
+ response = self.class.post("#{base_uri}token", query: request_params(code), headers: headers, timeout: 60)
26
+ handle_error(response)
27
+ @config.attributes = response.parsed_response
28
+ end
29
+
30
+ def request_from_url(url)
31
+ request(parse_url_response(url))
32
+ end
33
+
34
+ def refresh
35
+ check_refresh_token
36
+ response = self.class.post("#{base_uri}token", query: refresh_params, headers: headers, timeout: 60)
37
+ handle_error(response)
38
+ @config.attributes = response.parsed_response
39
+ end
40
+
41
+ def revoke
42
+ check_revoke_token
43
+ params = { token: @config.access_token }
44
+ response = self.class.post("#{base_uri}token/revoke", query: params, headers: headers, timeout: 60)
45
+ handle_error(response)
46
+ clear_auth
47
+ end
48
+
49
+ private
50
+
51
+ def encoded_client_id
52
+ Base64.urlsafe_encode64("#{@config.client_id}:#{@config.client_secret}")
53
+ end
54
+
55
+ def headers
56
+ {
57
+ 'Content-Type': 'application/x-www-form-urlencoded',
58
+ 'User-Agent': @config.application.to_s,
59
+ 'Accept': '*/*',
60
+ 'Authorization': "Basic #{encoded_client_id}"
61
+ }
62
+ end
63
+
64
+ def request_params(code)
65
+ {
66
+ code: code,
67
+ grant_type: :authorization_code,
68
+ redirect_uri: @config.redirect_uri,
69
+ state: @config.state
70
+ }
71
+ end
72
+
73
+ def refresh_params
74
+ {
75
+ grant_type: :refresh_token,
76
+ refresh_token: @config.refresh_token
77
+ }
78
+ end
79
+
80
+ def grant_params
81
+ {
82
+ client_id: @config.client_id,
83
+ redirect_on_deny: @config.redirect_on_deny,
84
+ redirect_uri: @config.redirect_uri,
85
+ response_type: :code,
86
+ state: @config.state
87
+ }.delete_if { |_, v| v.nil? }
88
+ end
89
+
90
+ def check_state(state)
91
+ raise DotloopApi::UnmatchState.new('State does ont match. Possible CSRF!') if state && state != @config.state
92
+ end
93
+
94
+ def check_refresh_token
95
+ raise DotloopApi::MissingRefreshToken.new('Missing token to refresh') unless @config.access_token
96
+ end
97
+
98
+ def check_revoke_token
99
+ raise DotloopApi::MissingRevokeToken.new('Missing token to revoke') unless @config.access_token
100
+ end
101
+
102
+ def handle_error(response)
103
+ return if response.code == 200 || !(error_class = DotloopApi::CodeMap.call(response.code))
104
+ raise error_class.new("Error communicating: Response code #{response.code}")
105
+ end
106
+
107
+ def parse_url_response(url)
108
+ params = URI.decode_www_form(URI(url).query)
109
+ Hash[*params.flatten].symbolize_keys
110
+ end
111
+
112
+ def clear_auth
113
+ @config.attributes = { access_token: nil, token_type: nil, refresh_token: nil, expires_in: nil, scope: nil }
114
+ end
115
+
116
+ def base_uri
117
+ 'https://auth.dotloop.com/oauth/'
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,89 @@
1
+ module DotloopApi
2
+ class Client
3
+ include HTTParty
4
+ require 'active_support/all'
5
+ base_uri 'https://api-gateway.dotloop.com/public/v2/'
6
+
7
+ attr_accessor :access_token
8
+ attr_accessor :application
9
+
10
+ def initialize(access_token:, application: 'dotloop')
11
+ @access_token = access_token
12
+ @application = application
13
+ end
14
+
15
+ def get(page, params = {})
16
+ response = raw(page, params)
17
+ self.class.snakify(response)
18
+ end
19
+
20
+ def patch(page, model)
21
+ response = self.class.patch(page, query: model.attributes, headers: headers, timeout: 60)
22
+ handle_dotloop_error(response.code) if response.code != 200
23
+ self.class.snakify(response.parsed_response)
24
+ end
25
+
26
+ def post(page, model)
27
+ response = self.class.post(page, query: model.attributes, headers: headers, timeout: 60)
28
+ handle_dotloop_error(response.code) if response.code != 200
29
+ self.class.snakify(response.parsed_response)
30
+ end
31
+
32
+ def delete(page)
33
+ response = self.class.delete(page, headers: headers, timeout: 60)
34
+ handle_dotloop_error(response.code) if response.code != 200
35
+ self.class.snakify(response.parsed_response)
36
+ end
37
+
38
+ def raw(page, params = {})
39
+ response = self.class.get(page, query: params, headers: headers, timeout: 60)
40
+ handle_dotloop_error(response.code) if response.code != 200
41
+ response.parsed_response
42
+ end
43
+
44
+ def download(page, params = {})
45
+ response = self.class.get(page, query: params, headers: download_headers, timeout: 360)
46
+ handle_dotloop_error(response.code) if response.code != 200
47
+ response.body
48
+ end
49
+
50
+ def handle_dotloop_error(code)
51
+ return unless (error_class = DotloopApi::CodeMap.call(code))
52
+ raise error_class.new "Error communicating: Response code #{code}"
53
+ end
54
+
55
+ def account
56
+ @account ||= DotloopApi::EndPoints::Account.new(client: self).find
57
+ end
58
+
59
+ def Profile # rubocop:disable Naming/MethodName
60
+ @Profile ||= DotloopApi::EndPoints::Profile.new(client: self) # rubocop:disable Naming/VariableName
61
+ end
62
+
63
+ def Contact # rubocop:disable Naming/MethodName
64
+ @Contact ||= DotloopApi::EndPoints::Contact.new(client: self) # rubocop:disable Naming/VariableName
65
+ end
66
+
67
+ def self.snakify(hash)
68
+ if hash.is_a? Array
69
+ hash.map { |item| item.to_snake_keys.symbolize_keys }
70
+ else
71
+ hash.to_snake_keys.symbolize_keys
72
+ end
73
+ end
74
+
75
+ private
76
+
77
+ def download_headers
78
+ headers.merge('Accept' => 'application/pdf')
79
+ end
80
+
81
+ def headers
82
+ {
83
+ 'Authorization' => "Bearer #{@access_token}",
84
+ 'User-Agent' => @application,
85
+ 'Accept' => '*/*'
86
+ }
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,19 @@
1
+ module DotloopApi
2
+ module EndPoints
3
+ class Account
4
+ def initialize(client:)
5
+ @client = client
6
+ end
7
+
8
+ def find
9
+ build_model(@client.get('/account')[:data])
10
+ end
11
+
12
+ private
13
+
14
+ def build_model(attrs)
15
+ DotloopApi::Models::Contact.new(attrs)
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,22 @@
1
+ module DotloopApi
2
+ module EndPoints
3
+ class Activity < DotloopApi::EndPoints::Batch
4
+ attr_accessor :profile_id
5
+ attr_accessor :loop_id
6
+ undef_method :find
7
+ undef_method :save
8
+ undef_method :create
9
+ undef_method :delete
10
+
11
+ def initialize(client:, profile_id: nil, loop_id: nil)
12
+ @profile_id = profile_id
13
+ @loop_id = loop_id
14
+ super(client: client, path: path, type: DotloopApi::Models::Profile::Loop::Activity)
15
+ end
16
+
17
+ def path
18
+ "/profile/#{@profile_id}/loop/#{@loop_id}/activity"
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,37 @@
1
+ module DotloopApi
2
+ module EndPoints
3
+ class Base
4
+ include DotloopApi::EndPoints::ModelBuilder
5
+ include Virtus.model
6
+ attribute :client
7
+ attribute :path
8
+ attribute :type
9
+ def all
10
+ @client.get(path)[:data].map { |attrs| build_model(attrs) }
11
+ end
12
+
13
+ def find(id:)
14
+ build_model(@client.get(single_path(id))[:data])
15
+ end
16
+
17
+ def save(model)
18
+ return create(model) unless model.id
19
+ response = @client.patch(single_path(model.id), model)
20
+ build_model(response[:data])
21
+ end
22
+
23
+ def create(model)
24
+ response = @client.post(path, model)
25
+ build_model(response[:data])
26
+ end
27
+
28
+ def delete(model)
29
+ @client.delete(single_path(model.id))
30
+ end
31
+
32
+ def single_path(id)
33
+ [path, '/', id.to_i].join
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,34 @@
1
+ module DotloopApi
2
+ module EndPoints
3
+ class Batch < DotloopApi::EndPoints::Base
4
+ MAX_LOOPS = 500
5
+ def all(options = {})
6
+ options_to_params(options)
7
+ return batch(options) if options[:batch_number]
8
+ loop_over_all_batches
9
+ end
10
+
11
+ def batch(options = {})
12
+ options_to_params(options)
13
+ @client.get(path, @param_helper.params)[:data].map { |attrs| build_model(attrs) }
14
+ end
15
+
16
+ private
17
+
18
+ def options_to_params(options)
19
+ @param_helper = DotloopApi::EndPoints::ParamHelper.new(options)
20
+ end
21
+
22
+ def loop_over_all_batches
23
+ collection = []
24
+ (1..MAX_LOOPS).each do |i|
25
+ @param_helper.batch_number = i
26
+ current_batch = batch(@param_helper.attributes)
27
+ collection += current_batch
28
+ break if current_batch.size < @param_helper.batch_size
29
+ end
30
+ collection
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,9 @@
1
+ module DotloopApi
2
+ module EndPoints
3
+ class Contact < DotloopApi::EndPoints::Batch
4
+ def initialize(client:)
5
+ super(client: client, path: '/contact', type: DotloopApi::Models::Contact)
6
+ end
7
+ end
8
+ end
9
+ end