hyrax-doi 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +65 -0
  3. data/Rakefile +23 -0
  4. data/app/actors/hyrax/actors/doi_actor.rb +50 -0
  5. data/app/assets/config/hyrax_doi_manifest.js +2 -0
  6. data/app/assets/javascripts/hyrax/doi/application.js +15 -0
  7. data/app/assets/stylesheets/hyrax/doi/application.css +15 -0
  8. data/app/controllers/hyrax/doi/application_controller.rb +15 -0
  9. data/app/controllers/hyrax/doi/hyrax_doi_controller.rb +91 -0
  10. data/app/forms/concerns/hyrax/doi/datacite_doi_form_behavior.rb +18 -0
  11. data/app/forms/concerns/hyrax/doi/doi_form_behavior.rb +18 -0
  12. data/app/helpers/hyrax/doi/helper_behavior.rb +9 -0
  13. data/app/helpers/hyrax/doi/work_form_helper.rb +15 -0
  14. data/app/helpers/hyrax/doi/work_show_helper.rb +12 -0
  15. data/app/jobs/hyrax/doi/application_job.rb +7 -0
  16. data/app/jobs/hyrax/doi/register_doi_job.rb +18 -0
  17. data/app/models/concerns/hyrax/doi/datacite_doi_behavior.rb +21 -0
  18. data/app/models/concerns/hyrax/doi/doi_behavior.rb +38 -0
  19. data/app/models/concerns/hyrax/doi/solr_document/datacite_doi_behavior.rb +14 -0
  20. data/app/models/concerns/hyrax/doi/solr_document/doi_behavior.rb +14 -0
  21. data/app/presenters/concerns/hyrax/doi/datacite_doi_presenter_behavior.rb +20 -0
  22. data/app/presenters/concerns/hyrax/doi/doi_presenter_behavior.rb +12 -0
  23. data/app/services/bolognese/readers/hyrax_work_reader.rb +99 -0
  24. data/app/services/bolognese/writers/hyrax_work_writer.rb +50 -0
  25. data/app/services/hyrax/doi/datacite_client.rb +138 -0
  26. data/app/services/hyrax/doi/datacite_registrar.rb +121 -0
  27. data/app/views/hyrax/base/_attribute_rows.html.erb +18 -0
  28. data/app/views/hyrax/base/_form_doi.html.erb +73 -0
  29. data/config/locales/hyrax_doi.en.yml +7 -0
  30. data/config/routes.rb +5 -0
  31. data/lib/generators/hyrax/doi/add_to_work_type_generator.rb +97 -0
  32. data/lib/generators/hyrax/doi/install_generator.rb +74 -0
  33. data/lib/generators/hyrax/doi/templates/config/initializers/hyrax-doi.rb +15 -0
  34. data/lib/hyrax/doi.rb +9 -0
  35. data/lib/hyrax/doi/engine.rb +28 -0
  36. data/lib/hyrax/doi/errors.rb +8 -0
  37. data/lib/hyrax/doi/spec/shared_specs.rb +9 -0
  38. data/lib/hyrax/doi/spec/shared_specs/datacite_doi_behavior.rb +40 -0
  39. data/lib/hyrax/doi/spec/shared_specs/datacite_doi_form_behavior.rb +17 -0
  40. data/lib/hyrax/doi/spec/shared_specs/datacite_doi_presenter_behavior.rb +39 -0
  41. data/lib/hyrax/doi/spec/shared_specs/doi_behavior.rb +63 -0
  42. data/lib/hyrax/doi/spec/shared_specs/doi_form_behavior.rb +17 -0
  43. data/lib/hyrax/doi/spec/shared_specs/doi_presenter_behavior.rb +19 -0
  44. data/lib/hyrax/doi/spec/shared_specs/solr_document/datacite_doi_behavior.rb +20 -0
  45. data/lib/hyrax/doi/spec/shared_specs/solr_document/doi_behavior.rb +20 -0
  46. data/lib/hyrax/doi/version.rb +6 -0
  47. data/lib/tasks/hyrax/doi_tasks.rake +5 -0
  48. metadata +319 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: d0263fd4d11d35e30f48410437354a6604f0726ddfeb13791062faf174c85d81
