labimotion 2.2.0.rc13 → 2.2.0.rc14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/labimotion/apis/labimotion_doi_api.rb +12 -12
- data/lib/labimotion/apis/labimotion_template_browse_api.rb +2 -2
- data/lib/labimotion/usecases/build_template_doi_xml.rb +103 -0
- data/lib/labimotion/usecases/release_template_doi.rb +90 -0
- data/lib/labimotion/usecases/reserve_template_doi.rb +70 -0
- data/lib/labimotion/usecases/template_doi_helpers.rb +75 -0
- data/lib/labimotion/usecases/update_template_publication_metadata.rb +59 -0
- data/lib/labimotion/version.rb +1 -1
- data/lib/labimotion.rb +7 -0
- metadata +6 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 92bb0da102b4ac516f423c274a21bb59a08fffecb2d451d0f1d5caee8631d5af
|
|
4
|
+
data.tar.gz: e25335102afc9aa97669c3c09f671f64fa2102344fa2764817b6fe9556638359
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f9f574b0057178c25cb0624bd11370757adf1992e8f5734a7492ac380dadd779d06c83accabe1e59c07e189ce316258da0a8177b2c42fa028f4aaf418245fbda
|
|
7
|
+
data.tar.gz: f9d629342ffead4be91ce5728d2048a56b9de3da660df32b962d549756391ca34758edf7e1392e3aefbd117d185ff179cda085e59f4d5ad4b20e802fbdcbf060
|
|
@@ -6,11 +6,11 @@ module Labimotion
|
|
|
6
6
|
class LabimotionDoiAPI < Grape::API
|
|
7
7
|
# rubocop:disable Metrics/BlockLength
|
|
8
8
|
# Grape route and helper DSL blocks are necessarily large.
|
|
9
|
-
KLASS_TYPES =
|
|
9
|
+
KLASS_TYPES = Labimotion::TemplateDoiHelpers::KLASS_BY_TYPE.keys.freeze
|
|
10
10
|
|
|
11
11
|
helpers do
|
|
12
12
|
def resolve_klass!(type, id)
|
|
13
|
-
model =
|
|
13
|
+
model = Labimotion::TemplateDoiHelpers::KLASS_BY_TYPE[type]
|
|
14
14
|
error!('unknown template type', 404) unless model
|
|
15
15
|
|
|
16
16
|
record = model.find_by(id: id)
|
|
@@ -20,7 +20,7 @@ module Labimotion
|
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
def render_template_doi(record)
|
|
23
|
-
type =
|
|
23
|
+
type = Labimotion::TemplateDoiHelpers.klass_type_for(record)
|
|
24
24
|
dois = ::Doi.labimotion_dois(record)
|
|
25
25
|
# `doi` is the current (most recently reserved) DOI; `released_doi` is the
|
|
26
26
|
# latest released (minted) one. They differ once a new version has been
|
|
@@ -35,9 +35,9 @@ module Labimotion
|
|
|
35
35
|
publication: (record.properties_template || {})['publication'],
|
|
36
36
|
released_at: record.released_at,
|
|
37
37
|
released_by: record.released_by,
|
|
38
|
-
new_version_available:
|
|
38
|
+
new_version_available: Labimotion::TemplateDoiHelpers.new_version_available?(record),
|
|
39
39
|
next_doi_version: ::Doi.labimotion_version_segment(record),
|
|
40
|
-
template_url:
|
|
40
|
+
template_url: Labimotion::TemplateDoiHelpers.template_url(record)
|
|
41
41
|
)
|
|
42
42
|
end
|
|
43
43
|
|
|
@@ -67,7 +67,7 @@ module Labimotion
|
|
|
67
67
|
label: record.try(:label),
|
|
68
68
|
template_version: record.try(:version),
|
|
69
69
|
next_doi_version: ::Doi.labimotion_version_segment(record),
|
|
70
|
-
template_url:
|
|
70
|
+
template_url: Labimotion::TemplateDoiHelpers.template_url(record),
|
|
71
71
|
publication: publication,
|
|
72
72
|
versions: versions
|
|
73
73
|
)
|
|
@@ -82,7 +82,7 @@ module Labimotion
|
|
|
82
82
|
route_param :type, type: String, values: KLASS_TYPES do
|
|
83
83
|
desc 'Released DOIs + publication metadata for every released template of a type'
|
|
84
84
|
get :released do
|
|
85
|
-
model =
|
|
85
|
+
model = Labimotion::TemplateDoiHelpers::KLASS_BY_TYPE[params[:type]]
|
|
86
86
|
grouped = ::Doi.where(doiable_type: model.name).where.not(minted_at: nil)
|
|
87
87
|
.order(:id).group_by(&:doiable_id)
|
|
88
88
|
records = model.where(id: grouped.keys).index_by(&:id)
|
|
@@ -98,7 +98,7 @@ module Labimotion
|
|
|
98
98
|
route_param :type, type: String, values: KLASS_TYPES do
|
|
99
99
|
desc "DOI state (reserved / released) per template of a type, for the designer grid's DOI icon"
|
|
100
100
|
get :states do
|
|
101
|
-
model =
|
|
101
|
+
model = Labimotion::TemplateDoiHelpers::KLASS_BY_TYPE[params[:type]]
|
|
102
102
|
grouped = ::Doi.where(doiable_type: model.name).order(:id).group_by(&:doiable_id)
|
|
103
103
|
states = grouped.transform_values do |list|
|
|
104
104
|
released = list.last.minted == true
|
|
@@ -125,7 +125,7 @@ module Labimotion
|
|
|
125
125
|
version: ::Doi.labimotion_doi_version(doi),
|
|
126
126
|
full_doi: doi.full_doi,
|
|
127
127
|
minted: doi.minted == true,
|
|
128
|
-
xml:
|
|
128
|
+
xml: Labimotion::BuildTemplateDoiXml.new(record, doi, current_user).call
|
|
129
129
|
}
|
|
130
130
|
end
|
|
131
131
|
{ versions: versions }
|
|
@@ -137,7 +137,7 @@ module Labimotion
|
|
|
137
137
|
end
|
|
138
138
|
put :publication_metadata do
|
|
139
139
|
record = resolve_klass!(params[:type], params[:id])
|
|
140
|
-
uc =
|
|
140
|
+
uc = Labimotion::UpdateTemplatePublicationMetadata.new(
|
|
141
141
|
record, current_user, params[:publication]
|
|
142
142
|
).call
|
|
143
143
|
error!(uc.error, 422) unless uc.success?
|
|
@@ -148,7 +148,7 @@ module Labimotion
|
|
|
148
148
|
desc 'Reserves a DataCite DOI for a template'
|
|
149
149
|
post :reserve_doi do
|
|
150
150
|
record = resolve_klass!(params[:type], params[:id])
|
|
151
|
-
uc =
|
|
151
|
+
uc = Labimotion::ReserveTemplateDoi.new(record, current_user).call
|
|
152
152
|
error!(uc.error, 422) unless uc.success?
|
|
153
153
|
|
|
154
154
|
render_template_doi(record.reload)
|
|
@@ -157,7 +157,7 @@ module Labimotion
|
|
|
157
157
|
desc 'Releases (mints) the reserved DOI for a template'
|
|
158
158
|
post :release_doi do
|
|
159
159
|
record = resolve_klass!(params[:type], params[:id])
|
|
160
|
-
uc =
|
|
160
|
+
uc = Labimotion::ReleaseTemplateDoi.new(record, current_user).call
|
|
161
161
|
error!(uc.error, 422) unless uc.success?
|
|
162
162
|
|
|
163
163
|
render_template_doi(record.reload)
|
|
@@ -7,7 +7,7 @@ module Labimotion
|
|
|
7
7
|
# properties_release so the frontend can render the example inline.
|
|
8
8
|
# Whitelisted in RepoAPI::PUBLIC_URLS so anonymous hub visitors can use it.
|
|
9
9
|
class LabimotionTemplateBrowseAPI < Grape::API
|
|
10
|
-
KLASS_BY_TYPE =
|
|
10
|
+
KLASS_BY_TYPE = Labimotion::TemplateDoiHelpers::KLASS_BY_TYPE
|
|
11
11
|
KLASS_TYPES = KLASS_BY_TYPE.keys.freeze
|
|
12
12
|
# type => [revision model, foreign key to the klass]
|
|
13
13
|
REVISION_BY_TYPE = {
|
|
@@ -88,7 +88,7 @@ module Labimotion
|
|
|
88
88
|
full = doi.full_doi
|
|
89
89
|
{ version: ::Doi.labimotion_doi_version(doi), doi: full,
|
|
90
90
|
doi_url: "https://dx.doi.org/#{full}", minted_at: doi.minted_at,
|
|
91
|
-
xml:
|
|
91
|
+
xml: Labimotion::BuildTemplateDoiXml.new(record, doi).call }
|
|
92
92
|
end
|
|
93
93
|
end
|
|
94
94
|
end
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'erb'
|
|
4
|
+
|
|
5
|
+
module Labimotion
|
|
6
|
+
# Builds the DataCite (kernel-4) metadata XML for a LabIMotion template DOI.
|
|
7
|
+
# The same XML is uploaded to DataCite at release time and shown in the UI
|
|
8
|
+
# preview, so what users see is exactly what gets sent. A template has a
|
|
9
|
+
# single DOI; the ERB template binds to this instance, so the methods below
|
|
10
|
+
# are its accessors.
|
|
11
|
+
class BuildTemplateDoiXml
|
|
12
|
+
include ::DataCitePublisher
|
|
13
|
+
|
|
14
|
+
TEMPLATE_PATH = Rails.root.join('app/publish/datacite_metadata_labimotion_template.html.erb')
|
|
15
|
+
|
|
16
|
+
RESOURCE_LABEL = {
|
|
17
|
+
'element' => 'LabIMotion Element Template',
|
|
18
|
+
'segment' => 'LabIMotion Segment Template',
|
|
19
|
+
'dataset' => 'LabIMotion Dataset Template'
|
|
20
|
+
}.freeze
|
|
21
|
+
|
|
22
|
+
def initialize(record, doi = nil, current_user = nil)
|
|
23
|
+
@record = record
|
|
24
|
+
@doi = doi || ::Doi.labimotion_latest_doi(record)
|
|
25
|
+
@current_user = current_user
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Returns the XML string, or nil when no DOI has been reserved yet.
|
|
29
|
+
def call
|
|
30
|
+
return nil if @doi.nil?
|
|
31
|
+
|
|
32
|
+
ERB.new(File.read(TEMPLATE_PATH), trim_mode: '-').result(binding)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def full_doi
|
|
38
|
+
@doi.full_doi
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def title
|
|
42
|
+
h(publication['title'].presence || @record.label.presence || @record.try(:name))
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def description
|
|
46
|
+
h(publication['description'])
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# The DOI version this DOI was reserved for (kept on the DOI), so each
|
|
50
|
+
# DOI's XML keeps showing its own version even after the template moves on.
|
|
51
|
+
def version
|
|
52
|
+
h(::Doi.labimotion_doi_version(@doi).presence || ::Doi.labimotion_version_segment(@record))
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def license
|
|
56
|
+
h(publication['license'].presence || 'CC-BY-4.0')
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def resource_label
|
|
60
|
+
RESOURCE_LABEL[TemplateDoiHelpers.klass_type_for(@record)]
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def publication_year
|
|
64
|
+
(@doi.minted_at || Time.current).strftime('%Y')
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def creators
|
|
68
|
+
Array(publication['authors']).filter_map do |author|
|
|
69
|
+
next unless author.is_a?(Hash)
|
|
70
|
+
|
|
71
|
+
given = author['givenName'].to_s.strip
|
|
72
|
+
family = author['familyName'].to_s.strip
|
|
73
|
+
next if given.empty? && family.empty?
|
|
74
|
+
|
|
75
|
+
{ 'givenName' => h(given), 'familyName' => h(family),
|
|
76
|
+
'orcid' => h(author['orcid'].to_s.strip), 'affiliation' => h(author['affiliation'].to_s.strip) }
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# The acting user, rendered as a DataCite "Researcher" contributor. Nil
|
|
81
|
+
# (no contributors block) when there is no user or they have no name.
|
|
82
|
+
def contributor
|
|
83
|
+
user = @current_user
|
|
84
|
+
return nil unless user
|
|
85
|
+
|
|
86
|
+
given = user.try(:first_name).to_s.strip
|
|
87
|
+
family = user.try(:last_name).to_s.strip
|
|
88
|
+
return nil if given.empty? && family.empty?
|
|
89
|
+
|
|
90
|
+
orcid = user.respond_to?(:orcid) ? user.orcid.to_s.strip : ''
|
|
91
|
+
{ 'givenName' => h(given), 'familyName' => h(family), 'orcid' => h(orcid) }
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def publication
|
|
95
|
+
@publication ||=
|
|
96
|
+
(@record.properties_template.is_a?(Hash) ? @record.properties_template['publication'] : nil) || {}
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def h(value)
|
|
100
|
+
ERB::Util.html_escape(value.to_s)
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Labimotion
|
|
4
|
+
# Mints (publishes) a previously reserved DOI for a LabIMotion template
|
|
5
|
+
# via DataCite MDS. On success flips Doi#minted=true and stamps
|
|
6
|
+
# released_by/released_at on the template record.
|
|
7
|
+
class ReleaseTemplateDoi
|
|
8
|
+
include TemplateDoiHelpers
|
|
9
|
+
|
|
10
|
+
attr_reader :record, :current_user, :doi, :error
|
|
11
|
+
|
|
12
|
+
def initialize(record, current_user)
|
|
13
|
+
@record = record
|
|
14
|
+
@current_user = current_user
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def call
|
|
18
|
+
return failure('record is required') if record.nil?
|
|
19
|
+
return failure('unauthorized') unless TemplateDoiHelpers.authorized?(record, current_user)
|
|
20
|
+
|
|
21
|
+
# Release the template's current (highest-sequence) DOI — the one a fresh
|
|
22
|
+
# reserve created or returned.
|
|
23
|
+
@doi = ::Doi.labimotion_latest_doi(record)
|
|
24
|
+
return failure('doi not reserved') if @doi.nil?
|
|
25
|
+
return self if @doi.minted
|
|
26
|
+
|
|
27
|
+
return failure('publication metadata is incomplete') unless metadata_ready?
|
|
28
|
+
|
|
29
|
+
publish_at_datacite
|
|
30
|
+
self
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def success?
|
|
34
|
+
@error.nil? && @doi&.minted
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def publish_at_datacite
|
|
40
|
+
if send_to_datacite?
|
|
41
|
+
mds = ::Repo::Datacite::Mds.new
|
|
42
|
+
# DataCite requires the metadata to exist before the DOI can be minted.
|
|
43
|
+
metadata = mds.upload_metadata(BuildTemplateDoiXml.new(record, doi, current_user).call)
|
|
44
|
+
return failure(datacite_error('metadata upload', metadata)) unless metadata.is_a?(Net::HTTPSuccess)
|
|
45
|
+
|
|
46
|
+
response = mds.mint(doi.full_doi, TemplateDoiHelpers.template_url(record))
|
|
47
|
+
return failure(datacite_error('mint', response)) unless response.is_a?(Net::HTTPSuccess)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
ActiveRecord::Base.transaction do
|
|
51
|
+
doi.update!(minted: true, minted_at: Time.current)
|
|
52
|
+
stamp_release_on_record
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def datacite_error(step, response)
|
|
57
|
+
status = response.respond_to?(:code) ? response.code : 'unknown'
|
|
58
|
+
"datacite #{step} failed (status #{status})"
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Mirrors Publication#transition_from_doi_registering_to_registered!: only
|
|
62
|
+
# hit DataCite for real on test DOIs or in production publishing. In any
|
|
63
|
+
# other mode (e.g. dev/staging) skip the network call and just mark the
|
|
64
|
+
# DOI released locally.
|
|
65
|
+
def send_to_datacite?
|
|
66
|
+
ENV['DATACITE_MODE'] == 'test' || ENV['PUBLISH_MODE'] == 'production'
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def stamp_release_on_record
|
|
70
|
+
attrs = {}
|
|
71
|
+
attrs[:released_at] = Time.current if record.respond_to?(:released_at)
|
|
72
|
+
attrs[:released_by] = current_user.id if record.respond_to?(:released_by) && current_user
|
|
73
|
+
record.update!(attrs) if attrs.any?
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def metadata_ready?
|
|
77
|
+
publication = record.properties_template.is_a?(Hash) ? record.properties_template['publication'] : nil
|
|
78
|
+
return false unless publication.is_a?(Hash)
|
|
79
|
+
|
|
80
|
+
publication['title'].to_s.strip.present? &&
|
|
81
|
+
publication['description'].to_s.strip.present? &&
|
|
82
|
+
Array(publication['authors']).any? { |a| a.is_a?(Hash) && a['familyName'].to_s.strip.present? }
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def failure(message)
|
|
86
|
+
@error = message
|
|
87
|
+
self
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Labimotion
|
|
4
|
+
# Reserves a DataCite DOI for a LabIMotion template (ElementKlass /
|
|
5
|
+
# SegmentKlass / DatasetKlass). A DOI is published per major release
|
|
6
|
+
# (template X.0 -> DOI vX; later minor revisions X.y roll into v(X+1));
|
|
7
|
+
# sub-1.0 and unversioned templates map to the first DOI (v1). The existing
|
|
8
|
+
# DOI is returned unless the latest one has been released AND the template
|
|
9
|
+
# has since moved to a version that maps to a new DOI version.
|
|
10
|
+
class ReserveTemplateDoi
|
|
11
|
+
include TemplateDoiHelpers
|
|
12
|
+
|
|
13
|
+
attr_reader :record, :current_user, :doi, :error
|
|
14
|
+
|
|
15
|
+
def initialize(record, current_user)
|
|
16
|
+
@record = record
|
|
17
|
+
@current_user = current_user
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def call
|
|
21
|
+
return failure('record is required') if record.nil?
|
|
22
|
+
return failure('unauthorized') unless TemplateDoiHelpers.authorized?(record, current_user)
|
|
23
|
+
|
|
24
|
+
@doi = reserve
|
|
25
|
+
self
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def success?
|
|
29
|
+
@error.nil? && @doi.present?
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
# Returns the template's current DOI, or reserves one for a new version.
|
|
35
|
+
# A new DOI is only created when there is none yet, or once the latest DOI
|
|
36
|
+
# has been released and the template has moved to a new version. Otherwise
|
|
37
|
+
# the existing DOI is returned, so repeated reserves are idempotent and a
|
|
38
|
+
# released version stays read-only until the template is revised.
|
|
39
|
+
def reserve
|
|
40
|
+
latest = ::Doi.labimotion_latest_doi(record)
|
|
41
|
+
return latest if latest && !TemplateDoiHelpers.new_version_available?(record)
|
|
42
|
+
|
|
43
|
+
create_doi
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def create_doi
|
|
47
|
+
suffix = ::Doi.build_labimotion_suffix(record)
|
|
48
|
+
return failure('could not build a DOI suffix for this template') if suffix.blank?
|
|
49
|
+
|
|
50
|
+
# Set inchikey == suffix and version_count: 0 so Doi#align_suffix keeps
|
|
51
|
+
# the deterministic suffix verbatim (no /concept, no rebuild).
|
|
52
|
+
::Doi.create!(
|
|
53
|
+
doiable_id: record.id,
|
|
54
|
+
doiable_type: record.class.name,
|
|
55
|
+
inchikey: suffix,
|
|
56
|
+
suffix: suffix,
|
|
57
|
+
version_count: 0,
|
|
58
|
+
metadata: ::Doi.labimotion_doi_metadata(record)
|
|
59
|
+
)
|
|
60
|
+
rescue ActiveRecord::RecordInvalid => e
|
|
61
|
+
failure(e.message)
|
|
62
|
+
nil
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def failure(message)
|
|
66
|
+
@error = message
|
|
67
|
+
self
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Labimotion
|
|
4
|
+
# Shared helpers for LabIMotion template DOIs: the type <-> klass mapping,
|
|
5
|
+
# authorization, the public Template Hub URL and "is a new version reservable"
|
|
6
|
+
# check. Used by the template DOI usecases and APIs.
|
|
7
|
+
module TemplateDoiHelpers
|
|
8
|
+
KLASS_BY_TYPE = {
|
|
9
|
+
'element' => ::Labimotion::ElementKlass,
|
|
10
|
+
'segment' => ::Labimotion::SegmentKlass,
|
|
11
|
+
'dataset' => ::Labimotion::DatasetKlass
|
|
12
|
+
}.freeze
|
|
13
|
+
|
|
14
|
+
# Maps a template record to the `generic_admin` profile key that grants
|
|
15
|
+
# admin rights over it. Keys are plural, matching the LabIMotion gem's
|
|
16
|
+
# `authenticate_admin!` and `User#generic_admin`.
|
|
17
|
+
GENERIC_ADMIN_KEY = {
|
|
18
|
+
::Labimotion::ElementKlass => 'elements',
|
|
19
|
+
::Labimotion::SegmentKlass => 'segments',
|
|
20
|
+
::Labimotion::DatasetKlass => 'datasets'
|
|
21
|
+
}.freeze
|
|
22
|
+
|
|
23
|
+
module_function
|
|
24
|
+
|
|
25
|
+
# True once the template's latest DOI has been released (minted) and the
|
|
26
|
+
# template has since moved to a new major version — i.e. a DOI for the new
|
|
27
|
+
# version may now be reserved. False while there is no DOI, the latest is
|
|
28
|
+
# still unreleased, or the DOI version is unchanged (released = read-only,
|
|
29
|
+
# and minor template revisions keep the same DOI).
|
|
30
|
+
def new_version_available?(record)
|
|
31
|
+
latest = ::Doi.labimotion_latest_doi(record)
|
|
32
|
+
return false unless latest&.minted
|
|
33
|
+
|
|
34
|
+
::Doi.labimotion_doi_version(latest) != ::Doi.labimotion_version_segment(record)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def klass_type_for(record)
|
|
38
|
+
case record
|
|
39
|
+
when ::Labimotion::ElementKlass then 'element'
|
|
40
|
+
when ::Labimotion::SegmentKlass then 'segment'
|
|
41
|
+
when ::Labimotion::DatasetKlass then 'dataset'
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Public URL that opens the template in the LabIMotion Template Hub's
|
|
46
|
+
# "Find a Template" page (the Finder), preselecting the template type, the
|
|
47
|
+
# template and its release version:
|
|
48
|
+
# /home/genericHub?type=finder&kind=<type>&id=<identifier|uuid>&version=<v>
|
|
49
|
+
# Used as the DOI's DataCite landing page and surfaced in the UI.
|
|
50
|
+
def template_url(record)
|
|
51
|
+
type = klass_type_for(record)
|
|
52
|
+
host = ENV['REPO_HOST'].presence || ENV['DOI_DOMAIN_TEST'].presence ||
|
|
53
|
+
ENV['DOI_DOMAIN'].presence || 'https://www.chemotion-repository.net'
|
|
54
|
+
record_id = record.try(:identifier).presence || record.try(:uuid).presence || record.id
|
|
55
|
+
query = "type=finder&kind=#{type}&id=#{record_id}"
|
|
56
|
+
version = record.try(:version)
|
|
57
|
+
query += "&version=#{version}" if version.present?
|
|
58
|
+
"#{host.chomp('/')}/home/genericHub?#{query}"
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# A user may manage a template's DOI iff they hold the LabIMotion
|
|
62
|
+
# `generic_admin` right for that template kind — the same gate the gem's
|
|
63
|
+
# `authenticate_admin!` and the admin UI use. `admin_ids`/`created_by` do
|
|
64
|
+
# NOT apply: segment templates carry neither, so they were always denied.
|
|
65
|
+
def authorized?(record, user)
|
|
66
|
+
return false unless record && user
|
|
67
|
+
|
|
68
|
+
key = GENERIC_ADMIN_KEY[record.class]
|
|
69
|
+
return false unless key
|
|
70
|
+
|
|
71
|
+
admin = user.respond_to?(:generic_admin) ? user.generic_admin : nil
|
|
72
|
+
admin.is_a?(Hash) && !!admin[key]
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Labimotion
|
|
4
|
+
# Merges a normalized "publication" sub-object into the template's
|
|
5
|
+
# properties_template jsonb. Keeps the rest of the jsonb untouched so the
|
|
6
|
+
# labimotion gem's runtime shape stays intact.
|
|
7
|
+
class UpdateTemplatePublicationMetadata
|
|
8
|
+
include TemplateDoiHelpers
|
|
9
|
+
|
|
10
|
+
ALLOWED_KEYS = %w[title description authors license].freeze
|
|
11
|
+
|
|
12
|
+
attr_reader :record, :current_user, :publication, :error
|
|
13
|
+
|
|
14
|
+
def initialize(record, current_user, publication)
|
|
15
|
+
@record = record
|
|
16
|
+
@current_user = current_user
|
|
17
|
+
@publication = publication
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def call
|
|
21
|
+
return failure('record is required') if record.nil?
|
|
22
|
+
return failure('unauthorized') unless TemplateDoiHelpers.authorized?(record, current_user)
|
|
23
|
+
return failure('publication payload must be a hash') unless publication.is_a?(Hash)
|
|
24
|
+
|
|
25
|
+
merged = normalized_publication
|
|
26
|
+
props = (record.properties_template || {}).dup
|
|
27
|
+
props['publication'] = merged
|
|
28
|
+
record.update!(properties_template: props)
|
|
29
|
+
@publication = merged
|
|
30
|
+
self
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def success?
|
|
34
|
+
@error.nil?
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def normalized_publication
|
|
40
|
+
slice = publication.stringify_keys.slice(*ALLOWED_KEYS)
|
|
41
|
+
slice['authors'] = Array(slice['authors']).filter_map { |a| normalize_author(a) }
|
|
42
|
+
slice
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def normalize_author(author)
|
|
46
|
+
return nil unless author.is_a?(Hash)
|
|
47
|
+
|
|
48
|
+
h = author.stringify_keys.slice('givenName', 'familyName', 'orcid', 'affiliation', 'affiliation_id')
|
|
49
|
+
return nil if h.values_at('givenName', 'familyName').all? { |v| v.to_s.strip.empty? }
|
|
50
|
+
|
|
51
|
+
h
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def failure(message)
|
|
55
|
+
@error = message
|
|
56
|
+
self
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
data/lib/labimotion/version.rb
CHANGED
data/lib/labimotion.rb
CHANGED
|
@@ -71,6 +71,13 @@ module Labimotion
|
|
|
71
71
|
autoload :RepositoryHelpers, 'labimotion/helpers/repository_helpers'
|
|
72
72
|
autoload :VocabularyHelpers, 'labimotion/helpers/vocabulary_helpers'
|
|
73
73
|
|
|
74
|
+
######## Usecases
|
|
75
|
+
autoload :TemplateDoiHelpers, 'labimotion/usecases/template_doi_helpers'
|
|
76
|
+
autoload :BuildTemplateDoiXml, 'labimotion/usecases/build_template_doi_xml'
|
|
77
|
+
autoload :ReserveTemplateDoi, 'labimotion/usecases/reserve_template_doi'
|
|
78
|
+
autoload :ReleaseTemplateDoi, 'labimotion/usecases/release_template_doi'
|
|
79
|
+
autoload :UpdateTemplatePublicationMetadata, 'labimotion/usecases/update_template_publication_metadata'
|
|
80
|
+
|
|
74
81
|
######## Libs
|
|
75
82
|
autoload :Converter, 'labimotion/libs/converter'
|
|
76
83
|
autoload :DatasetBuilder, 'labimotion/libs/dataset_builder'
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: labimotion
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.2.0.
|
|
4
|
+
version: 2.2.0.rc14
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Chia-Lin Lin
|
|
@@ -164,6 +164,11 @@ files:
|
|
|
164
164
|
- lib/labimotion/models/template_submission.rb
|
|
165
165
|
- lib/labimotion/models/vocabulary.rb
|
|
166
166
|
- lib/labimotion/models/wellplate.rb
|
|
167
|
+
- lib/labimotion/usecases/build_template_doi_xml.rb
|
|
168
|
+
- lib/labimotion/usecases/release_template_doi.rb
|
|
169
|
+
- lib/labimotion/usecases/reserve_template_doi.rb
|
|
170
|
+
- lib/labimotion/usecases/template_doi_helpers.rb
|
|
171
|
+
- lib/labimotion/usecases/update_template_publication_metadata.rb
|
|
167
172
|
- lib/labimotion/utils/con_state.rb
|
|
168
173
|
- lib/labimotion/utils/export_utils.rb
|
|
169
174
|
- lib/labimotion/utils/field_type.rb
|