lifen-fhir 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.rspec +2 -0
  4. data/.ruby-version +1 -0
  5. data/CHANGELOG.md +4 -0
  6. data/Gemfile +3 -0
  7. data/Gemfile.lock +74 -0
  8. data/License.txt +22 -0
  9. data/README.md +131 -0
  10. data/lib/lifen.rb +29 -0
  11. data/lib/lifen/app_authenticated_client.rb +37 -0
  12. data/lib/lifen/attachment.rb +26 -0
  13. data/lib/lifen/base.rb +7 -0
  14. data/lib/lifen/binary.rb +31 -0
  15. data/lib/lifen/category.rb +26 -0
  16. data/lib/lifen/channel.rb +31 -0
  17. data/lib/lifen/client.rb +107 -0
  18. data/lib/lifen/communication_request.rb +67 -0
  19. data/lib/lifen/configuration.rb +22 -0
  20. data/lib/lifen/content_string.rb +15 -0
  21. data/lib/lifen/error.rb +27 -0
  22. data/lib/lifen/medium.rb +17 -0
  23. data/lib/lifen/patient.rb +27 -0
  24. data/lib/lifen/practitioner.rb +97 -0
  25. data/lib/lifen/user_authenticated_client.rb +39 -0
  26. data/lib/lifen/version.rb +3 -0
  27. data/lifen-fhir.gemspec +30 -0
  28. data/spec/binary_spec.rb +29 -0
  29. data/spec/cassettes/binary/download/invalid.yml +65 -0
  30. data/spec/cassettes/binary/download/valid.yml +1320 -0
  31. data/spec/cassettes/communication_request/send/invalid_medium.yml +69 -0
  32. data/spec/cassettes/communication_request/send/valid_attributes.yml +75 -0
  33. data/spec/cassettes/communication_request/send/valid_attributes_binary.yml +74 -0
  34. data/spec/cassettes/practitionner/create_channel/address/old_valid_attributes.yml +71 -0
  35. data/spec/cassettes/practitionner/create_channel/address/valid_attributes.yml +71 -0
  36. data/spec/cassettes/practitionner/create_channel/telecom/valid_attributes.yml +70 -0
  37. data/spec/cassettes/practitionner/find_by_rpps/existing_rpps.yml +125 -0
  38. data/spec/cassettes/practitionner/find_by_rpps/missing_line_attribute.yml +122 -0
  39. data/spec/cassettes/practitionner/find_by_rpps/wrong_rpps.yml +68 -0
  40. data/spec/category_spec.rb +21 -0
  41. data/spec/communication_request_spec.rb +64 -0
  42. data/spec/practitionner_spec.rb +76 -0
  43. data/spec/spec_helper.rb +24 -0
  44. data/spec/support/master_plan.pdf +0 -0
  45. metadata +230 -0
@@ -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
@@ -0,0 +1,15 @@
1
+ module Lifen
2
+
3
+ class ContentString < Base
4
+
5
+ attribute :text, String
6
+
7
+ def fhir_payload
8
+ {
9
+ contentString: text
10
+
11
+ }
12
+ end
13
+
14
+ end
15
+ end
@@ -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
@@ -0,0 +1,17 @@
1
+
2
+ module Lifen
3
+
4
+ class Medium < Base
5
+
6
+ attribute :uuid, String
7
+
8
+ def fhir_payload
9
+ {
10
+ coding:[{
11
+ id: uuid
12
+ }]
13
+ }
14
+ end
15
+
16
+ end
17
+ end
@@ -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
@@ -0,0 +1,3 @@
1
+ module Lifen
2
+ VERSION = "0.1.0"
3
+ end
@@ -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