labimotion 2.2.0.rc10 → 2.2.0.rc11
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_api.rb +2 -0
- data/lib/labimotion/apis/labimotion_doi_api.rb +170 -0
- data/lib/labimotion/apis/labimotion_template_browse_api.rb +120 -0
- data/lib/labimotion/entities/labimotion_template_doi_entity.rb +30 -0
- data/lib/labimotion/version.rb +1 -1
- data/lib/labimotion.rb +3 -0
- metadata +5 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 491f3e3075c83954b5e49caeb8b091d1aba68ddf0135fe82ddbaf6aeb4b244e9
|
|
4
|
+
data.tar.gz: ae5dc4a93ecb31559feff1060f8fb449aa3e618f86890ea25324410226f4d166
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ce1f326748ee1ae7c9a71f97f43b2eef7d3273c28c9d7d7d3e87516cfcef58002927563dedbaad66ca87f6c396fd462cab6f827eb95d052cdf4f2703bfe8e307
|
|
7
|
+
data.tar.gz: 958083e170c64e0c378b38ecf9996343a88dfa6756617d0a7d6d9e548d3dbefc3b53c00eda2ec8412fd6da59ca3b959b7d0fa20f67e32a559fa74da9d01e695b
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Labimotion
|
|
4
|
+
# DOI lifecycle (reserve + release) and publication metadata for
|
|
5
|
+
# LabIMotion templates: ElementKlass / SegmentKlass / DatasetKlass.
|
|
6
|
+
class LabimotionDoiAPI < Grape::API
|
|
7
|
+
# rubocop:disable Metrics/BlockLength
|
|
8
|
+
# Grape route and helper DSL blocks are necessarily large.
|
|
9
|
+
KLASS_TYPES = ::Usecases::Labimotion::TemplateDoiHelpers::KLASS_BY_TYPE.keys.freeze
|
|
10
|
+
|
|
11
|
+
helpers do
|
|
12
|
+
def resolve_klass!(type, id)
|
|
13
|
+
model = ::Usecases::Labimotion::TemplateDoiHelpers::KLASS_BY_TYPE[type]
|
|
14
|
+
error!('unknown template type', 404) unless model
|
|
15
|
+
|
|
16
|
+
record = model.find_by(id: id)
|
|
17
|
+
error!('template not found', 404) unless record
|
|
18
|
+
|
|
19
|
+
record
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def render_template_doi(record)
|
|
23
|
+
type = ::Usecases::Labimotion::TemplateDoiHelpers.klass_type_for(record)
|
|
24
|
+
dois = ::Doi.labimotion_dois(record)
|
|
25
|
+
# `doi` is the current (most recently reserved) DOI; `released_doi` is the
|
|
26
|
+
# latest released (minted) one. They differ once a new version has been
|
|
27
|
+
# reserved on top of a released one, so the modal can show both at once.
|
|
28
|
+
doi = dois.last
|
|
29
|
+
released_doi = dois.reverse.find(&:minted)
|
|
30
|
+
Labimotion::LabimotionTemplateDoiEntity.represent(
|
|
31
|
+
type: type,
|
|
32
|
+
klass_id: record.id,
|
|
33
|
+
doi: doi,
|
|
34
|
+
released_doi_version: released_doi && ::Doi.labimotion_doi_version(released_doi),
|
|
35
|
+
publication: (record.properties_template || {})['publication'],
|
|
36
|
+
released_at: record.released_at,
|
|
37
|
+
released_by: record.released_by,
|
|
38
|
+
new_version_available: ::Usecases::Labimotion::TemplateDoiHelpers.new_version_available?(record),
|
|
39
|
+
next_doi_version: ::Doi.labimotion_version_segment(record),
|
|
40
|
+
template_url: ::Usecases::Labimotion::TemplateDoiHelpers.template_url(record)
|
|
41
|
+
)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Public, read-only view of one released DOI version.
|
|
45
|
+
def released_doi_version(doi)
|
|
46
|
+
full = doi.full_doi
|
|
47
|
+
{
|
|
48
|
+
doi: full,
|
|
49
|
+
doi_url: "https://dx.doi.org/#{full}",
|
|
50
|
+
version: ::Doi.labimotion_doi_version(doi),
|
|
51
|
+
minted_at: doi.minted_at
|
|
52
|
+
}
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Public payload for a template that has at least one released DOI: the
|
|
56
|
+
# latest released DOI promoted to the top level, plus its publication
|
|
57
|
+
# metadata, hub deep-link and every released version. `record_id` matches
|
|
58
|
+
# the hub grid's row id (identifier || uuid) so the frontend can merge it.
|
|
59
|
+
def released_template_doi(record, dois)
|
|
60
|
+
versions = dois.sort_by(&:id).map { |doi| released_doi_version(doi) }
|
|
61
|
+
publication = (record.properties_template || {})['publication']
|
|
62
|
+
publication = publication.is_a?(Hash) ? publication.slice('title', 'description', 'authors', 'license') : {}
|
|
63
|
+
versions.last.merge(
|
|
64
|
+
record_id: record.try(:identifier).presence || record.try(:uuid).presence || record.id,
|
|
65
|
+
identifier: record.try(:identifier),
|
|
66
|
+
uuid: record.try(:uuid),
|
|
67
|
+
label: record.try(:label),
|
|
68
|
+
template_version: record.try(:version),
|
|
69
|
+
next_doi_version: ::Doi.labimotion_version_segment(record),
|
|
70
|
+
template_url: ::Usecases::Labimotion::TemplateDoiHelpers.template_url(record),
|
|
71
|
+
publication: publication,
|
|
72
|
+
versions: versions
|
|
73
|
+
)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
namespace :labimotion_doi do
|
|
78
|
+
# Public, anonymous read-only DOI info for the LabIMotion Template Hub.
|
|
79
|
+
# Whitelisted in RepoAPI::PUBLIC_URLS so it bypasses authenticate!; only
|
|
80
|
+
# released (minted) DOIs and their public publication metadata are exposed.
|
|
81
|
+
namespace :public do
|
|
82
|
+
route_param :type, type: String, values: KLASS_TYPES do
|
|
83
|
+
desc 'Released DOIs + publication metadata for every released template of a type'
|
|
84
|
+
get :released do
|
|
85
|
+
model = ::Usecases::Labimotion::TemplateDoiHelpers::KLASS_BY_TYPE[params[:type]]
|
|
86
|
+
grouped = ::Doi.where(doiable_type: model.name).where.not(minted_at: nil)
|
|
87
|
+
.order(:id).group_by(&:doiable_id)
|
|
88
|
+
records = model.where(id: grouped.keys).index_by(&:id)
|
|
89
|
+
released = grouped.filter_map do |klass_id, dois|
|
|
90
|
+
record = records[klass_id]
|
|
91
|
+
record && released_template_doi(record, dois)
|
|
92
|
+
end
|
|
93
|
+
{ released_dois: released }
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
route_param :type, type: String, values: KLASS_TYPES do
|
|
99
|
+
desc "DOI state (reserved / released) per template of a type, for the designer grid's DOI icon"
|
|
100
|
+
get :states do
|
|
101
|
+
model = ::Usecases::Labimotion::TemplateDoiHelpers::KLASS_BY_TYPE[params[:type]]
|
|
102
|
+
grouped = ::Doi.where(doiable_type: model.name).order(:id).group_by(&:doiable_id)
|
|
103
|
+
states = grouped.transform_values do |list|
|
|
104
|
+
released = list.last.minted == true
|
|
105
|
+
{ reserved: !released, released: released }
|
|
106
|
+
end
|
|
107
|
+
{ states: states }
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
route_param :id, type: Integer do
|
|
111
|
+
desc 'Returns the current DOI/publication state for a template'
|
|
112
|
+
get do
|
|
113
|
+
record = resolve_klass!(params[:type], params[:id])
|
|
114
|
+
render_template_doi(record)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
desc "Returns the DataCite metadata XML for every one of the template's DOI versions"
|
|
118
|
+
get :metadata_xml do
|
|
119
|
+
record = resolve_klass!(params[:type], params[:id])
|
|
120
|
+
dois = ::Doi.labimotion_dois(record)
|
|
121
|
+
error!('no DOI reserved', 404) if dois.empty?
|
|
122
|
+
|
|
123
|
+
versions = dois.map do |doi|
|
|
124
|
+
{
|
|
125
|
+
version: ::Doi.labimotion_doi_version(doi),
|
|
126
|
+
full_doi: doi.full_doi,
|
|
127
|
+
minted: doi.minted == true,
|
|
128
|
+
xml: ::Usecases::Labimotion::BuildTemplateDoiXml.new(record, doi, current_user).call
|
|
129
|
+
}
|
|
130
|
+
end
|
|
131
|
+
{ versions: versions }
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
desc 'Updates the publication metadata of a template'
|
|
135
|
+
params do
|
|
136
|
+
requires :publication, type: Hash
|
|
137
|
+
end
|
|
138
|
+
put :publication_metadata do
|
|
139
|
+
record = resolve_klass!(params[:type], params[:id])
|
|
140
|
+
uc = ::Usecases::Labimotion::UpdateTemplatePublicationMetadata.new(
|
|
141
|
+
record, current_user, params[:publication]
|
|
142
|
+
).call
|
|
143
|
+
error!(uc.error, 422) unless uc.success?
|
|
144
|
+
|
|
145
|
+
render_template_doi(record.reload)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
desc 'Reserves a DataCite DOI for a template'
|
|
149
|
+
post :reserve_doi do
|
|
150
|
+
record = resolve_klass!(params[:type], params[:id])
|
|
151
|
+
uc = ::Usecases::Labimotion::ReserveTemplateDoi.new(record, current_user).call
|
|
152
|
+
error!(uc.error, 422) unless uc.success?
|
|
153
|
+
|
|
154
|
+
render_template_doi(record.reload)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
desc 'Releases (mints) the reserved DOI for a template'
|
|
158
|
+
post :release_doi do
|
|
159
|
+
record = resolve_klass!(params[:type], params[:id])
|
|
160
|
+
uc = ::Usecases::Labimotion::ReleaseTemplateDoi.new(record, current_user).call
|
|
161
|
+
error!(uc.error, 422) unless uc.success?
|
|
162
|
+
|
|
163
|
+
render_template_doi(record.reload)
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
# rubocop:enable Metrics/BlockLength
|
|
169
|
+
end
|
|
170
|
+
end
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Labimotion
|
|
4
|
+
# Public, read-only browsing of released LabIMotion templates for the Template
|
|
5
|
+
# Hub "Find a Template" tab: pick a type, a template, then a release version.
|
|
6
|
+
# Returns every released version (revision) of one template, each with its own
|
|
7
|
+
# properties_release so the frontend can render the example inline.
|
|
8
|
+
# Whitelisted in RepoAPI::PUBLIC_URLS so anonymous hub visitors can use it.
|
|
9
|
+
class LabimotionTemplateBrowseAPI < Grape::API
|
|
10
|
+
KLASS_BY_TYPE = ::Usecases::Labimotion::TemplateDoiHelpers::KLASS_BY_TYPE
|
|
11
|
+
KLASS_TYPES = KLASS_BY_TYPE.keys.freeze
|
|
12
|
+
# type => [revision model, foreign key to the klass]
|
|
13
|
+
REVISION_BY_TYPE = {
|
|
14
|
+
'element' => [::Labimotion::ElementKlassesRevision, :element_klass_id],
|
|
15
|
+
'segment' => [::Labimotion::SegmentKlassesRevision, :segment_klass_id],
|
|
16
|
+
'dataset' => [::Labimotion::DatasetKlassesRevision, :dataset_klass_id]
|
|
17
|
+
}.freeze
|
|
18
|
+
|
|
19
|
+
# rubocop:disable Metrics/BlockLength
|
|
20
|
+
# The helpers block is large due to the many template/version/DOI shaping helpers.
|
|
21
|
+
helpers do
|
|
22
|
+
def find_template(model, identifier)
|
|
23
|
+
model.find_by(identifier: identifier) || model.find_by(uuid: identifier)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Numeric sort key for a "major.minor" version string.
|
|
27
|
+
def version_sort_key(version)
|
|
28
|
+
version.to_s.split('.').map(&:to_i)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def template_summary(record, type)
|
|
32
|
+
element_klass = record.try(:element_klass)
|
|
33
|
+
{
|
|
34
|
+
type: type,
|
|
35
|
+
identifier: record.try(:identifier),
|
|
36
|
+
uuid: record.try(:uuid),
|
|
37
|
+
name: record.try(:name),
|
|
38
|
+
label: record.try(:label),
|
|
39
|
+
desc: record.try(:desc),
|
|
40
|
+
klass_prefix: record.try(:klass_prefix),
|
|
41
|
+
icon_name: record.try(:icon_name),
|
|
42
|
+
current_version: record.try(:version),
|
|
43
|
+
element_klass: element_klass && { label: element_klass.label, icon_name: element_klass.icon_name }
|
|
44
|
+
}
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def version_entry(version, released_at, uuid, properties_release)
|
|
48
|
+
{ version: version, released_at: released_at, uuid: uuid, properties_release: properties_release }
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# The current klass's released version is appended only when no revision
|
|
52
|
+
# already captures it, so the latest is always selectable.
|
|
53
|
+
def append_current?(record, versions)
|
|
54
|
+
record.try(:released_at).present? && versions.none? { |v| v[:version] == record.try(:version) }
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Drops blank-version entries and orders newest-first.
|
|
58
|
+
def sort_versions(versions)
|
|
59
|
+
versions.reject { |v| v[:version].to_s.strip.empty? }
|
|
60
|
+
.sort_by { |v| version_sort_key(v[:version]) }.reverse
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# All released versions of a template, newest first. Built from the
|
|
64
|
+
# revision snapshots, plus the current klass (see append_current?).
|
|
65
|
+
def released_versions(record, type)
|
|
66
|
+
rev_model, foreign_key = REVISION_BY_TYPE[type]
|
|
67
|
+
scope = rev_model.where(foreign_key => record.id).where.not(released_at: nil)
|
|
68
|
+
versions = scope.map { |rev| version_entry(rev.version, rev.released_at, rev.uuid, rev.properties_release) }
|
|
69
|
+
if append_current?(record, versions)
|
|
70
|
+
versions << version_entry(record.try(:version), record.released_at,
|
|
71
|
+
record.try(:uuid), record.try(:properties_release))
|
|
72
|
+
end
|
|
73
|
+
sort_versions(versions)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# The template's DOI publication metadata (shown alongside the DOI).
|
|
77
|
+
def template_publication(record)
|
|
78
|
+
pub = (record.properties_template || {})['publication']
|
|
79
|
+
pub.is_a?(Hash) ? pub.slice('title', 'description', 'authors', 'license') : {}
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Released (minted) DOIs of the template, each tagged with its DOI version
|
|
83
|
+
# and its DataCite (kernel-4) metadata XML (the downloadable DOI metadata).
|
|
84
|
+
# The frontend maps a chosen template version to the applicable DOI version
|
|
85
|
+
# and shows/downloads the matching entry.
|
|
86
|
+
def released_dois(record)
|
|
87
|
+
::Doi.labimotion_dois(record).select(&:minted).map do |doi|
|
|
88
|
+
full = doi.full_doi
|
|
89
|
+
{ version: ::Doi.labimotion_doi_version(doi), doi: full,
|
|
90
|
+
doi_url: "https://dx.doi.org/#{full}", minted_at: doi.minted_at,
|
|
91
|
+
xml: ::Usecases::Labimotion::BuildTemplateDoiXml.new(record, doi).call }
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
# rubocop:enable Metrics/BlockLength
|
|
96
|
+
|
|
97
|
+
namespace :labimotion_template_browse do
|
|
98
|
+
route_param :type, type: String, values: KLASS_TYPES do
|
|
99
|
+
desc 'Released versions (revisions) of one template, each with properties_release'
|
|
100
|
+
params do
|
|
101
|
+
requires :identifier, type: String, desc: 'template identifier or uuid'
|
|
102
|
+
end
|
|
103
|
+
get :versions do
|
|
104
|
+
model = KLASS_BY_TYPE[params[:type]]
|
|
105
|
+
error!('unknown template type', 404) unless model
|
|
106
|
+
|
|
107
|
+
record = find_template(model, params[:identifier])
|
|
108
|
+
error!('template not found', 404) unless record
|
|
109
|
+
|
|
110
|
+
{
|
|
111
|
+
template: template_summary(record, params[:type]),
|
|
112
|
+
versions: released_versions(record, params[:type]),
|
|
113
|
+
dois: released_dois(record),
|
|
114
|
+
publication: template_publication(record)
|
|
115
|
+
}
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Labimotion
|
|
4
|
+
# Serializes the DOI + publication state for a LabIMotion template
|
|
5
|
+
# (ElementKlass / SegmentKlass / DatasetKlass). Built ad-hoc by
|
|
6
|
+
# Labimotion::LabimotionDoiAPI; not a direct AR record entity.
|
|
7
|
+
class LabimotionTemplateDoiEntity < Grape::Entity
|
|
8
|
+
expose :type
|
|
9
|
+
expose :klass_id
|
|
10
|
+
expose :doi do |obj|
|
|
11
|
+
next nil unless obj[:doi]
|
|
12
|
+
|
|
13
|
+
{
|
|
14
|
+
id: obj[:doi].id,
|
|
15
|
+
suffix: obj[:doi].suffix,
|
|
16
|
+
full_doi: obj[:doi].full_doi,
|
|
17
|
+
version: ::Doi.labimotion_doi_version(obj[:doi]),
|
|
18
|
+
minted: obj[:doi].minted == true,
|
|
19
|
+
minted_at: obj[:doi].minted_at
|
|
20
|
+
}
|
|
21
|
+
end
|
|
22
|
+
expose :released_doi_version
|
|
23
|
+
expose :publication
|
|
24
|
+
expose :released_at
|
|
25
|
+
expose :released_by
|
|
26
|
+
expose :new_version_available
|
|
27
|
+
expose :next_doi_version
|
|
28
|
+
expose :template_url
|
|
29
|
+
end
|
|
30
|
+
end
|
data/lib/labimotion/version.rb
CHANGED
data/lib/labimotion.rb
CHANGED
|
@@ -31,6 +31,8 @@ module Labimotion
|
|
|
31
31
|
autoload :MttAPI, 'labimotion/apis/mtt_api'
|
|
32
32
|
autoload :DoseRespRequestAPI, 'labimotion/apis/dose_resp_request_api'
|
|
33
33
|
autoload :ElementVariationAPI, 'labimotion/apis/element_variation_api'
|
|
34
|
+
autoload :LabimotionDoiAPI, 'labimotion/apis/labimotion_doi_api'
|
|
35
|
+
autoload :LabimotionTemplateBrowseAPI, 'labimotion/apis/labimotion_template_browse_api'
|
|
34
36
|
|
|
35
37
|
######## Entities
|
|
36
38
|
autoload :PropertiesEntity, 'labimotion/entities/properties_entity'
|
|
@@ -55,6 +57,7 @@ module Labimotion
|
|
|
55
57
|
autoload :VocabularyEntity, 'labimotion/entities/vocabulary_entity'
|
|
56
58
|
autoload :UserEntity, 'labimotion/entities/user_entity'
|
|
57
59
|
autoload :ElementVariationEntity, 'labimotion/entities/element_variation_entity'
|
|
60
|
+
autoload :LabimotionTemplateDoiEntity, 'labimotion/entities/labimotion_template_doi_entity'
|
|
58
61
|
|
|
59
62
|
######## Helpers
|
|
60
63
|
autoload :GenericHelpers, 'labimotion/helpers/generic_helpers'
|
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.rc11
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Chia-Lin Lin
|
|
@@ -9,7 +9,7 @@ authors:
|
|
|
9
9
|
autorequire:
|
|
10
10
|
bindir: bin
|
|
11
11
|
cert_chain: []
|
|
12
|
-
date: 2026-06-
|
|
12
|
+
date: 2026-06-22 00:00:00.000000000 Z
|
|
13
13
|
dependencies:
|
|
14
14
|
- !ruby/object:Gem::Dependency
|
|
15
15
|
name: caxlsx
|
|
@@ -65,7 +65,9 @@ files:
|
|
|
65
65
|
- lib/labimotion/apis/generic_element_api.rb
|
|
66
66
|
- lib/labimotion/apis/generic_klass_api.rb
|
|
67
67
|
- lib/labimotion/apis/labimotion_api.rb
|
|
68
|
+
- lib/labimotion/apis/labimotion_doi_api.rb
|
|
68
69
|
- lib/labimotion/apis/labimotion_hub_api.rb
|
|
70
|
+
- lib/labimotion/apis/labimotion_template_browse_api.rb
|
|
69
71
|
- lib/labimotion/apis/mtt_api.rb
|
|
70
72
|
- lib/labimotion/apis/segment_api.rb
|
|
71
73
|
- lib/labimotion/apis/standard_api.rb
|
|
@@ -88,6 +90,7 @@ files:
|
|
|
88
90
|
- lib/labimotion/entities/generic_klass_entity.rb
|
|
89
91
|
- lib/labimotion/entities/generic_public_entity.rb
|
|
90
92
|
- lib/labimotion/entities/klass_revision_entity.rb
|
|
93
|
+
- lib/labimotion/entities/labimotion_template_doi_entity.rb
|
|
91
94
|
- lib/labimotion/entities/properties_entity.rb
|
|
92
95
|
- lib/labimotion/entities/segment_entity.rb
|
|
93
96
|
- lib/labimotion/entities/segment_klass_entity.rb
|