lifen-fhir 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +4 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +74 -0
- data/License.txt +22 -0
- data/README.md +131 -0
- data/lib/lifen.rb +29 -0
- data/lib/lifen/app_authenticated_client.rb +37 -0
- data/lib/lifen/attachment.rb +26 -0
- data/lib/lifen/base.rb +7 -0
- data/lib/lifen/binary.rb +31 -0
- data/lib/lifen/category.rb +26 -0
- data/lib/lifen/channel.rb +31 -0
- data/lib/lifen/client.rb +107 -0
- data/lib/lifen/communication_request.rb +67 -0
- data/lib/lifen/configuration.rb +22 -0
- data/lib/lifen/content_string.rb +15 -0
- data/lib/lifen/error.rb +27 -0
- data/lib/lifen/medium.rb +17 -0
- data/lib/lifen/patient.rb +27 -0
- data/lib/lifen/practitioner.rb +97 -0
- data/lib/lifen/user_authenticated_client.rb +39 -0
- data/lib/lifen/version.rb +3 -0
- data/lifen-fhir.gemspec +30 -0
- data/spec/binary_spec.rb +29 -0
- data/spec/cassettes/binary/download/invalid.yml +65 -0
- data/spec/cassettes/binary/download/valid.yml +1320 -0
- data/spec/cassettes/communication_request/send/invalid_medium.yml +69 -0
- data/spec/cassettes/communication_request/send/valid_attributes.yml +75 -0
- data/spec/cassettes/communication_request/send/valid_attributes_binary.yml +74 -0
- data/spec/cassettes/practitionner/create_channel/address/old_valid_attributes.yml +71 -0
- data/spec/cassettes/practitionner/create_channel/address/valid_attributes.yml +71 -0
- data/spec/cassettes/practitionner/create_channel/telecom/valid_attributes.yml +70 -0
- data/spec/cassettes/practitionner/find_by_rpps/existing_rpps.yml +125 -0
- data/spec/cassettes/practitionner/find_by_rpps/missing_line_attribute.yml +122 -0
- data/spec/cassettes/practitionner/find_by_rpps/wrong_rpps.yml +68 -0
- data/spec/category_spec.rb +21 -0
- data/spec/communication_request_spec.rb +64 -0
- data/spec/practitionner_spec.rb +76 -0
- data/spec/spec_helper.rb +24 -0
- data/spec/support/master_plan.pdf +0 -0
- metadata +230 -0
data/lib/lifen/client.rb
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
module Lifen
|
2
|
+
class Client
|
3
|
+
|
4
|
+
def request(mode, url, params = {})
|
5
|
+
before_request
|
6
|
+
|
7
|
+
response = faraday_client.send(mode) do |req|
|
8
|
+
req.url url
|
9
|
+
|
10
|
+
req.headers['Authorization'] = "Bearer #{bearer}"
|
11
|
+
req.headers['Accept'] = use_and_remove_accept(params)
|
12
|
+
|
13
|
+
if mode == :post
|
14
|
+
req.headers['Content-Type'] = "application/json"
|
15
|
+
end
|
16
|
+
|
17
|
+
req.body = JSON.generate(params)
|
18
|
+
end
|
19
|
+
|
20
|
+
handle_errors(response, params)
|
21
|
+
|
22
|
+
handle_response(response)
|
23
|
+
end
|
24
|
+
|
25
|
+
def post(url, params = {})
|
26
|
+
request(:post, url, params)
|
27
|
+
end
|
28
|
+
|
29
|
+
def put(url, params = {})
|
30
|
+
request(:put, url, params)
|
31
|
+
end
|
32
|
+
|
33
|
+
def get(url, params = {})
|
34
|
+
request(:get, url, params)
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def handle_errors(response, params)
|
40
|
+
if response.status == 500
|
41
|
+
|
42
|
+
json = JSON.parse response.body
|
43
|
+
|
44
|
+
trace_id = json.fetch("X-B3-TraceId", "unknown")
|
45
|
+
|
46
|
+
raise Error, "Error 500, Internal server error (trace ID: #{trace_id}), #{response_error(response, params)}"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def handle_response(response)
|
51
|
+
if response.headers['Content-Type'].match "json"
|
52
|
+
JSON.parse response.body
|
53
|
+
else
|
54
|
+
response.body
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def faraday_client
|
59
|
+
@faraday_client ||= Faraday.new(faraday_options) do |faraday|
|
60
|
+
faraday.adapter Faraday.default_adapter # make requests with Net::HTTP
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def faraday_options
|
65
|
+
options = {url: site}
|
66
|
+
|
67
|
+
options[:proxy] = proxy_url if !proxy_url.nil?
|
68
|
+
|
69
|
+
options
|
70
|
+
end
|
71
|
+
|
72
|
+
def site
|
73
|
+
Lifen.configuration.site
|
74
|
+
end
|
75
|
+
|
76
|
+
def proxy_url
|
77
|
+
Lifen.configuration.proxy_url
|
78
|
+
end
|
79
|
+
|
80
|
+
def before_request
|
81
|
+
end
|
82
|
+
|
83
|
+
def response_error(response, params)
|
84
|
+
params[:payload] = "filtered" if params.is_a?(Hash) and params.has_key? :payload
|
85
|
+
|
86
|
+
"#{response.env.method.upcase} '#{response.env.url}' with params '#{params.inspect}' and bearer '#{trucanted_bearer}'"
|
87
|
+
end
|
88
|
+
|
89
|
+
def trucanted_bearer
|
90
|
+
if m = /^(.{24})(.*)$/.match(bearer)
|
91
|
+
"#{m[1]}#{"*" * m[2].length}"
|
92
|
+
else
|
93
|
+
bearer
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def bearer
|
98
|
+
raise "A bearer method must be defined"
|
99
|
+
end
|
100
|
+
|
101
|
+
def use_and_remove_accept(params)
|
102
|
+
return params.delete :accept if params.is_a?(Hash) and params.has_key? :accept
|
103
|
+
|
104
|
+
"application/json"
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module Lifen
|
2
|
+
class CommunicationRequest < Base
|
3
|
+
|
4
|
+
attribute :uuid, String
|
5
|
+
attribute :number_communications, Integer
|
6
|
+
|
7
|
+
attribute :sender, Lifen::Practitioner
|
8
|
+
attribute :recipients, [Lifen::Practitioner]
|
9
|
+
|
10
|
+
attribute :category, Lifen::Category, default: Lifen::Category.new
|
11
|
+
attribute :medium, [Lifen::Medium]
|
12
|
+
attribute :patient, Lifen::Patient
|
13
|
+
attribute :attachment, Lifen::Attachment
|
14
|
+
attribute :binary, Lifen::Binary
|
15
|
+
attribute :content_string, Lifen::ContentString
|
16
|
+
|
17
|
+
def send
|
18
|
+
json = application_client.post("fhir/CommunicationRequest", fhir_payload)
|
19
|
+
|
20
|
+
self.uuid = json["id"]
|
21
|
+
self.number_communications = Array(json["issue"]).length
|
22
|
+
|
23
|
+
self
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def fhir_payload
|
29
|
+
|
30
|
+
payload = {
|
31
|
+
resourceType: "CommunicationRequest",
|
32
|
+
sender: [ sender.fhir_payload ],
|
33
|
+
recipient: recipients.map(&:fhir_payload),
|
34
|
+
contained: [],
|
35
|
+
medium: medium.map(&:fhir_payload) ,
|
36
|
+
category: [ category.fhir_payload],
|
37
|
+
payload: [ document_content ]
|
38
|
+
}
|
39
|
+
|
40
|
+
if patient
|
41
|
+
payload[:contained] << patient.fhir_payload
|
42
|
+
payload[:subject] = [
|
43
|
+
{reference: "patient"}
|
44
|
+
]
|
45
|
+
end
|
46
|
+
|
47
|
+
if content_string
|
48
|
+
payload[:payload] << content_string.fhir_payload
|
49
|
+
end
|
50
|
+
|
51
|
+
payload
|
52
|
+
end
|
53
|
+
|
54
|
+
def document_content
|
55
|
+
if !attachment.nil?
|
56
|
+
attachment.fhir_payload
|
57
|
+
else
|
58
|
+
binary.fhir_payload
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def application_client
|
63
|
+
@application_client ||= AppAuthenticatedClient.new
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Lifen
|
2
|
+
class Configuration
|
3
|
+
|
4
|
+
attr_accessor :site, :application_access_token, :proxy_url
|
5
|
+
|
6
|
+
def site=(url)
|
7
|
+
if !/(.*)\/$/.match(url)
|
8
|
+
raise Lifen::Error, "Invalid 'site' provided in configuration: '#{url}', a trailing slash is missing"
|
9
|
+
end
|
10
|
+
|
11
|
+
@site = url
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.configuration
|
16
|
+
@configuration ||= Configuration.new
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.configure
|
20
|
+
yield(configuration) if block_given?
|
21
|
+
end
|
22
|
+
end
|
data/lib/lifen/error.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
module Lifen
|
2
|
+
|
3
|
+
class Error < StandardError
|
4
|
+
end
|
5
|
+
|
6
|
+
class UnauthorizedError < Error
|
7
|
+
end
|
8
|
+
|
9
|
+
class InvalidCredentialsError < Error
|
10
|
+
end
|
11
|
+
|
12
|
+
class InvalidParamsError < Error
|
13
|
+
end
|
14
|
+
|
15
|
+
class InvalidSecretTokenError < Error
|
16
|
+
end
|
17
|
+
|
18
|
+
class InvalidTokenError < Error
|
19
|
+
end
|
20
|
+
|
21
|
+
class UserAlreadyExistingError < Error
|
22
|
+
end
|
23
|
+
|
24
|
+
class CantRefreshTokenError < Error
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
data/lib/lifen/medium.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
module Lifen
|
2
|
+
class Patient < Base
|
3
|
+
|
4
|
+
attribute :first_name, String
|
5
|
+
attribute :last_name, String
|
6
|
+
attribute :birthdate, Date
|
7
|
+
|
8
|
+
def fhir_payload
|
9
|
+
{
|
10
|
+
id: "patient",
|
11
|
+
resourceType: "Patient",
|
12
|
+
name: [
|
13
|
+
{
|
14
|
+
family: [
|
15
|
+
last_name
|
16
|
+
],
|
17
|
+
given: [
|
18
|
+
first_name
|
19
|
+
]
|
20
|
+
}
|
21
|
+
],
|
22
|
+
birthDate: birthdate.to_s
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
module Lifen
|
2
|
+
class Practitioner < Base
|
3
|
+
|
4
|
+
attribute :channels, [Lifen::Channel]
|
5
|
+
|
6
|
+
attribute :uuid, String
|
7
|
+
attribute :last_name, String
|
8
|
+
attribute :first_name, String
|
9
|
+
attribute :rpps, String
|
10
|
+
|
11
|
+
def fhir_payload
|
12
|
+
{ reference: "Practitioner/#{uuid}" }
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.find_by_rpps(rpps)
|
16
|
+
json = application_client.get("fhir/Practitioner/?identifier=#{rpps}")
|
17
|
+
|
18
|
+
raise "Practitioner not found" if Array(json["entry"]).size != 1
|
19
|
+
|
20
|
+
user_json = Array(json["entry"]).first.fetch("resource") { {} }
|
21
|
+
|
22
|
+
user_json[:uuid] = user_json["id"]
|
23
|
+
|
24
|
+
user = new(user_json)
|
25
|
+
|
26
|
+
Array(user_json["telecom"]).each do |telecom_json|
|
27
|
+
user.channels << Lifen::Channel.from_json(telecom_json, "telecom")
|
28
|
+
end
|
29
|
+
|
30
|
+
Array(user_json["address"]).each do |address_json|
|
31
|
+
user.channels << Lifen::Channel.from_json(address_json, "address")
|
32
|
+
end
|
33
|
+
|
34
|
+
user
|
35
|
+
end
|
36
|
+
|
37
|
+
def create_address(params)
|
38
|
+
filtered_params = {"resourceType" => "Practitioner"}
|
39
|
+
|
40
|
+
address = {
|
41
|
+
"line": Array(params[:lines]),
|
42
|
+
"city": params[:city],
|
43
|
+
"postalCode": params[:postal_code],
|
44
|
+
"country": params[:country]
|
45
|
+
}
|
46
|
+
|
47
|
+
filtered_params[params[:type]] = address
|
48
|
+
|
49
|
+
json = application_client.post("fhir/Practitioner/#{uuid}/$add-address", filtered_params)
|
50
|
+
|
51
|
+
channel = Channel.new(uuid: json["issue"][0]["id"], type: params[:type], value: "#{Array(params[:lines]).join(", ")}, #{params[:postal_code]} #{params[:city]}")
|
52
|
+
|
53
|
+
self.channels << channel
|
54
|
+
|
55
|
+
channel
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.from_json(json)
|
59
|
+
reference = json["reference"]
|
60
|
+
|
61
|
+
uuid = reference.gsub("Practitioner/", "")
|
62
|
+
|
63
|
+
new(uuid: uuid)
|
64
|
+
end
|
65
|
+
|
66
|
+
def create_telecom(params)
|
67
|
+
filtered_params = {"resourceType" => "Practitioner"}
|
68
|
+
|
69
|
+
telecom = {
|
70
|
+
"system": params[:system],
|
71
|
+
"value": params[:value]
|
72
|
+
}
|
73
|
+
|
74
|
+
filtered_params[params[:type]] = telecom
|
75
|
+
|
76
|
+
json = application_client.post("fhir/Practitioner/#{uuid}/$add-telecom", filtered_params)
|
77
|
+
|
78
|
+
channel = Channel.new(uuid: json["issue"][0]["id"], type: params[:type], value: params[:value])
|
79
|
+
|
80
|
+
self.channels << channel
|
81
|
+
|
82
|
+
channel
|
83
|
+
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
def application_client
|
89
|
+
@application_client ||= AppAuthenticatedClient.new
|
90
|
+
end
|
91
|
+
|
92
|
+
def self.application_client
|
93
|
+
@application_client ||= AppAuthenticatedClient.new
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Lifen
|
2
|
+
class UserAuthenticatedClient < Client
|
3
|
+
|
4
|
+
def initialize(token)
|
5
|
+
@token = token
|
6
|
+
end
|
7
|
+
|
8
|
+
attr_reader :token
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def handle_errors(response, params)
|
13
|
+
super(response, params)
|
14
|
+
|
15
|
+
case response.status
|
16
|
+
when 400
|
17
|
+
raise InvalidParamsError, "Error 400, Invalid params, #{response_error(response, params)}"
|
18
|
+
when 401
|
19
|
+
raise UnauthorizedError, "Error 401, Token is not valid, #{response_error(response, params)}"
|
20
|
+
when 403
|
21
|
+
raise Error, "Error 403, Action is forbidden, #{response_error(response, params)}"
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
def response_error(response, params)
|
27
|
+
"User Client, #{super(response, params)}"
|
28
|
+
end
|
29
|
+
|
30
|
+
def bearer
|
31
|
+
token.value
|
32
|
+
end
|
33
|
+
|
34
|
+
def before_request
|
35
|
+
token.refresh_once_if_needed
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
data/lifen-fhir.gemspec
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
lib = File.expand_path('../lib', __FILE__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
require 'lifen/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "lifen-fhir"
|
7
|
+
spec.version = Lifen::VERSION
|
8
|
+
spec.authors = ["Leonard Sellam"]
|
9
|
+
spec.email = ["leonard@lifen.fr"]
|
10
|
+
spec.homepage = "https://github.com/honestica/lifen-fhir"
|
11
|
+
spec.description = %q{Lifen FHIR API Ruby client}
|
12
|
+
spec.summary = %q{Lifen FHIR API Ruby client}
|
13
|
+
spec.license = "MIT"
|
14
|
+
|
15
|
+
spec.files = `git ls-files`.split($/)
|
16
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
17
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
18
|
+
spec.require_paths = ["lib"]
|
19
|
+
|
20
|
+
spec.add_development_dependency "bundler", '~> 1.12'
|
21
|
+
spec.add_development_dependency "rake", '~> 10.5'
|
22
|
+
spec.add_development_dependency "rspec", '~> 3.5'
|
23
|
+
spec.add_development_dependency "vcr", '~> 3.0'
|
24
|
+
spec.add_development_dependency "webmock", '~> 1.24'
|
25
|
+
spec.add_development_dependency "awesome_print"
|
26
|
+
|
27
|
+
spec.add_runtime_dependency "virtus", '>= 1.0'
|
28
|
+
spec.add_runtime_dependency "inflecto"
|
29
|
+
spec.add_runtime_dependency "faraday", '>= 0.9'
|
30
|
+
end
|