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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 727f612dabb886b6189d747d61c391b6ccae83ebd2279a3961d38b652449b7e8
4
- data.tar.gz: 9d94f4337c5d28f44cba194e45bac135a715a0d193254f5792049b688801d734
3
+ metadata.gz: 491f3e3075c83954b5e49caeb8b091d1aba68ddf0135fe82ddbaf6aeb4b244e9
4
+ data.tar.gz: ae5dc4a93ecb31559feff1060f8fb449aa3e618f86890ea25324410226f4d166
5
5
  SHA512:
6
- metadata.gz: 64a75edfdc0efee53dca7bf8c03aa2747fc9cf0672723056e97a80989ca23ed03fb32700af3f38d6ebd80d757398250868bfdceb9458288ea77e4e78d79b8e8a
7
- data.tar.gz: 1e594266697dd026612ba27d6f7df1566f7f8dc696c8d18ce318a35ccdfc9eaf3c6b0e0467daf9e0601253f0bcc4d78c5bbb7a624319300b6b9a091db1f6de06
6
+ metadata.gz: ce1f326748ee1ae7c9a71f97f43b2eef7d3273c28c9d7d7d3e87516cfcef58002927563dedbaad66ca87f6c396fd462cab6f827eb95d052cdf4f2703bfe8e307
7
+ data.tar.gz: 958083e170c64e0c378b38ecf9996343a88dfa6756617d0a7d6d9e548d3dbefc3b53c00eda2ec8412fd6da59ca3b959b7d0fa20f67e32a559fa74da9d01e695b
@@ -16,5 +16,7 @@ module Labimotion
16
16
  mount Labimotion::MttAPI
17
17
  mount Labimotion::DoseRespRequestAPI
18
18
  mount Labimotion::ElementVariationAPI
19
+ mount Labimotion::LabimotionDoiAPI
20
+ mount Labimotion::LabimotionTemplateBrowseAPI
19
21
  end
20
22
  end
@@ -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
@@ -2,5 +2,5 @@
2
2
 
3
3
  ## Labimotion Version
4
4
  module Labimotion
5
- VERSION = '2.2.0.rc10'
5
+ VERSION = '2.2.0.rc11'
6
6
  end
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.rc10
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-19 00:00:00.000000000 Z
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