4
+ data.tar.gz: 7db8943b4fa0b9960572ad3dba14f4f6bb069af024338033b5260ba74aac4450
5
+ SHA512:
6
+ metadata.gz: 69950241eb6d5511544d937cd87cdb94351ec171ebc94e7ad6f4dc91a16cd95be5263dea80d366ff431c6e79ba47bc28fd0b89e435078d8d199fc5caa8f7d2eb
7
+ data.tar.gz: 789ac23699e1a9476c215350d1abef88dcf0fd5c596e2c7e76974d442fc4065d44ea9f0d8e26a895401635d93a5eb06f5bdc5b084189dfdd14702dff247b1266
@@ -0,0 +1,65 @@
1
+ # Hyrax::DOI
2
+ Code: [![CircleCI](https://circleci.com/gh/ubiquitypress/hyrax-doi.svg?style=svg)](https://circleci.com/gh/ubiquitypress/hyrax-doi)
3
+ [![Code Climate](https://codeclimate.com/github/ubiquitypress/hyrax-doi/badges/gpa.svg)](https://codeclimate.com/github/ubiquitypress/hyrax-doi)
4
+
5
+
6
+ Docs: [![Contribution Guidelines](http://img.shields.io/badge/CONTRIBUTING-Guidelines-blue.svg)](./CONTRIBUTING.md)
7
+ [![Apache 2.0 License](http://img.shields.io/badge/APACHE2-license-blue.svg)](./LICENSE)
8
+
9
+ Jump in: [![Slack Status](http://slack.samvera.org/badge.svg)](http://slack.samvera.org/)
10
+
11
+ Hyrax-doi is a Hyrax plugin that provides tools for working with DOIs including model attributes, minting, and fetching descriptive metadata.
12
+
13
+ ## Compatibilty
14
+ Hyrax-doi is compatible with Hyrax 2.9+ and tested with a [Hyrax 2.9.0 test application](https://github.com/ubiquitypress/hyrax_test_app) that mirrors the generated app used by Hyrax internally for testing.
15
+
16
+ ## Installation
17
+ Add this line to your application's Gemfile:
18
+
19
+ ```ruby
20
+ gem 'hyrax-doi'
21
+ ```
22
+
23
+ And then execute:
24
+ ```bash
25
+ $ bundle
26
+ ```
27
+
28
+ Or install it yourself as:
29
+ ```bash
30
+ $ gem install hyrax-doi
31
+ ```
32
+
33
+ ## Usage
34
+
35
+ ### Setup work type for hyrax-doi
36
+ Run the generator to add DOI support to a given work type:
37
+ ```
38
+ rails g hyrax:doi:add_to_work_type MyWorkType
39
+ ```
40
+
41
+ ## Development
42
+
43
+ ### Running Rake Tasks and Generators
44
+ When working on this engine rake tasks from Hyku can be run by prepending the `app` namespace (e.g. `rake app:db:migrate`). Generators provided by rails or other gems/engines can be run like normal from this engine's root (e.g. `rails g job UbiquityExporter`).
45
+
46
+ ### Development Server
47
+
48
+ To run a development server locally outside of docker do the following with each line in its own shell from the root of the engine:
49
+ ```
50
+ solr_wrapper -v --config .solr_wrapper.yml
51
+ fcrepo_wrapper -v --config .fcrepo_wrapper.yml
52
+ bundle exec rails server -b 0.0.0.0
53
+ ```
54
+
55
+ ### Testing
56
+
57
+ Tests are run automatically on CircleCI with rubocop and codeclimate. These tests must pass before pull requests can be merged.
58
+
59
+ To run the tests locally outside of docker do the following with each line in its own shell from the root of the engine:
60
+ ```
61
+ solr_wrapper -v --config .solr_wrapper_test.yml
62
+ fcrepo_wrapper -v --config .fcrepo_wrapper_test.yml
63
+ bundle exec rspec
64
+ ```
65
+ You shouldn't need to run anything from inside `spec/internal_test_hyrax` unless explicitly told to do so.
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+ begin
3
+ require 'bundler/setup'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+
8
+ require 'rdoc/task'
9
+
10
+ RDoc::Task.new(:rdoc) do |rdoc|
11
+ rdoc.rdoc_dir = 'rdoc'
12
+ rdoc.title = 'Hyrax::DOI'
13
+ rdoc.options << '--line-numbers'
14
+ rdoc.rdoc_files.include('README.md')
15
+ rdoc.rdoc_files.include('lib/**/*.rb')
16
+ end
17
+
18
+ APP_RAKEFILE = File.expand_path("spec/internal_test_hyrax/Rakefile", __dir__)
19
+ load 'rails/tasks/engine.rake'
20
+
21
+ load 'rails/tasks/statistics.rake'
22
+
23
+ require 'bundler/gem_tasks'
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+ module Hyrax
3
+ module Actors
4
+ ##
5
+ # An actor that registers a DOI using the configured registar
6
+ # This actor should come after the model actor which saves the work
7
+ #
8
+ # @example use in middleware
9
+ # stack = ActionDispatch::MiddlewareStack.new.tap do |middleware|
10
+ # # middleware.use OtherMiddleware
11
+ # middleware.use Hyrax::Actors::DOIActor
12
+ # # middleware.use MoreMiddleware
13
+ # end
14
+ #
15
+ # env = Hyrax::Actors::Environment.new(object, ability, attributes)
16
+ # last_actor = Hyrax::Actors::Terminator.new
17
+ # stack.build(last_actor).create(env)
18
+ class DOIActor < AbstractActor
19
+ ##
20
+ # @return [Boolean]
21
+ #
22
+ # @see Hyrax::Actors::AbstractActor
23
+ def create(env)
24
+ # Assume the model actor has already run and saved the work
25
+ create_or_update_doi(env.curation_concern) && next_actor.create(env)
26
+ end
27
+
28
+ ##
29
+ # @return [Boolean]
30
+ #
31
+ # @see Hyrax::Actors::AbstractActor
32
+ def update(env)
33
+ create_or_update_doi(env.curation_concern) && next_actor.update(env)
34
+ end
35
+
36
+ private
37
+
38
+ def create_or_update_doi(work)
39
+ return true unless doi_enabled_work_type?(work)
40
+
41
+ Hyrax::DOI::RegisterDOIJob.perform_later(work, registrar: work.doi_registrar.presence, registrar_opts: work.doi_registrar_opts)
42
+ end
43
+
44
+ # Check if work is DOI enabled
45
+ def doi_enabled_work_type?(work)
46
+ work.class.ancestors.include? Hyrax::DOI::DOIBehavior
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,2 @@
1
+ //= link_directory ../javascripts/hyrax/doi .js
2
+ //= link_directory ../stylesheets/hyrax/doi .css
@@ -0,0 +1,15 @@
1
+ // This is a manifest file that'll be compiled into application.js, which will include all the files
2
+ // listed below.
3
+ //
4
+ // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
5
+ // or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
6
+ //
7
+ // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8
+ // compiled file. JavaScript code in this file should be added after the last require_* statement.
9
+ //
10
+ // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
11
+ // about supported directives.
12
+ //
13
+ //= require rails-ujs
14
+ //= require activestorage
15
+ //= require_tree .
@@ -0,0 +1,15 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9
+ * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
10
+ * files in this directory. Styles in this file should be added after the last require_* statement.
11
+ * It is generally better to create a new file per style scope.
12
+ *
13
+ *= require_tree .
14
+ *= require_self
15
+ */
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+ module Hyrax
3
+ module DOI
4
+ class ApplicationController < ActionController::Base
5
+ protect_from_forgery with: :exception
6
+
7
+ def self.search_state_class=(*)
8
+ # no-op to make Hyrax::Controller happy
9
+ end
10
+
11
+ # Include after search_state_class is defined since Hyrax::Controller calls it
12
+ include Hyrax::Controller
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+ module Hyrax
3
+ module DOI
4
+ class HyraxDOIController < ApplicationController
5
+ before_action :check_authorization
6
+
7
+ def create_draft_doi
8
+ draft_doi = mint_draft_doi
9
+
10
+ respond_to do |format|
11
+ format.js { render js: autofill_field(doi_attribute_name, draft_doi), status: :created }
12
+ format.json { render_json_response(response_type: :created, options: { data: draft_doi }) }
13
+ end
14
+ rescue Hyrax::DOI::DataCiteClient::Error => e
15
+ respond_to do |format|
16
+ format.js { render plain: e.message, status: :internal_server_error }
17
+ format.json { render_json_response(response_type: :internal_error, message: e.full_message) }
18
+ end
19
+ end
20
+
21
+ def autofill
22
+ doi = params['doi']
23
+
24
+ respond_to do |format|
25
+ format.js { render js: autofill_js(doi), status: :ok }
26
+ end
27
+ rescue Hyrax::DOI::NotFoundError => e
28
+ respond_to do |format|
29
+ format.js { render plain: e.message, status: :internal_server_error }
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ def check_authorization
36
+ raise Hydra::AccessDenied unless current_ability.can_create_any_work?
37
+ end
38
+
39
+ def mint_draft_doi
40
+ doi_registrar.mint_draft_doi
41
+ end
42
+
43
+ def doi_registrar
44
+ # TODO: generalize this
45
+ Hyrax::Identifier::Registrar.for(:datacite, {})
46
+ end
47
+
48
+ def field_selector(attribute_name)
49
+ ".#{params[:curation_concern]}_#{attribute_name}"
50
+ end
51
+
52
+ def doi_attribute_name
53
+ params[:attribute] || "doi"
54
+ end
55
+
56
+ def hyrax_work_from_doi(doi)
57
+ meta = Bolognese::Metadata.new(input: doi)
58
+ # Check that a record was actually loaded
59
+ raise Hyrax::DOI::NotFoundError, "DOI (#{doi}) could not be found." if meta.blank? || meta.doi.blank?
60
+ meta.hyrax_work
61
+ end
62
+
63
+ # TODO: Move this out to a partial that gets rendered?
64
+ def autofill_js(doi)
65
+ # TODO: Need to wipe old data or is this just supplemental?
66
+ js = hyrax_work_from_doi(doi).attributes.collect { |k, v| autofill_field(k, v) }.reject(&:blank?).join("\n")
67
+ js << "document.location = '#metadata';"
68
+ end
69
+
70
+ # TODO: Move this out to a partial that gets rendered?
71
+ def autofill_field(attribute_name, value)
72
+ js = []
73
+ # TODO: add error handling in the JS so an error doesn't leave the autofilling incomplete
74
+ Array(value).each_with_index do |v, index|
75
+ # Is this the right way to do this?
76
+ # Need to be smarter to see if all repeated fields are filled before trying to create a new one by clicking?
77
+ js << "document.querySelectorAll('#{field_selector(attribute_name)} button.add')[0].click();" unless index.zero?
78
+ js << "document.querySelectorAll('#{field_selector(attribute_name)} .form-control')[#{index}].value = '#{helpers.escape_javascript(v)}';"
79
+ end
80
+ js.reject(&:blank?).join("\n")
81
+ end
82
+
83
+ # Override of Hyrax method (See https://github.com/samvera/hyrax/pull/4495)
84
+ # render a json response for +response_type+
85
+ def render_json_response(response_type: :success, message: nil, options: {})
86
+ json_body = Hyrax::API.generate_response_body(response_type: response_type, message: message, options: options)
87
+ render json: json_body, status: Hyrax::API.default_responses[response_type][:code]
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+ module Hyrax
3
+ module DOI
4
+ module DataCiteDOIFormBehavior
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ self.terms += [:doi_status_when_public]
9
+
10
+ delegate :doi_status_when_public, to: :model
11
+ end
12
+
13
+ def secondary_terms
14
+ super - [:doi_status_when_public]
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+ module Hyrax
3
+ module DOI
4
+ module DOIFormBehavior
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ self.terms += [:doi]
9
+
10
+ delegate :doi, to: :model
11
+ end
12
+
13
+ def secondary_terms
14
+ super - [:doi]
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+ module Hyrax
3
+ module DOI
4
+ module HelperBehavior
5
+ include Hyrax::DOI::WorkFormHelper
6
+ include Hyrax::DOI::WorkShowHelper
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+ module Hyrax
3
+ module DOI
4
+ module WorkFormHelper
5
+ def form_tabs_for(form:)
6
+ if form.model_class.ancestors.include? Hyrax::DOI::DOIBehavior
7
+ # TODO: Add check for feature flipper?
8
+ super.prepend("doi")
9
+ else
10
+ super
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+ module Hyrax
3
+ module DOI
4
+ module WorkShowHelper
5
+ def render_doi?(presenter)
6
+ return false unless presenter.class.ancestors.include? Hyrax::DOI::DOIPresenterBehavior
7
+ return presenter.doi_status_when_public.in? [nil, 'registered', 'findable'] if presenter.class.ancestors.include? Hyrax::DOI::DataCiteDOIPresenterBehavior
8
+ true
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+ module Hyrax
3
+ module DOI
4
+ class ApplicationJob < ActiveJob::Base
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+ module Hyrax
3
+ module DOI
4
+ class RegisterDOIJob < ApplicationJob
5
+ queue_as Hyrax.config.ingest_queue_name
6
+
7
+ ##
8
+ # @param model [ActiveFedora::Base]
9
+ # @param registrar [String] Note this is a string and not a symbol because ActiveJob cannot serialize a symbol
10
+ # @param registrar_opts [Hash]
11
+ def perform(model, registrar: Hyrax.config.identifier_registrars.keys.first, registrar_opts: {})
12
+ Hyrax::Identifier::Dispatcher
13
+ .for(registrar.to_sym, **registrar_opts)
14
+ .assign_for!(object: model, attribute: :doi)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+ module Hyrax
3
+ module DOI
4
+ module DataCiteDOIBehavior
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ property :doi_status_when_public, predicate: ::RDF::URI('http://samvera.org/ns/hyrax/doi#doi_status_when_public'), multiple: false do |index|
9
+ index.as :stored_sortable
10
+ end
11
+
12
+ validates :doi_status_when_public, inclusion: { in: Hyrax::DOI::DataCiteRegistrar::STATES }, allow_nil: true
13
+ end
14
+
15
+ # Override
16
+ def doi_registrar
17
+ 'datacite'
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+ module Hyrax
3
+ module DOI
4
+ module DOIBehavior
5
+ extend ActiveSupport::Concern
6
+
7
+ DOI_REGEX = /\A10\.\d{4,}(\.\d+)*\/[-._;():\/A-Za-z\d]+\z/.freeze
8
+
9
+ included do
10
+ property :doi, predicate: ::RDF::Vocab::BIBO.doi, multiple: true do |index|
11
+ index.as :stored_sortable
12
+ end
13
+
14
+ validate :validate_doi
15
+ end
16
+
17
+ # Override this method
18
+ # Specify a registrar to use with this class
19
+ def doi_registrar
20
+ nil
21
+ end
22
+
23
+ # Override this method
24
+ # Specify options for the registrar to use with this class
25
+ def doi_registrar_opts
26
+ {}
27
+ end
28
+
29
+ private
30
+
31
+ def validate_doi
32
+ Array(doi).each do |doi|
33
+ errors.add(:doi, "DOI (#{doi}) is invalid.") unless doi.match? DOI_REGEX
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end