entrata 1.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.
- checksums.yaml +7 -0
- data/.ci +144 -0
- data/.gitignore +2 -0
- data/.rspec +3 -0
- data/CODEOWNERS +1 -0
- data/Gemfile +4 -0
- data/README.md +110 -0
- data/Rakefile +7 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/entrata.gemspec +27 -0
- data/lib/entrata/client.rb +152 -0
- data/lib/entrata/parameter/customer.rb +20 -0
- data/lib/entrata/parameter/customer_preferences.rb +61 -0
- data/lib/entrata/request/base.rb +117 -0
- data/lib/entrata/request/error.rb +21 -0
- data/lib/entrata/request/get_access_token.rb +36 -0
- data/lib/entrata/request/get_client_info.rb +21 -0
- data/lib/entrata/request/get_ils_properties_data.rb +24 -0
- data/lib/entrata/request/get_property_info.rb +24 -0
- data/lib/entrata/request/process_property_activation.rb +28 -0
- data/lib/entrata/request/send_inactive_leads.rb +104 -0
- data/lib/entrata/request/send_lead_details.rb +64 -0
- data/lib/entrata/request/send_reactivate_leads.rb +84 -0
- data/lib/entrata/test_client.rb +93 -0
- data/lib/entrata/utils/static_file_fetcher.rb +25 -0
- data/lib/entrata/version.rb +3 -0
- data/lib/entrata.rb +8 -0
- data/lib/tasks/package.rake +49 -0
- metadata +168 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 2316690e03ac91b2580dbaab63c56ac93afc70eaef268c787d206aba024a484f
|
|
4
|
+
data.tar.gz: 574ed8030e63871b2cb1a59afd3e5ea25a57a995b790ba5a2a6c606aa3965db3
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 678ea4ce2d8dbf797263d707df80c21351f26085c6e690d5024a23a346c52a272695f049e23c3a74cf536ff96257156878c9ba019ee050d62046f42ace087fe2
|
|
7
|
+
data.tar.gz: e92102c9060201f39c692dbac217f27b77b60d131ac5ff12d33b639453225d728fd02c6b1548bad0423b12a04be91820e495d5e4736bd76498ec1ddf4b4d7c64
|
data/.ci
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
#!/usr/bin/env groovy
|
|
2
|
+
|
|
3
|
+
// https://github.com/apartmentlist/ci-shared-library
|
|
4
|
+
@Library('ci-shared-library')_
|
|
5
|
+
|
|
6
|
+
// Log Rotation
|
|
7
|
+
properties([
|
|
8
|
+
buildDiscarder(
|
|
9
|
+
logRotator(
|
|
10
|
+
artifactDaysToKeepStr: '',
|
|
11
|
+
artifactNumToKeepStr: '',
|
|
12
|
+
daysToKeepStr: '30',
|
|
13
|
+
numToKeepStr: '100'
|
|
14
|
+
)
|
|
15
|
+
)
|
|
16
|
+
]) //properties
|
|
17
|
+
|
|
18
|
+
// Generate unique slave labels
|
|
19
|
+
def k8s_label = "${UUID.randomUUID().toString()}"
|
|
20
|
+
|
|
21
|
+
pipeline {
|
|
22
|
+
environment {
|
|
23
|
+
APP_NAME = 'entrata'
|
|
24
|
+
APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE = 'true'
|
|
25
|
+
CI = 'true'
|
|
26
|
+
CLOUDSDK_CORE_DISABLE_PROMPTS = '1'
|
|
27
|
+
GIT_COMMIT_SHORT = sh(script: "git rev-parse --short ${GIT_COMMIT}", returnStdout: true).trim()
|
|
28
|
+
GIT_MESSAGE = sh(script: "git log --format=%B -n 1 ${GIT_COMMIT}", returnStdout: true).trim()
|
|
29
|
+
GIT_USER = sh(script: "git log -1 --pretty=format:'%ae'", returnStdout: true).trim()
|
|
30
|
+
GITHUB_URL = "https://github.com"
|
|
31
|
+
LANG = "en_US.UTF-8"
|
|
32
|
+
LANGUAGE = "en_US:en"
|
|
33
|
+
LC_ALL = "en_US.UTF-8"
|
|
34
|
+
PRODUCTION_DEPLOY="false"
|
|
35
|
+
SLACK_SUCCESS_CHANNEL = "#staging-releases"
|
|
36
|
+
SLACK_FAILURE_CHANNEL = "#supply_log_info"
|
|
37
|
+
} // environment
|
|
38
|
+
|
|
39
|
+
agent {
|
|
40
|
+
kubernetes {
|
|
41
|
+
label k8s_label
|
|
42
|
+
defaultContainer 'jnlp'
|
|
43
|
+
yaml """
|
|
44
|
+
---
|
|
45
|
+
apiVersion: v1
|
|
46
|
+
kind: Pod
|
|
47
|
+
metadata:
|
|
48
|
+
name: test
|
|
49
|
+
spec:
|
|
50
|
+
restartPolicy: Never
|
|
51
|
+
containers:
|
|
52
|
+
- name: ruby
|
|
53
|
+
image: gcr.io/alist-development/ruby:2.7.6
|
|
54
|
+
imagePullPolicy: Always
|
|
55
|
+
resources:
|
|
56
|
+
requests:
|
|
57
|
+
memory: "1024Mi"
|
|
58
|
+
cpu: "1"
|
|
59
|
+
requests:
|
|
60
|
+
memory: "1024Mi"
|
|
61
|
+
cpu: "1"
|
|
62
|
+
command:
|
|
63
|
+
- "tail"
|
|
64
|
+
- "-f"
|
|
65
|
+
- "/dev/null"
|
|
66
|
+
"""
|
|
67
|
+
} // kubernetes
|
|
68
|
+
} // agent
|
|
69
|
+
|
|
70
|
+
options {
|
|
71
|
+
timestamps()
|
|
72
|
+
timeout(time: 10, unit: 'MINUTES')
|
|
73
|
+
ansiColor('xterm')
|
|
74
|
+
} // options
|
|
75
|
+
|
|
76
|
+
stages {
|
|
77
|
+
stage('Preparation') {
|
|
78
|
+
parallel {
|
|
79
|
+
stage('Slack') {
|
|
80
|
+
steps {
|
|
81
|
+
slackPreparation()
|
|
82
|
+
} // steps
|
|
83
|
+
} // stage - Slack
|
|
84
|
+
stage('Build Description') {
|
|
85
|
+
steps {
|
|
86
|
+
buildDescription()
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
stage('Bundle') {
|
|
90
|
+
steps {
|
|
91
|
+
container('ruby') {
|
|
92
|
+
sh 'gem install bundler:2.2.6 --no-post-install-message --no-document'
|
|
93
|
+
sh 'bundle install -j 12'
|
|
94
|
+
} // container
|
|
95
|
+
} // steps
|
|
96
|
+
} // stage - Bundle
|
|
97
|
+
} //parallel
|
|
98
|
+
} //stage - Preparation
|
|
99
|
+
|
|
100
|
+
stage('Testing') {
|
|
101
|
+
when {
|
|
102
|
+
allOf {
|
|
103
|
+
changeRequest target: 'main'
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
parallel {
|
|
108
|
+
stage('Run RSpec') {
|
|
109
|
+
steps {
|
|
110
|
+
container('ruby') {
|
|
111
|
+
sh label: 'RSpec', script: 'bundle exec rake'
|
|
112
|
+
} // container
|
|
113
|
+
} // steps
|
|
114
|
+
} // stage
|
|
115
|
+
} //parallel
|
|
116
|
+
} //stage
|
|
117
|
+
|
|
118
|
+
stage("Publish") {
|
|
119
|
+
when {
|
|
120
|
+
allOf {
|
|
121
|
+
branch "main"
|
|
122
|
+
not { changeRequest() }
|
|
123
|
+
}
|
|
124
|
+
} // when
|
|
125
|
+
parallel {
|
|
126
|
+
stage("to Github packages") {
|
|
127
|
+
steps {
|
|
128
|
+
publishRubyGemToGHP("ruby")
|
|
129
|
+
}
|
|
130
|
+
} // stage - to Github packages
|
|
131
|
+
} // parallel
|
|
132
|
+
} // stage - Publish
|
|
133
|
+
} // stages
|
|
134
|
+
|
|
135
|
+
post {
|
|
136
|
+
success {
|
|
137
|
+
success('alist-production')
|
|
138
|
+
} // success
|
|
139
|
+
aborted {
|
|
140
|
+
aborted('alist-production')
|
|
141
|
+
} // aborted
|
|
142
|
+
} // post
|
|
143
|
+
|
|
144
|
+
} // pipeline
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/CODEOWNERS
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
* @apartmentlist/integrations
|
data/Gemfile
ADDED
data/README.md
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
[](https://circleci.com/gh/apartmentlist/entrata)
|
|
2
|
+
|
|
3
|
+
# Entrata
|
|
4
|
+
|
|
5
|
+
Ruby client for Entrata API
|
|
6
|
+
|
|
7
|
+
## Versions
|
|
8
|
+
* 0.1.0 Initial Release
|
|
9
|
+
* 0.1.1 Extend request timeout to 10 minutes for all requests
|
|
10
|
+
* 0.1.2 Request GetIlsPropertiesData via `curl` to avoid errors on large responses
|
|
11
|
+
* 0.2.0 Add buildingName to fixture file for getIlsPropertiesData success response
|
|
12
|
+
* 0.2.1 Make paths in StaticFileFetcher relative
|
|
13
|
+
* 0.2.2 Add buildingName to unit C606 in fixture file for successful getIlsPropertiesData
|
|
14
|
+
* 0.3.0 Raise a Request::Error when the response is not valid JSON and update Travis Ruby version
|
|
15
|
+
* 0.3.2 Expose lead request body for troubleshooting when sending leads
|
|
16
|
+
* 1.0.0 Allow us to pass renter min and max price range in
|
|
17
|
+
* 1.0.4 Make entrata gem compatible with Ruby 3 syntax updates
|
|
18
|
+
* 1.0.5 No functional changes. Updating build pipeline to publish to GitHub Packages.
|
|
19
|
+
* 1.1.0 Adding services to manage lea lite inactive and reactive guestcard requests.
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
Add this line to your application's Gemfile:
|
|
24
|
+
|
|
25
|
+
```ruby
|
|
26
|
+
gem 'entrata'
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
And then execute:
|
|
30
|
+
|
|
31
|
+
$ bundle
|
|
32
|
+
|
|
33
|
+
Or install it yourself as:
|
|
34
|
+
|
|
35
|
+
$ gem install entrata
|
|
36
|
+
|
|
37
|
+
## Usage
|
|
38
|
+
|
|
39
|
+
### Configuration
|
|
40
|
+
|
|
41
|
+
No gem configuration, but getting an access token (via `Entrata::Client.get_access_token`) requires `client_id` and `client_secret` in addition to `auth_code` provided by Entrata on property activation.
|
|
42
|
+
|
|
43
|
+
### API Resources
|
|
44
|
+
|
|
45
|
+
All API access is performed via the `Entrata::Client` class.
|
|
46
|
+
|
|
47
|
+
There are two usage patterns:
|
|
48
|
+
1. Gathering client credential and PMC subdomain
|
|
49
|
+
2. Making authenticated requests to API resources using saved credential and PMC subdomain
|
|
50
|
+
|
|
51
|
+
#### Gathering client credential and PMC subdomain
|
|
52
|
+
Class methods are available for convenience of this one-time call sequence per PMC
|
|
53
|
+
```
|
|
54
|
+
auth_code = 'received from activation endpoint'
|
|
55
|
+
client_id = ENV['ENTRATA_CLIENT_ID'] # Apartmentlist assigned client_id
|
|
56
|
+
client_secret = ENV['ENTRATA_CLIENT_SECRET'] # Apartmentlist current client_secret
|
|
57
|
+
|
|
58
|
+
token_hash = Entrata::Client.get_access_token(auth_code: auth_code,
|
|
59
|
+
client_id: client_id,
|
|
60
|
+
client_secret: client_secret)
|
|
61
|
+
|
|
62
|
+
client_info = Entrata::Client.get_client_subdomain(token_hash['token'])
|
|
63
|
+
subdomain = client_info['subdomain']
|
|
64
|
+
|
|
65
|
+
# create or update Entrata credential with token and subdomain
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
#### Making authenticated requests to API resources
|
|
69
|
+
An instance of the `Entrata::Client` may be created per PMC to request authenticated resources available under its subdomain.
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
credential = #find by PMC or iterate all entrata credentials
|
|
73
|
+
token = credential.fields['access_token']
|
|
74
|
+
subdomain = credential.fields['subdomain']
|
|
75
|
+
|
|
76
|
+
client = Entrata::Client.new(subdomain: subdomain, token: token)
|
|
77
|
+
|
|
78
|
+
remote_property_id = #get from activation
|
|
79
|
+
|
|
80
|
+
client.process_property_activation(remote_property_id) #defaults to 'approve'-ing the activation
|
|
81
|
+
property_hash = client.get_property_info(remote_property_id)
|
|
82
|
+
multiple_property_hash = client.get_ils_properties_data([remote_property_id])
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Testing
|
|
86
|
+
|
|
87
|
+
A test client is available for integration testing in your consuming application.
|
|
88
|
+
|
|
89
|
+
`Entrata::TestClient` behaves like `Entrata::Client`, but returns static responses or raises errors just like the real client would. It never makes any real HTTP requests.
|
|
90
|
+
|
|
91
|
+
`Entrata::TestClient` defaults to providing successful (static) responses collected from the real API.
|
|
92
|
+
|
|
93
|
+
You may force failure by providing specific ("fail") input values. See below for specific trigger parameters:
|
|
94
|
+
```
|
|
95
|
+
# Providing "fail" to specific parameters will raise `Entrata::Request::Error` just like the real client would.
|
|
96
|
+
Entrata::TestClient.get_access_token(auth_code: 'fail', client_id: 'anything', client_secret: 'anything') #BOOM!
|
|
97
|
+
Entrata::TestClient.get_client_info('fail') #BOOM!
|
|
98
|
+
|
|
99
|
+
test_client = Entrata::TestClient.new(subdomain: 'anything', token: 'anything')
|
|
100
|
+
test_client.get_property_info('fail') #BOOM!
|
|
101
|
+
test_client.process_property_activation('fail') #BOOM!
|
|
102
|
+
test_client.get_ils_properties_data(['at', 'least', 'one', 'fail']) #BOOM!
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Development
|
|
107
|
+
|
|
108
|
+
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.
|
|
109
|
+
|
|
110
|
+
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).
|
data/Rakefile
ADDED
data/bin/console
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require 'bundler/setup'
|
|
4
|
+
require 'pry'
|
|
5
|
+
require 'entrata'
|
|
6
|
+
|
|
7
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
|
8
|
+
# with your gem easier. You can also use a different console, if you like.
|
|
9
|
+
|
|
10
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
|
11
|
+
# require 'pry'
|
|
12
|
+
# Pry.start
|
|
13
|
+
|
|
14
|
+
require 'irb'
|
|
15
|
+
IRB.start
|
data/bin/setup
ADDED
data/entrata.gemspec
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
|
+
require 'entrata/version'
|
|
5
|
+
|
|
6
|
+
Gem::Specification.new do |spec|
|
|
7
|
+
spec.name = 'entrata'
|
|
8
|
+
spec.version = Entrata::VERSION
|
|
9
|
+
spec.authors = ['Justin Richard']
|
|
10
|
+
spec.email = ['justin@apartmentlist.com']
|
|
11
|
+
|
|
12
|
+
spec.summary = 'Ruby client of Entrata API'
|
|
13
|
+
spec.homepage = 'https://github.com/apartmentlist/entrata'
|
|
14
|
+
|
|
15
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
|
16
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
17
|
+
spec.require_paths = ['lib']
|
|
18
|
+
|
|
19
|
+
spec.add_runtime_dependency 'activesupport', '~> 5.2'
|
|
20
|
+
spec.add_runtime_dependency 'faraday', '< 1.0'
|
|
21
|
+
|
|
22
|
+
spec.add_development_dependency 'bundler'
|
|
23
|
+
spec.add_development_dependency 'pry-byebug', '~> 3.3'
|
|
24
|
+
spec.add_development_dependency 'rake'
|
|
25
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
|
26
|
+
spec.add_development_dependency 'webmock', '~> 1.21'
|
|
27
|
+
end
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
require 'faraday'
|
|
2
|
+
require 'uri'
|
|
3
|
+
require 'entrata/request/get_access_token'
|
|
4
|
+
require 'entrata/request/get_client_info'
|
|
5
|
+
require 'entrata/request/get_ils_properties_data'
|
|
6
|
+
require 'entrata/request/get_property_info'
|
|
7
|
+
require 'entrata/request/process_property_activation'
|
|
8
|
+
require 'entrata/request/send_lead_details'
|
|
9
|
+
require 'entrata/request/send_inactive_leads'
|
|
10
|
+
require 'entrata/request/send_reactivate_leads'
|
|
11
|
+
|
|
12
|
+
# Client to access all API resources through
|
|
13
|
+
module Entrata
|
|
14
|
+
class Client
|
|
15
|
+
def initialize(subdomain:, token:)
|
|
16
|
+
@subdomain = subdomain
|
|
17
|
+
@token = token
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
class << self
|
|
21
|
+
def get_access_token(auth_code:, client_id:, client_secret:,
|
|
22
|
+
conn: Faraday.new('https://sync.entrata.com'))
|
|
23
|
+
Request::GetAccessToken.new(
|
|
24
|
+
conn: conn,
|
|
25
|
+
auth_code: auth_code,
|
|
26
|
+
client_id: client_id,
|
|
27
|
+
client_secret: client_secret
|
|
28
|
+
).perform
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# requires token directly; typical use is to #get_access_token, then
|
|
32
|
+
# #get_client_info immediately with the same client
|
|
33
|
+
def get_client_info(token, conn: Faraday.new('https://sync.entrata.com'))
|
|
34
|
+
conn.authorization :Bearer, token
|
|
35
|
+
|
|
36
|
+
Request::GetClientInfo.new(conn: conn).perform
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def get_property_info(property_id)
|
|
41
|
+
Request::GetPropertyInfo.new(
|
|
42
|
+
conn: conn,
|
|
43
|
+
property_id: property_id
|
|
44
|
+
).perform
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def process_property_activation(property_id, activation_status: 'approve')
|
|
48
|
+
Request::ProcessPropertyActivation.new(
|
|
49
|
+
conn: conn,
|
|
50
|
+
property_id: property_id,
|
|
51
|
+
activation_status: activation_status
|
|
52
|
+
).perform
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def get_ils_properties_data(property_ids)
|
|
56
|
+
Request::GetIlsPropertiesData.new(
|
|
57
|
+
conn: conn,
|
|
58
|
+
property_ids: property_ids
|
|
59
|
+
).perform_with_curl
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def send_lead_details(customer:, property_id:, preferences: nil)
|
|
63
|
+
Request::SendLeadDetails.new(
|
|
64
|
+
conn: conn,
|
|
65
|
+
customer: customer,
|
|
66
|
+
preferences: preferences,
|
|
67
|
+
property_id: property_id
|
|
68
|
+
).perform
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def send_inactive_leads(customer:, lead_source_id:, leasing_agent_id:, preferences:, property_id:)
|
|
72
|
+
Request::SendInactiveLeads.new(
|
|
73
|
+
conn: basic_auth_conn,
|
|
74
|
+
customer: customer,
|
|
75
|
+
lead_source_id: lead_source_id,
|
|
76
|
+
leasing_agent_id: leasing_agent_id,
|
|
77
|
+
preferences: preferences,
|
|
78
|
+
property_id: property_id
|
|
79
|
+
).perform
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def send_reactivate_leads(applicant_id:, application_id:, customer:, preferences:, property_id:)
|
|
83
|
+
Request::SendReactivateLeads.new(
|
|
84
|
+
conn: basic_auth_conn,
|
|
85
|
+
applicant_id: applicant_id,
|
|
86
|
+
application_id: application_id,
|
|
87
|
+
customer: customer,
|
|
88
|
+
preferences: preferences,
|
|
89
|
+
property_id: property_id
|
|
90
|
+
).perform
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def lead_details_request_body(customer:, property_id:, preferences: nil)
|
|
94
|
+
Request::SendLeadDetails.new(
|
|
95
|
+
conn: conn,
|
|
96
|
+
customer: customer,
|
|
97
|
+
preferences: preferences,
|
|
98
|
+
property_id: property_id
|
|
99
|
+
).body
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def inactive_lead_request_body(customer:, lead_source_id:, leasing_agent_id:, preferences:, property_id:)
|
|
103
|
+
Request::SendInactiveLeads.new(
|
|
104
|
+
conn: basic_auth_conn,
|
|
105
|
+
customer: customer,
|
|
106
|
+
lead_source_id: lead_source_id,
|
|
107
|
+
leasing_agent_id: leasing_agent_id,
|
|
108
|
+
preferences: preferences,
|
|
109
|
+
property_id: property_id
|
|
110
|
+
).body
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def reactivate_lead_request_body(applicant_id:, application_id:, customer:, preferences:, property_id:)
|
|
114
|
+
Request::SendReactivateLeads.new(
|
|
115
|
+
conn: basic_auth_conn,
|
|
116
|
+
applicant_id: applicant_id,
|
|
117
|
+
application_id: application_id,
|
|
118
|
+
customer: customer,
|
|
119
|
+
preferences: preferences,
|
|
120
|
+
property_id: property_id
|
|
121
|
+
).body
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
private
|
|
125
|
+
|
|
126
|
+
def url
|
|
127
|
+
URI::HTTPS.build(host: "#{subdomain}.entrata.com").to_s
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def conn
|
|
131
|
+
@conn_with_auth ||= Faraday.new(url) do |request|
|
|
132
|
+
# MUST specify adapter when configuring with block! Otherwise no adapter
|
|
133
|
+
# will be set: https://github.com/lostisland/faraday/issues/121
|
|
134
|
+
request.adapter Faraday.default_adapter
|
|
135
|
+
request.authorization :Bearer, token
|
|
136
|
+
request.options[:timeout] = 600 # 10 minutes
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def basic_auth_conn
|
|
141
|
+
@basic_auth_conn ||= Faraday.new(url) do |request|
|
|
142
|
+
# MUST specify adapter when configuring with block! Otherwise no adapter
|
|
143
|
+
# will be set: https://github.com/lostisland/faraday/issues/121
|
|
144
|
+
request.adapter Faraday.default_adapter
|
|
145
|
+
request.authorization :Basic, token
|
|
146
|
+
request.options[:timeout] = 600 # 10 minutes
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
attr_reader :token, :subdomain
|
|
151
|
+
end
|
|
152
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module Entrata
|
|
2
|
+
module Parameter
|
|
3
|
+
# Wraps the user-related data that we pass to Entrata. All of the Customer
|
|
4
|
+
# attributes are required when inserting a lead.
|
|
5
|
+
class Customer
|
|
6
|
+
attr_reader :email, :first_name, :last_name, :phone
|
|
7
|
+
|
|
8
|
+
# @param email [String] Renter email
|
|
9
|
+
# @param first_name [String] Renter first name
|
|
10
|
+
# @param last_name [String] Renter last name
|
|
11
|
+
# @param phone [Integer] Renter phone number, digits only
|
|
12
|
+
def initialize(email:, first_name:, last_name:, phone:)
|
|
13
|
+
@email = email
|
|
14
|
+
@first_name = first_name
|
|
15
|
+
@last_name = last_name
|
|
16
|
+
@phone = phone
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
module Entrata
|
|
2
|
+
module Parameter
|
|
3
|
+
# Wraps the customer preference data that we pass to Entrata. All of the
|
|
4
|
+
# preference attributes are optional when inserting a lead.
|
|
5
|
+
class CustomerPreferences
|
|
6
|
+
attr_reader :beds,
|
|
7
|
+
:baths,
|
|
8
|
+
:comments,
|
|
9
|
+
:lease_terms,
|
|
10
|
+
:move_in_date,
|
|
11
|
+
:number_of_pets,
|
|
12
|
+
:preferred_floorplan_id,
|
|
13
|
+
:preferred_unit_id,
|
|
14
|
+
:min_price,
|
|
15
|
+
:max_price
|
|
16
|
+
|
|
17
|
+
# @param beds [Integer] Number of beds the renter is interested in
|
|
18
|
+
# @param baths [Float] Number of baths the renter is interested in
|
|
19
|
+
# @param comments [String] Notes about the renter's preferences plus any
|
|
20
|
+
# information about a tour if there is one, plus the renter's message
|
|
21
|
+
# for the property. Entrata only gives us one key for comments, so we
|
|
22
|
+
# need to put everything there.
|
|
23
|
+
# @param lease_terms [String] Length of preferred lease term,
|
|
24
|
+
# e.g. '12 months'
|
|
25
|
+
# @param move_in_date [Date] Renter move in date
|
|
26
|
+
# @param number_of_pets [String] The number of pets the renter has
|
|
27
|
+
# @param preferred_floorplan_id [Integer] Entrata's ID for the floorplan
|
|
28
|
+
# the renter is interested in. They do not verify that this ID matches
|
|
29
|
+
# any real floorplan on the property in their system.
|
|
30
|
+
# @param preferred_unit_id [Integer] Entrata's ID for the unit the renter
|
|
31
|
+
# is interested in. They do not verify that this ID matches any real
|
|
32
|
+
# unit on the property in their system.
|
|
33
|
+
# @param min_price [Float] Renter's minimum budget
|
|
34
|
+
# @param max_price [Float] Renter's maximum budget
|
|
35
|
+
def initialize(
|
|
36
|
+
beds: nil,
|
|
37
|
+
baths: nil,
|
|
38
|
+
comments: nil,
|
|
39
|
+
lease_terms: nil,
|
|
40
|
+
move_in_date: nil,
|
|
41
|
+
number_of_pets: nil,
|
|
42
|
+
preferred_floorplan_id: nil,
|
|
43
|
+
preferred_unit_id: nil,
|
|
44
|
+
min_price: nil,
|
|
45
|
+
max_price: nil
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
@beds = beds
|
|
49
|
+
@baths = baths
|
|
50
|
+
@comments = comments
|
|
51
|
+
@lease_terms = lease_terms
|
|
52
|
+
@move_in_date = move_in_date
|
|
53
|
+
@number_of_pets = number_of_pets
|
|
54
|
+
@preferred_floorplan_id = preferred_floorplan_id
|
|
55
|
+
@preferred_unit_id = preferred_unit_id
|
|
56
|
+
@min_price = min_price
|
|
57
|
+
@max_price = max_price
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
require 'faraday'
|
|
2
|
+
require 'json'
|
|
3
|
+
require 'entrata/request/error'
|
|
4
|
+
require 'open3'
|
|
5
|
+
|
|
6
|
+
module Entrata
|
|
7
|
+
module Request
|
|
8
|
+
class Base
|
|
9
|
+
def initialize(conn:, **other_args)
|
|
10
|
+
@conn = conn
|
|
11
|
+
set_content_header!
|
|
12
|
+
|
|
13
|
+
after_initialize(**other_args)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Initialization call back for subclasses init parameter access
|
|
17
|
+
def after_initialize(**params)
|
|
18
|
+
# No-op, this method is a call back for subclasses
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def resource_name
|
|
22
|
+
raise NotImplementedError, 'implement with exact case of resource'
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def resource_path
|
|
26
|
+
'/api/ils/internetlisting'
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def resource_auth
|
|
30
|
+
{ type: 'oauth' }
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def resource_params
|
|
34
|
+
{}
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def perform
|
|
38
|
+
response = conn.post(resource_path) do |request|
|
|
39
|
+
request.body = body
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
result_hash = try_parsing_body(response.body)
|
|
43
|
+
|
|
44
|
+
if response.success? && result_hash
|
|
45
|
+
result_hash
|
|
46
|
+
else
|
|
47
|
+
error = create_response_error(response.body)
|
|
48
|
+
raise error
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def perform_with_curl
|
|
53
|
+
stdout, _stderr, status = Open3.capture3(curl_command,)
|
|
54
|
+
result_hash = try_parsing_body(stdout)
|
|
55
|
+
|
|
56
|
+
if status == 0 && result_hash
|
|
57
|
+
result_hash
|
|
58
|
+
else
|
|
59
|
+
error = create_response_error(stdout)
|
|
60
|
+
raise error
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def body
|
|
65
|
+
{
|
|
66
|
+
auth: resource_auth,
|
|
67
|
+
method: {
|
|
68
|
+
name: resource_name,
|
|
69
|
+
params: resource_params
|
|
70
|
+
}
|
|
71
|
+
}.to_json
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
private
|
|
75
|
+
|
|
76
|
+
def try_parsing_body(raw_body)
|
|
77
|
+
@parsed_body = JSON.parse(raw_body)
|
|
78
|
+
@parsed_body.fetch('response', {}).fetch('result', nil)
|
|
79
|
+
rescue JSON::ParserError
|
|
80
|
+
nil
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def create_response_error(message)
|
|
84
|
+
error_hash = try_parsing_error_hash(message)
|
|
85
|
+
if error_hash
|
|
86
|
+
Entrata::Request::Error.new(error_hash['message'], error_hash['code'])
|
|
87
|
+
else
|
|
88
|
+
Entrata::Request::Error.new(message)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def try_parsing_error_hash(error)
|
|
93
|
+
parsed_error = JSON.parse(error)
|
|
94
|
+
parsed_error.fetch('response', {}).fetch('error', nil)
|
|
95
|
+
rescue JSON::ParserError
|
|
96
|
+
nil
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def set_content_header!
|
|
100
|
+
conn.headers['Content-Type'] = 'application/json'
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def curl_command
|
|
104
|
+
full_url =
|
|
105
|
+
URI::HTTPS.build(host: conn.url_prefix.host, path: resource_path)
|
|
106
|
+
|
|
107
|
+
<<-CURL_COMMAND.gsub!(/\s+/, ' ').strip
|
|
108
|
+
curl #{full_url.to_s} -X POST -H 'Content-Type: application/json'
|
|
109
|
+
-H 'Authorization: #{conn.headers['Authorization']}'
|
|
110
|
+
--data '#{body}'
|
|
111
|
+
CURL_COMMAND
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
attr_reader :conn, :hostname, :parsed_body
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|