# frozen_string_literal: true module Imis class Api AUTHENTICATION_PATH = 'Token' API_PATH = 'api' QUERY_PATH = 'api/Query' attr_reader :token, :token_expiration, :imis_id def initialize(skip_authentication: false) authenticate unless skip_authentication end # Manually set the current ID, if you already have it for a given member # def imis_id=(id) @imis_id = id.to_s end # Convert a member's certificate number into an iMIS ID number # def imis_id_for(certificate) result = query(Imis.configuration.imis_id_query_name, { certificate: certificate }) @imis_id = result['Items']['$values'][0]['ID'] end # Run requests as DSL, with specific iMIS ID only maintained for this scope # # This should be used with methods that do not change the value of `imis_id` # def with(id, &block) old_id = imis_id self.imis_id = id instance_eval(&block) ensure self.imis_id = old_id end # Get a business object for the current member # def get(business_object_name) uri = uri_for(business_object_name) request = Net::HTTP::Get.new(uri) result = submit(uri, authorize(request)) JSON.parse(result.body) end # Update only specific fields on a business object for the current member # # fields - hash of shape: { field_name => new_value } # def put(business_object_name, fields) uri = uri_for(business_object_name) request = Net::HTTP::Put.new(uri) updated = filter_fields(business_object_name, fields) request.body = JSON.dump(updated) result = submit(uri, authorize(request)) JSON.parse(result.body) end # Run an IQA Query # # query_name - the full path of the query in IQA, e.g. `$/_ABC/Fiander/iMIS_ID` # query_params - hash of shape: { param_name => param_value } # def query(query_name, query_params = {}) query_params[:QueryName] = query_name path = "#{QUERY_PATH}?#{query_params.to_query}" uri = URI(File.join(imis_hostname, path)) request = Net::HTTP::Get.new(uri) result = submit(uri, authorize(request)) JSON.parse(result.body) end def mapper @mapper ||= Mapper.new(self) end private def client(uri) Net::HTTP.new(uri.host, uri.port).tap do |http| http.use_ssl = true http.verify_mode = OpenSSL::SSL::VERIFY_PEER end end def imis_hostname Imis.configuration.hostname end # Authorize a request prior to submitting # # If the current token is missing/expired, request a new one # def authorize(request) authenticate if token_expiration < Time.now request.tap { |r| r.add_field('Authorization', "Bearer #{token}") } end # Construct a business object API endpoint address # def uri_for(business_object_name) URI(File.join(imis_hostname, "#{API_PATH}/#{business_object_name}/#{imis_id}")) end def submit(uri, request) client(uri).request(request).tap do |result| raise Error::Api.from(result) unless result.is_a?(Net::HTTPSuccess) end end # Authenticate to the iMIS API, and store the access token and expiration time # def authenticate uri = URI(File.join(imis_hostname, AUTHENTICATION_PATH)) req = Net::HTTP::Post.new(uri) authentication_data = { grant_type: 'password', username: Imis.configuration.username, password: Imis.configuration.password } req.body = URI.encode_www_form(authentication_data) result = submit(uri, req) json = JSON.parse(result.body) @token = json['access_token'] @token_expiration = Time.parse(json['.expires']) end # Manually assemble the matching data structure, with fields in the correct order # def filter_fields(business_object_name, fields) existing = get(business_object_name) JSON.parse(JSON.dump(existing)).tap do |updated| # The first property is always the iMIS ID again updated['Properties']['$values'] = [existing['Properties']['$values'][0]] # Iterate through all existing fields existing['Properties']['$values'].each do |value| next unless fields.keys.include?(value['Name']) # Strings are not wrapped in the type definition structure new_value = fields[value['Name']] if new_value.is_a?(String) value['Value'] = new_value else value['Value']['$value'] = new_value end # Add the completed field with the updated value updated['Properties']['$values'] << value end end end end end