redox-client 0.0.0.pre.0 → 0.5.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cc2920cfcaf61217ebd83d4d252b9a78a2a182124d40fcdf390555584599d2c5
4
- data.tar.gz: 6afc487c8b22dab175ea6f92c3b22161c8cce51c316aa86249efb865a0343029
3
+ metadata.gz: 7e25af2125fc17dda61023bc6513a886a24fe2e3b6fe9fae958ba6c43edb351b
4
+ data.tar.gz: 33b595b60b546ae11e18c54b6db60cdfb0c6d21681c0f69d8f1d8d9d3afea1ca
5
5
  SHA512:
6
- metadata.gz: d2d1a6488c76791aa5bc1b78208758ed735abd761a90a38a6bccbc356d2559538b9b3b312b5294fbbe0a6171e533b6a5a7383e3099aa543770e8ca7c11c2db10
7
- data.tar.gz: cb19077d938e6e75112e433d5b1832083d4c2be54dd8bf8643ae13e8f3921788b57ad71ae5d6cca214db09dbcd37655ff565a62ff0ace7c606e5cf372cd25f03
6
+ metadata.gz: bdc40d505021f48100f7a2f176e4a0811611b90de70cf9caee2fdb489d707f1266a815564dbf36567bab0de64ad79642b65914169ec76b7c42218d8212118271
7
+ data.tar.gz: 23c892f9c708256e3ed4d6f83158501daef2f3ee155dfde99938ef4bc5de2071f27850d1e036604556ca6527a5ec468e388bff38b7c33ba66732073ac56184de
@@ -0,0 +1,47 @@
1
+ # Changelog
2
+
3
+ ## [Unreleased]
4
+ ### Added
5
+
6
+ ### Changed
7
+
8
+ ### Removed
9
+
10
+ ## [0.5.0] - 2020-10-29
11
+ ### Added
12
+ - File Upload
13
+ - Media.New
14
+
15
+ ### Changed
16
+ - fixed: source was re-authenticating every request (DateTime math was incorrect)
17
+
18
+ ### Removed
19
+ - remove SchedulingBooked.Query Patient property. It is not present on the corresponding redox model and was never used.
20
+
21
+ ## [0.4.0] - 2020-09-04
22
+ ### Added
23
+ - add email and phone to demographics
24
+ - add patient contacts
25
+
26
+ ## [0.3.0] - 2020-08-27
27
+ ### Added
28
+ - _body attribute containing raw response text on query results
29
+ - SchedulingBooked.Query
30
+
31
+ ## [0.2.0] - 2020-08-16
32
+ ### Added
33
+ - improve release process
34
+
35
+ ### Changed
36
+ - fixed CHANGELOG url in gemspec
37
+
38
+ ## [0.1.0] - 2020-08-14
39
+ ### Added
40
+ - implement PatientSearch.Query
41
+
42
+ [0.1.0]: https://github.com/patient-discovery/redox-client/releases/tag/v0.1.0
43
+ [0.2.0]: https://github.com/patient-discovery/redox-client/releases/tag/v0.2.0
44
+ [0.3.0]: https://github.com/patient-discovery/redox-client/releases/tag/v0.3.0
45
+ [0.4.0]: https://github.com/patient-discovery/redox-client/releases/tag/v0.4.0
46
+ [0.5.0]: https://github.com/patient-discovery/redox-client/releases/tag/v0.5.0
47
+ [Unreleased]: https://github.com/patient-discovery/redox-client/compare/v0.5.0...HEAD
data/Gemfile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  source "https://rubygems.org"
2
4
 
3
5
  gemspec
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- redox-client (0.0.0.pre.0)
4
+ redox-client (0.5.0)
5
5
  faraday (~> 1.0, >= 1.0.1)
6
6
  hashie (~> 4.1)
7
7
 
@@ -10,6 +10,7 @@ GEM
10
10
  specs:
11
11
  ast (2.4.1)
12
12
  diff-lcs (1.4.4)
13
+ docile (1.3.2)
13
14
  faraday (1.0.1)
14
15
  multipart-post (>= 1.2, < 3)
15
16
  hashie (4.1.0)
@@ -25,6 +26,8 @@ GEM
25
26
  rspec-core (~> 3.9.0)
26
27
  rspec-expectations (~> 3.9.0)
27
28
  rspec-mocks (~> 3.9.0)
29
+ rspec-collection_matchers (1.2.0)
30
+ rspec-expectations (>= 2.99.0.beta1)
28
31
  rspec-core (3.9.2)
29
32
  rspec-support (~> 3.9.3)
30
33
  rspec-expectations (3.9.2)
