lifen-fhir 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|