datacite/lupo

View on GitHub
app/models/concerns/helpable.rb

Summary

Maintainability
D
2 days
Test Coverage
F
17%
# frozen_string_literal: true

module Helpable
  extend ActiveSupport::Concern

  require "bolognese"
  require "csv"
  require "securerandom"
  require "base32/url"

  UPPER_LIMIT = 1_073_741_823

  included do
    include Bolognese::Utils
    include Bolognese::DoiUtils

    def register_url
      if url.blank?
        raise ActionController::BadRequest.new,
              "[Handle] Error updating DOI " + doi + ": url missing."
      end

      if client_id.blank?
        raise ActionController::BadRequest.new,
              "[Handle] Error updating DOI " + doi + ": client ID missing."
      end

      unless is_registered_or_findable?
        raise ActionController::BadRequest.new,
              "DOI is not registered or findable."
      end

      payload = [
        {
          "index" => 100,
          "type" => "HS_ADMIN",
          "data" => {
            "format" => "admin",
            "value" => {
              "handle" => ENV["HANDLE_USERNAME"],
              "index" => 300,
              "permissions" => "111111111111",
            },
          },
        },
        {
          "index" => 1,
          "type" => "URL",
          "data" => { "format" => "string", "value" => url },
        },
      ].to_json

      handle_url = "#{ENV['HANDLE_URL']}/api/handles/#{doi}"
      response =
        Maremma.put(
          handle_url,
          content_type: "application/json;charset=UTF-8",
          data: payload,
          username: "300%3A#{ENV['HANDLE_USERNAME']}",
          password: ENV["HANDLE_PASSWORD"],
          ssl_self_signed: true,
          timeout: 10,
        )

      # update minted column after first successful registration in handle system

      if [200, 201].include?(response.status)
        if minted.blank?
          update(minted: Time.zone.now, updated: Time.zone.now)
        end

        unless Rails.env.test?
          Rails.logger.debug "[Handle] URL for DOI " + doi + " updated to " +
            url +
            "."
        end
      elsif response.status == 404
        Rails.logger.info "[Handle] Error updating URL for DOI " + doi +
          ": not found"
      elsif response.status == 408
        Rails.logger.warn "[Handle] Error updating URL for DOI " + doi +
          ": timeout"
      else
        unless Rails.env.test?
          Rails.logger.error "[Handle] Error updating URL for DOI " + doi +
            ": " +
            response.body.inspect
        end
      end

      response
    end

    def get_url
      url = "#{ENV['HANDLE_URL']}/api/handles/#{doi}?index=1"
      response = Maremma.get(url, ssl_self_signed: true, timeout: 10)

      if response.status != 200
        unless Rails.env.test?
          Rails.logger.error "[Handle] Error fetching URL for DOI " + doi +
            ": " +
            response.body.inspect
        end
      end

      response
    end

    def generate_random_provider_symbol
      "4:X".gen
    end

    def generate_random_repository_symbol
      "6:X".gen
    end

    def generate_random_dois(str, options = {})
      prefix = validate_prefix(str)
      fail IdentifierError, "No valid prefix found" if prefix.blank?

      shoulder = str.split("/", 2)[1].to_s
      encode_doi(
        prefix,
        shoulder: shoulder, number: options[:number], size: options[:size],
      )
    end

    def encode_doi(prefix, options = {})
      return nil if prefix.blank?

      number = options[:number].to_s.scan(/\d+/).join("").to_i
      shoulder = options[:shoulder].to_s
      shoulder += "-" if shoulder.present?
      length = 8
      split = 4
      size = (options[:size] || 1).to_i

      Array.new(size).map do |_a|
        n = number.positive? ? number : SecureRandom.random_number(UPPER_LIMIT)
        prefix.to_s + "/" + shoulder +
          Base32::URL.encode(n, split: split, length: length, checksum: true)
      end.uniq
    end

    def epoch_to_utc(epoch)
      Time.at(epoch).to_datetime.utc.iso8601
    end

    def https_to_http(url)
      uri = Addressable::URI.parse(url)
      uri.scheme = "http"
      uri.to_s
    end

    def match_url_with_domains(url: nil, domains: nil)
      return false if url.blank? || domains.blank?
      return true if domains == "*"

      uri = Addressable::URI.parse(url)
      domain_list = domains.split(",")
      domain_list.any? do |d|
        if d.starts_with?("*.")
          # strip asterix for subdomain

          d = d[1..-1]
          uri.host.ends_with?(d)
        else
          uri.host == d
        end
      end
    end
  end

  module ClassMethods
    def get_dois(options = {})
      if options[:prefix].blank?
        return OpenStruct.new(
          body: { "errors" => [{ "title" => "Prefix missing" }] },
        )
      end

      count_url =
        ENV["HANDLE_URL"] + "/api/handles?prefix=#{options[:prefix]}&pageSize=0"
      response =
        Maremma.get(
          count_url,
          username: "300%3A#{ENV['HANDLE_USERNAME']}",
          password: ENV["HANDLE_PASSWORD"],
          ssl_self_signed: true,
          timeout: 60,
        )

      total = response.body.dig("data", "totalCount").to_i
      dois = []

      if total > 0
        # walk through paginated results
        total_pages = (total.to_f / 1_000).ceil

        (0...total_pages).each do |page|
          url =
            ENV["HANDLE_URL"] +
            "/api/handles?prefix=#{options[:prefix]}&page=#{
              page
            }&pageSize=1000"
          response =
            Maremma.get(
              url,
              username: "300%3A#{ENV['HANDLE_USERNAME']}",
              password: ENV["HANDLE_PASSWORD"],
              ssl_self_signed: true,
              timeout: 60,
            )
          if response.status == 200
            dois += (response.body.dig("data", "handles") || [])
          else
            text = "Error " + response.body["errors"].inspect

            Rails.logger.error "[Handle] " + text
            unless Rails.env.test?
              User.send_notification_to_slack(
                text,
                title: "Error #{response.status}", level: "danger",
              )
            end
          end
        end
      end

      dois
    end

    def get_doi(options = {})
      if options[:doi].blank?
        return OpenStruct.new(
          body: { "errors" => [{ "title" => "DOI missing" }] },
        )
      end

      url =
        if Rails.env.production?
          "https://doi.org"
        else
          "https://handle.test.datacite.org"
        end
      url += "/api/handles/#{options[:doi]}"
      response =
        Maremma.get(
          url,
          username: "300%3A#{ENV['HANDLE_USERNAME']}",
          password: ENV["HANDLE_PASSWORD"],
          ssl_self_signed: true,
          timeout: 10,
        )

      if response.status == 200
        response
      elsif response.status == 404
        OpenStruct.new(
          status: 404,
          body: { "errors" => [{ "status" => 404, "title" => "Not found." }] },
        )
      else
        text = "Error " + response.body["errors"].inspect

        Rails.logger.error "[Handle] " + text
        unless Rails.env.test?
          User.send_notification_to_slack(
            text,
            title: "Error #{response.status}", level: "danger",
          )
        end
        OpenStruct.new(
          status: 400,
          body: {
            "errors" => [
              { "status" => 400, "title" => response.body["errors"].inspect },
            ],
          },
        )
      end
    end

    def delete_doi(options = {})
      if options[:doi].blank?
        return OpenStruct.new(
          body: { "errors" => [{ "title" => "DOI missing" }] },
        )
      end
      unless options[:doi].start_with?("10.5072")
        return OpenStruct.new(
          body: {
            "errors" => [
              { "title" => "Only DOIs with prefix 10.5072 can be deleted" },
            ],
          },
        )
      end

      url = "#{ENV['HANDLE_URL']}/api/handles/#{options[:doi]}"
      response =
        Maremma.delete(
          url,
          username: "300%3A#{ENV['HANDLE_USERNAME']}",
          password: ENV["HANDLE_PASSWORD"],
          ssl_self_signed: true,
          timeout: 10,
        )

      if response.status == 200
        response
      elsif response.status == 404
        OpenStruct.new(
          status: 404,
          body: { "errors" => [{ "status" => 404, "title" => "Not found." }] },
        )
      else
        text = "Error " + response.body["errors"].inspect

        Rails.logger.error "[Handle] " + text
        unless Rails.env.test?
          User.send_notification_to_slack(
            text,
            title: "Error #{response.status}", level: "danger",
          )
        end
        response
      end
    end

    def parse_attributes(element, options = {})
      content = options[:content] || "__content__"

      if element.is_a?(String) && options[:content].nil?
        CGI.unescapeHTML(element)
      elsif element.is_a?(Hash)
        element.fetch(CGI.unescapeHTML(content), nil)
      elsif element.is_a?(Array)
        a =
          element.map do |e|
            e.is_a?(Hash) ? e.fetch(CGI.unescapeHTML(content), nil) : e
          end.uniq
        options[:first] ? a.first : a.unwrap
      end
    end
  end
end