@@ -48,10 +51,15 @@ GEM
48
51
  rubocop-performance (1.6.1)
49
52
  rubocop (>= 0.71.0)
50
53
  ruby-progressbar (1.10.1)
54
+ simplecov (0.18.5)
55
+ docile (~> 1.1)
56
+ simplecov-html (~> 0.11)
57
+ simplecov-html (0.12.2)
51
58
  standard (0.4.7)
52
59
  rubocop (~> 0.85.0)
53
60
  rubocop-performance (~> 1.6.0)
54
61
  unicode-display_width (1.7.0)
62
+ vcr (6.0.0)
55
63
 
56
64
  PLATFORMS
57
65
  ruby
@@ -60,7 +68,10 @@ DEPENDENCIES
60
68
  rake (~> 12.0)
61
69
  redox-client!
62
70
  rspec (~> 3.9)
71
+ rspec-collection_matchers (~> 1.2)
72
+ simplecov (~> 0.18.5)
63
73
  standard (~> 0.4.7)
74
+ vcr (~> 6.0)
64
75
 
65
76
  BUNDLED WITH
66
77
  2.1.4
data/README.md CHANGED
@@ -1,15 +1,139 @@
1
- # Redox::Client
1
+ [![Gem Version](https://img.shields.io/gem/v/redox-client.svg)](https://badge.fury.io/rb/redox-client)
2
+ [![Ruby Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://github.com/testdouble/standard)
3
+ ![Test](https://github.com/patient-discovery/redox-client/workflows/Test/badge.svg)
2
4
 
3
- Ruby gem that makes it easy to consume Redox JSON APIs.
5
+ # redox-client - Ruby gem facade for Redox APIs
6
+
7
+ This gem makes it easy to consume [Redox APIs](https://developer.redoxengine.com/).
4
8
 
5
9
  *Note: This is pre-release software under active development and should be considered unstable until version 1.0.0*
6
10
 
11
+ ## Features
12
+ - supports creation of multiple Redox Sources, each with its own API key and secret
13
+ - automatically requests Redox access tokens when needed in a thread safe way
14
+ - provides ruby style [DTOs](https://en.wikipedia.org/wiki/Data_transfer_object) to conveniently consume and generate Redox camel cased JSON
15
+
7
16
  ## Installation
8
17
 
18
+ Add the following to your `Gemfile`:
19
+
20
+ ```ruby
21
+ gem "redox-client"
22
+ ```
23
+
24
+ and run:
25
+
26
+ ```bash
27
+ bundle install
28
+ ```
29
+
9
30
  ## Usage
31
+ To uses redox-client effectively you will want to be familiar with the [Redox APIs](https://developer.redoxengine.com/) and in particular how sources, destinations, and subscriptions work.
32
+
33
+ Create a Redox Source.
34
+
35
+ ```ruby
36
+ source = Redox::Source.new(
37
+ endpoint: ENV["REDOX_ENDPOINT"],
38
+ api_key: ENV["REDOX_API_KEY"],
39
+ secret: ENV["REDOX_SECRET"]
40
+ )
41
+ ```
42
+
43
+ Build query object for API you wish to execute:
44
+
45
+ ```ruby
46
+ query = Redox::PatientSearch::Query.new(
47
+ patient: Redox::Models::Patient.new(
48
+ demographics: Redox::Models::Demographics.new(
49
+ first_name: "Timothy",
50
+ middle_name: "Paul",
51
+ ...
52
+ )
53
+ )
54
+ )
55
+ ```
56
+
57
+ Perform the query using your source and the appropriate destination id:
58
+
59
+ ```ruby
60
+ result = query.perform source, "my-destination-id"
61
+ ```
62
+
63
+ The result object is a DTO containing the Redox response:
64
+
65
+ ```ruby
66
+ result.patient.identifiers.first.id_type # => "MR"
67
+ result.patient.identifiers.first.id # => "0000000001"
68
+ ```
69
+
70
+ See the `specs/` for more examples.
71
+
72
+ ### Authentication and Lifecycle
73
+ `Redox::Source` requests a Redox access token using the Redox API key and secret. The access token is used until it is near expiration, which is typically 1 day after being issued. So a `Redox::Source` object is intended to be created once and reused.
74
+
75
+ Since `Redox::Source` has shared state, i.e., the access token and its expiration time, `Redox::Source` uses a [Monitor](https://ruby-doc.org/stdlib-2.6.3/libdoc/monitor/rdoc/MonitorMixin.html) to be thread safe.
76
+
77
+
78
+ ## Supported APIs
79
+
80
+ - PatientSearch.Query
81
+ - Scheduling.Booked
82
+ - Media.New
83
+ - File Upload
10
84
 
11
85
  ## Development
86
+ ***Nota Bene**: This project uses [VCR](https://relishapp.com/vcr/vcr/docs) to record HTTP requests and responses and play them back during tests. Do NOT use Redox production credentials when developing tests.*
87
+
88
+ ### Initial Setup
89
+ After checking out the repo, run `bin/setup` to install gem dependencies and a git pre-commit hook. The pre-commit hook checks test fixtures for Redox credential exposure. While recommended the hook is not required and can be removed or replaced if desired. After setup completes run `rake` to run all the tests.
90
+
91
+ ### Testing
92
+ This project uses `rspec` and [VCR](https://relishapp.com/vcr/vcr/docs). VCR provides fast deterministic testing of HTTP APIs. It also makes it possible to set up any server response you want to test by authoring the server responses directly. This comes in handy when trying to test edge cases that might occur but are hard to reproduce.
93
+
94
+ Some effort has been made to filter credentials from recorded HTTP interactions, but you should always carefully review all your changes before pushing them to avoid credential exposure.
95
+
96
+ ### Recording and Playing Back Cassettes
97
+ The `VCR_MODE` environment variable controls whether VCR is recording cassettes or playing them back. When recoding cassettes the following environment variables are used to call Redox APIs:
98
+
99
+ - `REDOX_ENDPOINT`: base URL of Redox API endpoint (e.g., https://api.redoxengine.com/)
100
+ - `REDOX_API_KEY`: Redox `apiKey` used by /auth/authenticate
101
+ - `REDOX_SECRET`: Redox `secret` used by /auth/authenticate
102
+
103
+ Set these to the credentials for the Redox environment you are using for testing.
104
+
105
+ When playing back cassettes these environment variables are ignored and no API requests are made to Redox.
106
+
107
+ To make a new test with a new recording, set environment variables above then:
108
+
109
+ ```bash
110
+ env VCR_MODE=record rspec spec/my_new_spec.rb
111
+ ```
112
+
113
+ This will record all API requests made by the test. Be **careful**: if you run all the tests with `VCR_MODE=record` it will re-record all the cassettes.
114
+
115
+ The default mode is playback, so to playback cassettes just run `rspec`.
116
+
117
+ ### Coding Style
118
+ This project adheres to [StandardRB](https://github.com/testdouble/standard/blob/master/README.md). Additionally
119
+ [# frozen_string_literal](https://bugs.ruby-lang.org/issues/8976#note-30) is required in Ruby source files, and is enforced by Rubocop.
120
+
121
+ Run `rake` to run the style checks. Run `rake fix` to fix violations.
122
+
123
+ ### Useful commands
124
+ - `rake` - run all tests (lint, Redox cred scan, rspec)
125
+ - `rake fix` - Fix RuboCop and StandardRB violations
126
+ - `rake vcr:fix` - attempt to replace real looking credentials in VCR cassettes with dummy test values.
127
+ - `rake -T` - see available rake tasks
128
+ - `bin/console` - get an interactive prompt for experimenting
129
+
130
+ ### Release Process
131
+ This project uses [Semantic Versioning](https://semver.org)
132
+
133
+ Prepare release on master branch, then run:
12
134
 
13
- ## Contributing
135
+ ```
136
+ rake prepare:release
137
+ ```
14
138
 
15
- ## License
139
+ and follow the instructions.
data/Rakefile CHANGED
@@ -1,6 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "date"
1
4
  require "bundler/gem_tasks"
2
5
  require "rspec/core/rake_task"
6
+ require "standard/rake"
7
+ require "rubocop/rake_task"
8
+
9
+ RSpec::Core::RakeTask.new :spec
10
+ RuboCop::RakeTask.new :rubocop do |task|
11
+ task.options = %w[--format quiet]
12
+ end
13
+
14
+ desc "Run all checks and tests"
15
+ task default: :test
16
+
17
+ desc "Run all checks and tests"
18
+ task test: [:rubocop, :standard, :vcr, :spec]
19
+
20
+ desc "Fix RuboCop and StandardRB violations"
21
+ task fix: ["rubocop:auto_correct", "standard:fix"]
22
+
23
+ # TODO: Make these a proper lib/rake_tasks.rb task and avoid exec'ing out
24
+ desc "Check VCR cassettes for Redox credential leak"
25
+ task "vcr" do
26
+ raise "Possible credential leak, run rake vcr:fix" unless system "bin/check-vcr-cassettes"
27
+ end
28
+
29
+ desc "Scrub VCR cassettes, replacing suspected real creds with test ones"
30
+ task "vcr:fix" do
31
+ system "bin/check-vcr-cassettes --scrub"
32
+ end
33
+
34
+ task "prepare:changelog" do
35
+ repo = "https://github.com/patient-discovery/redox-client"
36
+ changelog = IO.readlines("CHANGELOG.md").map(&:chomp)
37
+ entries = [
38
+ "## [#{Redox::VERSION}] - #{Date.today}",
39
+ "[#{Redox::VERSION}]: #{repo}/releases/tag/v#{Redox::VERSION}",
40
+ "[Unreleased]: #{repo}/compare/v#{Redox::VERSION}...HEAD"
41
+ ]
42
+ unless entries.all? { |x| changelog.include? x }
43
+ puts "\nAdd these to CHANGELOG.md:\n\n"
44
+ puts entries.join("\n")
45
+ puts "\n\n"
46
+ abort "ERROR: CHANGELOG version entries needed"
47
+ end
48
+ puts "CHANGELOG looks good"
49
+ end
50
+
51
+ task "prepare:version" do
52
+ if system("git show-ref --tags v#{Redox::VERSION}")
53
+ abort("ERROR: Tag already exists. Check version.rb")
54
+ end
55
+ end
56
+
57
+ task "prepare:tag" do
58
+ unless system("git tag -m 'Version #{Redox::VERSION}' v#{Redox::VERSION}")
59
+ abort("ERROR: Unable to tag")
60
+ end
61
+ end
3
62
 
4
- RSpec::Core::RakeTask.new(:spec)
63
+ desc "Prepare a gem release"
64
+ task "prepare:release": [
65
+ "release:guard_clean", "test", "prepare:version", "prepare:changelog", "prepare:tag"
66
+ ] do
67
+ puts "Release tagged. To create release run:\n\n"
68
+ puts "git push --follow-tags"
69
+ end
5
70
 
6
- task :default => :spec
71
+ CLEAN.include "coverage"
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "redox/version"
4
+
5
+ require_relative "redox/model"
6
+ require_relative "redox/models/address"
7
+ require_relative "redox/models/contact"
8
+ require_relative "redox/models/demographics"
9
+ require_relative "redox/models/destination"
10
+ require_relative "redox/models/error"
11
+ require_relative "redox/models/location"
12
+ require_relative "redox/models/media"
13
+ require_relative "redox/models/message"
14
+ require_relative "redox/models/meta"
15
+ require_relative "redox/models/patient_identifier"
16
+ require_relative "redox/models/patient"
17
+ require_relative "redox/models/phone_number"
18
+ require_relative "redox/models/provider"
19
+ require_relative "redox/models/visit"
20
+
21
+ require_relative "redox/source"
22
+ require_relative "redox/query"
23
+ require_relative "redox/file_upload"
24
+
25
+ require_relative "redox/patient_search/query.rb"
26
+ require_relative "redox/scheduling/booked.rb"
27
+ require_relative "redox/media/new.rb"
28
+
29
+ module Redox
30
+ class Error < StandardError
31
+ attr_accessor :status
32
+ attr_accessor :body
33
+ attr_accessor :message
34
+
35
+ def initialize(status: nil, body: nil, message: nil)
36
+ self.status = status
37
+ self.body = body
38
+ self.message = message
39
+ end
40
+ end
41
+
42
+ class AuthenticationError < Error; end
43
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Redox
4
+ class FileUpload
5
+ ENDPOINT = "https://blob.redoxengine.com/upload"
6
+
7
+ # Create a multipart file upload.
8
+ #
9
+ # @param filename_or_io [String, IO] Either a String filename of a local file
10
+ # or an open IO object
11
+ # @param content_type [String] String content type of the file data
12
+ # @param upload_as_filename [String] (optional) Name to use for uploaded file
13
+ def initialize(filename_or_io, content_type, upload_as_filename = nil)
14
+ @file_part = Faraday::FilePart.new(filename_or_io, content_type, upload_as_filename)
15
+ end
16
+
17
+ # Upload the file.
18
+ #
19
+ # @param [Redox::Source] source to use for authentication
20
+ def perform(source)
21
+ source.ensure_access_token
22
+
23
+ connection = Faraday.new { |f|
24
+ f.authorization :Bearer, source.access_token
25
+ f.request :multipart
26
+ f.headers[:accept] = "application/json"
27
+ }
28
+ res = connection.post ENDPOINT, {file: @file_part}
29
+
30
+ raise Redox::Error.new(status: res.status, body: res.body) unless res.success?
31
+
32
+ uri =
33
+ begin
34
+ JSON.parse(res.body)["URI"]
35
+ rescue
36
+ end
37
+ raise Redox::Error.new(status: res.status, body: res.body) unless uri
38
+
39
+ uri
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "redox"
4
+
5
+ module Redox
6
+ module Media
7
+ class New < Redox::Query
8
+ redox_property :Patient, coerce: Models::Patient
9
+ redox_property :Visit, coerce: Models::Visit
10
+ redox_property :Media, coerce: Models::Media
11
+
12
+ def response_type
13
+ NewResponse
14
+ end
15
+ end
16
+
17
+ class NewResponse < Models::Message
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "hashie"
4
+ require "json"
5
+
6
+ module Redox::Models
7
+ end
8
+ class Redox::Model < Hashie::Trash
9
+ include Hashie::Extensions::Dash::Coercion
10
+ include Hashie::Extensions::IgnoreUndeclared
11
+
12
+ def initialize(hash = {})
13
+ if hash.keys.first.is_a? String
14
+ hash = hash.transform_keys { |key|
15
+ translated_key = self.class.translations[key.to_sym]
16
+ translated_key.nil? ? key : translated_key
17
+ }
18
+ end
19
+ super hash
20
+ end
21
+
22
+ def self.redox_property(redox_property, options = {})
23
+ property to_snake_case(redox_property).to_sym, options.merge({from: redox_property})
24
+ end
25
+
26
+ def self.from_redox_json(json)
27
+ case json
28
+ when String
29
+ json = JSON.parse json
30
+ end
31
+ new json.transform_keys(&:to_sym)
32
+ end
33
+
34
+ def to_redox_hash
35
+ to_h
36
+ .transform_keys { |k| self.class.inverse_translations[k] }
37
+ .transform_values { |v| value_to_redox_hash(v) }
38
+ end
39
+
40
+ def value_to_redox_hash(value)
41
+ if value.respond_to?(:to_redox_hash)
42
+ value.to_redox_hash
43
+ elsif value.is_a?(Array)
44
+ value.map { |element| value_to_redox_hash(element) }
45
+ else
46
+ value
47
+ end
48
+ end
49
+
50
+ def to_redox_json
51
+ to_redox_hash.to_json
52
+ end
53
+
54
+ # Convert word from CamelCase to snake_case. This is roughly the same as the
55
+ # rails `String.underscore` method with the following simplications:
56
+ #
57
+ # - any word in all caps is assumed to be an acronym (ZIP -> zip)
58
+ # - only alphabetic characters are modified
59
+ #
60
+ # Note: this function does not have an inverse. Both "Zip" and "ZIP"
61
+ # map to "zip".
62
+ #
63
+ # @param [String] camel_cased_word word to convert to snake case
64
+ # @return [String] camel_cased_qord converted to snake case
65
+ def self.to_snake_case(camel_cased_word)
66
+ word = camel_cased_word.to_s
67
+ return word.downcase if word.upcase == word
68
+
69
+ word.gsub!(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2')
70
+ word.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
71
+ word.downcase!
72
+ word
73
+ end
74
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Redox
4
+ module Models
5
+ class Address < Redox::Model
6
+ redox_property :StreetAddress
7
+ redox_property :City
8
+ redox_property :State
9
+ redox_property :ZIP
10
+ redox_property :County
11
+ redox_property :Country
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "address"
4
+ require_relative "phone_number"
5
+
6
+ module Redox
7
+ module Models
8
+ class Contact < Redox::Model
9
+ redox_property :FirstName
10
+ redox_property :MiddleName
11
+ redox_property :LastName
12
+ redox_property :Address, coerce: Address
13
+ redox_property :PhoneNumber, coerce: PhoneNumber
14
+ redox_property :EmailAddresses, coerce: Array[String]
15
+ redox_property :RelationToPatient
16
+ redox_property :Roles, coerce: Array[String]
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "address"
4
+ require_relative "phone_number"
5
+
6
+ module Redox
7
+ module Models
8
+ class Demographics < Redox::Model
9
+ redox_property :FirstName
10
+ redox_property :MiddleName
11
+ redox_property :LastName
12
+ redox_property :DOB
13
+ redox_property :SSN
14
+ redox_property :Sex
15
+ redox_property :Address, coerce: Address
16
+ redox_property :PhoneNumber, coerce: PhoneNumber
17
+ redox_property :EmailAddresses, coerce: Array[String]
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Redox
4
+ module Models
5
+ class Destination < Redox::Model
6
+ redox_property :ID
7
+ redox_property :Name
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Redox
4
+ module Models
5
+ class Error < Redox::Model
6
+ redox_property :ID
7
+ redox_property :Text
8
+ redox_property :Type
9
+ redox_property :Module
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Redox
4
+ module Models
5
+ class Location < Redox::Model
6
+ redox_property :Type
7
+ redox_property :Facility
8
+ redox_property :Department
9
+ redox_property :Room
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "provider"
4
+
5
+ module Redox
6
+ module Models
7
+ class Media < Redox::Model
8
+ redox_property :FileType
9
+ redox_property :FileName
10
+ redox_property :FileContents
11
+ redox_property :DocumentType
12
+ redox_property :DocumentID
13
+ redox_property :DocumentDescription
14
+ redox_property :CreationDateTime
15
+ redox_property :ServiceDateTime
16
+ redox_property :Provider, coerce: Provider
17
+ redox_property :Authenticated
18
+ redox_property :Authenticator, coerce: Provider
19
+ redox_property :Availability
20
+ redox_property :Notifications, coerce: Array[Provider]
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "meta"
4
+
5
+ module Redox
6
+ module Models
7
+ class Message < Redox::Model
8
+ attr_accessor :_body
9
+ redox_property :Meta, coerce: Redox::Models::Meta
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "destination"
4
+
5
+ module Redox
6
+ module Models
7
+ # Redox::Message is already taken: it refers to an entire response
8
+ class MetaMessage < Redox::Model
9
+ redox_property :ID
10
+ end
11
+ class Meta < Redox::Model
12
+ redox_property :DataModel
13
+ redox_property :EventType
14
+ redox_property :EventDateTime
15
+ redox_property :Test
16
+ redox_property :Destinations, coerce: Array[Redox::Models::Destination]
17
+ redox_property :Message, coerce: MetaMessage
18
+ redox_property :Errors, coerce: Array[Redox::Models::Error]
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "patient_identifier"
4
+ require_relative "demographics"
5
+ require_relative "contact"
6
+
7
+ module Redox
8
+ module Models
9
+ class Patient < Redox::Model
10
+ redox_property :Identifiers, coerce: Array[PatientIdentifier]
11
+ redox_property :Demographics, coerce: Demographics
12
+ redox_property :Contacts, coerce: Array[Contact]
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Redox
4
+ module Models
5
+ class PatientIdentifier < Redox::Model
6
+ redox_property :IDType
7
+ redox_property :ID
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Redox
4
+ module Models
5
+ class PhoneNumber < Redox::Model
6
+ redox_property :Home
7
+ redox_property :Office
8
+ redox_property :Mobile
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "address"
4
+ require_relative "location"
5
+ require_relative "phone_number"
6
+
7
+ module Redox
8
+ module Models
9
+ class Provider < Redox::Model
10
+ redox_property :ID
11
+ redox_property :IDType
12
+ redox_property :FirstName
13
+ redox_property :LastName
14
+ redox_property :Credentials, coerce: Array[String]
15
+ redox_property :Address, coerce: Address
16
+ redox_property :EmailAddresses, coerce: Array[String]
17
+ redox_property :PhoneNumber, coerce: PhoneNumber
18
+ redox_property :Location, coerce: Location
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "location"
4
+ require_relative "patient"
5
+ require_relative "provider"
6
+
7
+ module Redox
8
+ module Models
9
+ class Visit < Redox::Model
10
+ redox_property :VisitNumber
11
+ redox_property :AccountNumber
12
+ redox_property :VisitDateTime
13
+ redox_property :PatientClass
14
+ redox_property :Status
15
+ redox_property :Duration
16
+ redox_property :Reason
17
+ redox_property :CancelReason
18
+ redox_property :LastUpdated
19
+ redox_property :ScheduledDateTime
20
+ redox_property :CancelDateTime
21
+ redox_property :Type
22
+ redox_property :Instructions
23
+ redox_property :Patient, coerce: Patient
24
+ redox_property :AttendingProvider, coerce: Provider
25
+ redox_property :ConsultingProvider, coerce: Provider
26
+ redox_property :ReferringProvider, coerce: Provider
27
+ redox_property :VisitProvider, coerce: Provider
28
+ redox_property :Location, coerce: Location
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "redox"
4
+
5
+ module Redox
6
+ module PatientSearch
7
+ class Query < Redox::Query
8
+ redox_property :Patient, coerce: Models::Patient
9
+
10
+ def response_type
11
+ Response
12
+ end
13
+ end
14
+
15
+ class Response < Models::Message
16
+ redox_property :Patient, coerce: Models::Patient
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "date"
4
+
5
+ module Redox
6
+ class Query < Models::Message
7
+ def perform(source, destination_id)
8
+ data_model, event_type = self.class.name.split("::").drop(1)
9
+ self.meta = {
10
+ data_model: data_model,
11
+ event_type: event_type,
12
+ event_date_time: DateTime.now.iso8601,
13
+ destinations: [Redox::Models::Destination.new(id: destination_id)]
14
+ }
15
+ result_body = source.execute_query self
16
+ response = response_type.from_redox_json result_body
17
+ response._body = result_body
18
+ response
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "redox"
4
+
5
+ module Redox
6
+ module Scheduling
7
+ class Booked < Redox::Query
8
+ redox_property :StartDateTime, coerce: ->(v) {
9
+ v.respond_to?(:iso8601) ? v.iso8601 : v.to_s
10
+ }
11
+ redox_property :EndDateTime, coerce: ->(v) {
12
+ v.respond_to?(:iso8601) ? v.iso8601 : v.to_s
13
+ }
14
+
15
+ def response_type
16
+ BookedResponse
17
+ end
18
+ end
19
+
20
+ class BookedResponse < Models::Message
21
+ redox_property :Visits, coerce: Array[Models::Visit]
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "date"
4
+ require "json"
5
+ require "faraday"
6
+
7
+ class Redox::Source
8
+ include MonitorMixin
9
+
10
+ SECONDS_PER_DAY = 60 * 60 * 24
11
+ ACCESS_TOKEN_EXPIRATION_BUFFER = Rational(300, SECONDS_PER_DAY)
12
+
13
+ attr_reader :access_token_expires_at
14
+
15
+ def initialize(endpoint:, api_key:, secret:, test_mode: true)
16
+ super()
17
+ @connection = Faraday.new(
18
+ url: endpoint,
19
+ headers: {
20
+ accept: "application/json",
21
+ content_type: "application/json"
22
+ }
23
+ )
24
+ @api_key = api_key
25
+ @secret = secret
26
+ @test_mode = test_mode
27
+ end
28
+
29
+ def execute_query(model)
30
+ ensure_access_token
31
+ res = @connection.post("/endpoint", model.to_redox_json)
32
+ message =
33
+ begin
34
+ JSON.parse(res.body)
35
+ rescue
36
+ nil
37
+ end
38
+
39
+ unless res.success?
40
+ raise Redox::Error.new(
41
+ status: res.status,
42
+ body: res.body,
43
+ message: Redox::Models::Message.from_redox_json(message)
44
+ )
45
+ end
46
+ message
47
+ end
48
+
49
+ def ensure_access_token
50
+ synchronize {
51
+ authenticate if access_token.nil? || token_expiring_soon?
52
+ }
53
+ end
54
+
55
+ def access_token
56
+ @connection.headers["Authorization"]&.delete_prefix "Bearer "
57
+ end
58
+
59
+ def access_token=(token)
60
+ if token.nil?
61
+ @connection.headers.delete "Authorization"
62
+ else
63
+ @connection.authorization :Bearer, token
64
+ end
65
+ end
66
+
67
+ def token_expiring_soon?
68
+ DateTime.now > @access_token_expires_at - ACCESS_TOKEN_EXPIRATION_BUFFER
69
+ end
70
+
71
+ private
72
+
73
+ def authenticate
74
+ self.access_token = nil
75
+ res = @connection.post("/auth/authenticate", {apiKey: @api_key, secret: @secret}.to_json)
76
+ raise Redox::AuthenticationError.new status: res.status, body: res.body unless res.success?
77
+ data = JSON.parse(res.body)
78
+ self.access_token = data["accessToken"]
79
+ @access_token_expires_at = DateTime.parse(data["expires"])
80
+ end
81
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Redox
4
+ VERSION = "0.5.0"
5
+ end
@@ -1,8 +1,10 @@
1
- require_relative "lib/redox/client/version"
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/redox/version"
2
4
 
3
5
  Gem::Specification.new do |gem|
4
6
  gem.name = "redox-client"
5
- gem.version = Redox::Client::VERSION
7
+ gem.version = Redox::VERSION
6
8
  gem.homepage = "https://github.com/patient-discovery/redox-client"
7
9
  gem.authors = ["Michael Keirnan"]
8
10
  gem.email = ["mike@patientdiscovery.com"]
@@ -10,17 +12,20 @@ Gem::Specification.new do |gem|
10
12
  gem.summary = "Ruby library for consuming Redox JSON APIs"
11
13
  gem.required_ruby_version = Gem::Requirement.new(">= 2.6.0")
12
14
  gem.metadata["source_code_uri"] = gem.homepage
13
- gem.metadata["changelog_uri"] = "#{gem.homepage}/CHANGELOG.md"
15
+ gem.metadata["changelog_uri"] = "#{gem.homepage}/blob/master/CHANGELOG.md"
14
16
 
15
17
  gem.license = "MIT"
16
18
  gem.files = Dir.chdir(File.expand_path(__dir__)) do
17
- `git ls-files -z`.split("\x0").reject { |f| f.match(%r{(^spec|^\.)}) }
19
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{(^spec|^bin|^\.)}) }
18
20
  end
19
21
  gem.require_paths = ["lib"]
20
22
 
21
23
  gem.add_development_dependency "rake", "~> 12.0"
22
24
  gem.add_development_dependency "rspec", "~> 3.9"
25
+ gem.add_development_dependency "rspec-collection_matchers", "~> 1.2"
26
+ gem.add_development_dependency "simplecov", "~> 0.18.5"
23
27
  gem.add_development_dependency "standard", "~> 0.4.7"
28
+ gem.add_development_dependency "vcr", "~> 6.0"
24
29
 
25
30
  gem.add_runtime_dependency "faraday", "~> 1.0", ">= 1.0.1"
26
31
  gem.add_runtime_dependency "hashie", "~> 4.1"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: redox-client
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.0.pre.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Keirnan
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-08-06 00:00:00.000000000 Z
11
+ date: 2020-10-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -38,6 +38,34 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '3.9'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec-collection_matchers
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.2'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.2'
55
+ - !ruby/object:Gem::Dependency
56
+ name: simplecov
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 0.18.5
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 0.18.5
41
69
  - !ruby/object:Gem::Dependency
42
70
  name: standard
43
71
  requirement: !ruby/object:Gem::Requirement
@@ -52,6 +80,20 @@ dependencies:
52
80
  - - "~>"
53
81
  - !ruby/object:Gem::Version
54
82
  version: 0.4.7
83
+ - !ruby/object:Gem::Dependency
84
+ name: vcr
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '6.0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '6.0'
55
97
  - !ruby/object:Gem::Dependency
56
98
  name: faraday
57
99
  requirement: !ruby/object:Gem::Requirement
@@ -93,20 +135,42 @@ executables: []
93
135
  extensions: []
94
136
  extra_rdoc_files: []
95
137
  files:
138
+ - CHANGELOG.md
96
139
  - Gemfile
97
140
  - Gemfile.lock
98
141
  - LICENSE
99
142
  - README.md
100
143
  - Rakefile
101
- - lib/redox/client.rb
102
- - lib/redox/client/version.rb
144
+ - lib/redox.rb
145
+ - lib/redox/file_upload.rb
146
+ - lib/redox/media/new.rb
147
+ - lib/redox/model.rb
148
+ - lib/redox/models/address.rb
149
+ - lib/redox/models/contact.rb
150
+ - lib/redox/models/demographics.rb
151
+ - lib/redox/models/destination.rb
152
+ - lib/redox/models/error.rb
153
+ - lib/redox/models/location.rb
154
+ - lib/redox/models/media.rb
155
+ - lib/redox/models/message.rb
156
+ - lib/redox/models/meta.rb
157
+ - lib/redox/models/patient.rb
158
+ - lib/redox/models/patient_identifier.rb
159
+ - lib/redox/models/phone_number.rb
160
+ - lib/redox/models/provider.rb
161
+ - lib/redox/models/visit.rb
162
+ - lib/redox/patient_search/query.rb
163
+ - lib/redox/query.rb
164
+ - lib/redox/scheduling/booked.rb
165
+ - lib/redox/source.rb
166
+ - lib/redox/version.rb
103
167
  - redox-client.gemspec
104
168
  homepage: https://github.com/patient-discovery/redox-client
105
169
  licenses:
106
170
  - MIT
107
171
  metadata:
108
172
  source_code_uri: https://github.com/patient-discovery/redox-client
109
- changelog_uri: https://github.com/patient-discovery/redox-client/CHANGELOG.md
173
+ changelog_uri: https://github.com/patient-discovery/redox-client/blob/master/CHANGELOG.md
110
174
  post_install_message:
111
175
  rdoc_options: []
112
176
  require_paths:
@@ -118,9 +182,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
118
182
  version: 2.6.0
119
183
  required_rubygems_version: !ruby/object:Gem::Requirement
120
184
  requirements:
121
- - - ">"
185
+ - - ">="
122
186
  - !ruby/object:Gem::Version
123
- version: 1.3.1
187
+ version: '0'
124
188
  requirements: []
125
189
  rubygems_version: 3.0.3
126
190
  signing_key:
@@ -1,8 +0,0 @@
1
- require "redox/client/version"
2
-
3
- module Redox
4
- module Client
5
- class Error < StandardError; end
6
- # Your code goes here...
7
- end
8
- end
@@ -1,5 +0,0 @@
1
- module Redox
2
- module Client
3
- VERSION = '0.0.0-0'
4
- end
5
- end