labimotion 2.2.0.rc8 → 2.2.0.rc10
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/CHANGELOG.md +117 -0
- data/LICENSE +661 -0
- data/README.md +68 -0
- data/lib/labimotion/apis/dose_resp_request_api.rb +241 -0
- data/lib/labimotion/apis/element_variation_api.rb +45 -0
- data/lib/labimotion/apis/generic_element_api.rb +6 -3
- data/lib/labimotion/apis/labimotion_api.rb +4 -0
- data/lib/labimotion/apis/mtt_api.rb +238 -0
- data/lib/labimotion/apis/user_api.rb +56 -0
- data/lib/labimotion/entities/element_entity.rb +1 -0
- data/lib/labimotion/entities/element_variation_entity.rb +29 -0
- data/lib/labimotion/entities/user_entity.rb +9 -0
- data/lib/labimotion/helpers/element_helpers.rb +1 -1
- data/lib/labimotion/helpers/generic_helpers.rb +1 -1
- data/lib/labimotion/helpers/mtt_helpers.rb +428 -0
- data/lib/labimotion/helpers/param_helpers.rb +6 -0
- data/lib/labimotion/libs/export_element.rb +245 -19
- data/lib/labimotion/models/dose_resp_output.rb +27 -0
- data/lib/labimotion/models/dose_resp_request.rb +93 -0
- data/lib/labimotion/models/element.rb +6 -0
- data/lib/labimotion/models/element_variation.rb +21 -0
- data/lib/labimotion/version.rb +1 -1
- data/lib/labimotion.rb +10 -1
- metadata +15 -2
data/README.md
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# LabIMotion [](https://doi.org/10.5281/zenodo.8305411)
|
|
2
|
+
|
|
3
|
+
LabIMotion is a software that offers the flexibility to design new modules tailored to the specific needs of the scientists.
|
|
4
|
+
Generic elements, segments, and datasets are organized using Components, which are introduced as layers and fields. Each generic element, segment, or dataset can include multiple layers, with the potential for multiple fields within each layer. This hierarchical structure enables a flexible and comprehensive organization of data and information.
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+

|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
## Version 1.0.18 of LabIMotion, featuring:
|
|
12
|
+
|
|
13
|
+
* Generic Designer
|
|
14
|
+
* Workflow of Generic Element
|
|
15
|
+
* Repetitation of layers
|
|
16
|
+
* Drag Element to Element
|
|
17
|
+
* Dataset Metadata
|
|
18
|
+
* LabIMotion Template Hub Synchronization [**[LabIMotion Template Hub]**]
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
This repository contains a backend service for the LabIMotion. It is written in **[Ruby]**.
|
|
23
|
+
|
|
24
|
+
### Community
|
|
25
|
+
|
|
26
|
+
* [GitHub discussions](https://github.com/labimotion/labimotion/discussions)
|
|
27
|
+
|
|
28
|
+
### Code
|
|
29
|
+
|
|
30
|
+
* [GitHub code](https://github.com/labimotion/labimotion) and [bug tracker](https://github.com/labimotion/labimotion/issues)
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
## Documentation
|
|
36
|
+
|
|
37
|
+
Documentation for users **⸢ [Documentation] ⸥**
|
|
38
|
+
|
|
39
|
+
Documentation for developers **⸢ [Technical Documentation] ⸥**
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## License
|
|
44
|
+
|
|
45
|
+
Code released under the [AGPL-3.0 License]([https://www.gnu.org/licenses/agpl-3.0.txt](https://www.gnu.org/licenses/agpl-3.0.txt)).
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Acknowledgments
|
|
50
|
+
|
|
51
|
+
This project has been funded by the **[DFG]**.
|
|
52
|
+
|
|
53
|
+
[![DFG Logo]][DFG]
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
Funded by the [Deutsche Forschungsgemeinschaft (DFG, German Research Foundation)](https://www.dfg.de/) under the [National Research Data Infrastructure – NFDI4Chem](https://nfdi4chem.de/) – Projektnummer **441958208** since 2020.
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
<!----------------------------------------------------------------------------->
|
|
61
|
+
[Documentation]: https://www.chemotion.net/docs/labimotion/
|
|
62
|
+
[Technical Documentation]: https://www.rubydoc.info/gems/labimotion
|
|
63
|
+
[DFG]: https://www.dfg.de/en/
|
|
64
|
+
[DFG Logo]: https://www.dfg.de/zentralablage/bilder/service/logos_corporate_design/logo_negativ_267.png
|
|
65
|
+
[Nicole Jung]: mailto:nicole.jung@kit.edu
|
|
66
|
+
[Karlsruhe Institute of Technology]: https://www.kit.edu/english/
|
|
67
|
+
[Ruby]: https://www.ruby-lang.org/
|
|
68
|
+
[LabIMotion Template Hub]: https://www.chemotion-repository.net/home/genericHub
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'labimotion/version'
|
|
4
|
+
|
|
5
|
+
module Labimotion
|
|
6
|
+
# Dose Response Request API
|
|
7
|
+
class DoseRespRequestAPI < Grape::API
|
|
8
|
+
helpers Labimotion::ParamHelpers
|
|
9
|
+
|
|
10
|
+
resource :dose_resp_requests do
|
|
11
|
+
desc 'Get all dose response requests for current user'
|
|
12
|
+
params do
|
|
13
|
+
optional :element_id, type: Integer, desc: 'Filter by element ID'
|
|
14
|
+
optional :state, type: Integer, desc: 'Filter by state (-1, 0, 1, 2)'
|
|
15
|
+
optional :page, type: Integer, desc: 'Page number', default: 1
|
|
16
|
+
optional :per_page, type: Integer, desc: 'Items per page', default: 20
|
|
17
|
+
end
|
|
18
|
+
get do
|
|
19
|
+
requests = Labimotion::DoseRespRequest.where(created_by: current_user.id)
|
|
20
|
+
|
|
21
|
+
# Apply filters
|
|
22
|
+
requests = requests.where(element_id: params[:element_id]) if params[:element_id]
|
|
23
|
+
requests = requests.where(state: params[:state]) if params[:state]
|
|
24
|
+
|
|
25
|
+
# Pagination
|
|
26
|
+
page = params[:page] || 1
|
|
27
|
+
per_page = [params[:per_page] || 20, 100].min # Max 100 per page
|
|
28
|
+
|
|
29
|
+
total = requests.count
|
|
30
|
+
requests = requests.order(created_at: :desc)
|
|
31
|
+
.offset((page - 1) * per_page)
|
|
32
|
+
.limit(per_page)
|
|
33
|
+
|
|
34
|
+
{
|
|
35
|
+
requests: requests.map do |req|
|
|
36
|
+
{
|
|
37
|
+
id: req.id,
|
|
38
|
+
request_id: req.request_id,
|
|
39
|
+
element_id: req.element_id,
|
|
40
|
+
state: req.state,
|
|
41
|
+
state_label: state_label(req.state),
|
|
42
|
+
expires_at: req.expires_at,
|
|
43
|
+
created_at: req.created_at,
|
|
44
|
+
updated_at: req.updated_at,
|
|
45
|
+
first_accessed_at: req.first_accessed_at,
|
|
46
|
+
last_accessed_at: req.last_accessed_at,
|
|
47
|
+
access_count: req.access_count,
|
|
48
|
+
resp_message: req.resp_message,
|
|
49
|
+
active: req.active?
|
|
50
|
+
}
|
|
51
|
+
end,
|
|
52
|
+
pagination: {
|
|
53
|
+
page: page,
|
|
54
|
+
per_page: per_page,
|
|
55
|
+
total: total,
|
|
56
|
+
total_pages: (total.to_f / per_page).ceil
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
rescue StandardError => e
|
|
60
|
+
error!("Error: #{e.message}", 500)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
desc 'Get a dose response request by ID'
|
|
64
|
+
params do
|
|
65
|
+
requires :id, type: Integer, desc: 'Request ID'
|
|
66
|
+
end
|
|
67
|
+
get ':id' do
|
|
68
|
+
request = Labimotion::DoseRespRequest.find_by(id: params[:id])
|
|
69
|
+
error!('Request not found', 404) unless request
|
|
70
|
+
|
|
71
|
+
# Check authorization
|
|
72
|
+
error!('Unauthorized', 403) unless request.created_by == current_user.id
|
|
73
|
+
|
|
74
|
+
{
|
|
75
|
+
id: request.id,
|
|
76
|
+
request_id: request.request_id,
|
|
77
|
+
element_id: request.element_id,
|
|
78
|
+
state: request.state,
|
|
79
|
+
state_label: state_label(request.state),
|
|
80
|
+
wellplates_metadata: request.wellplates_metadata,
|
|
81
|
+
input_metadata: request.input_metadata,
|
|
82
|
+
expires_at: request.expires_at,
|
|
83
|
+
revoked_at: request.revoked_at,
|
|
84
|
+
created_at: request.created_at,
|
|
85
|
+
updated_at: request.updated_at,
|
|
86
|
+
first_accessed_at: request.first_accessed_at,
|
|
87
|
+
last_accessed_at: request.last_accessed_at,
|
|
88
|
+
access_count: request.access_count,
|
|
89
|
+
resp_message: request.resp_message,
|
|
90
|
+
active: request.active?,
|
|
91
|
+
expired: request.expired?,
|
|
92
|
+
revoked: request.revoked?
|
|
93
|
+
}
|
|
94
|
+
rescue StandardError => e
|
|
95
|
+
error!("Error: #{e.message}", 500)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
desc 'Update a dose response request'
|
|
99
|
+
params do
|
|
100
|
+
requires :id, type: Integer, desc: 'Request ID'
|
|
101
|
+
optional :state, type: Integer, desc: 'State', values: [-1, 0, 1, 2]
|
|
102
|
+
optional :resp_message, type: String, desc: 'Response message'
|
|
103
|
+
optional :wellplates_metadata, type: Hash, desc: 'Wellplates metadata'
|
|
104
|
+
end
|
|
105
|
+
put ':id' do
|
|
106
|
+
request = Labimotion::DoseRespRequest.find_by(id: params[:id])
|
|
107
|
+
error!('Request not found', 404) unless request
|
|
108
|
+
|
|
109
|
+
# Check authorization
|
|
110
|
+
error!('Unauthorized', 403) unless request.created_by == current_user.id
|
|
111
|
+
|
|
112
|
+
update_params = {}
|
|
113
|
+
update_params[:state] = params[:state] if params[:state]
|
|
114
|
+
update_params[:resp_message] = params[:resp_message] if params[:resp_message]
|
|
115
|
+
update_params[:wellplates_metadata] = params[:wellplates_metadata] if params[:wellplates_metadata]
|
|
116
|
+
|
|
117
|
+
request.update!(update_params)
|
|
118
|
+
|
|
119
|
+
{
|
|
120
|
+
success: true,
|
|
121
|
+
message: 'Request updated successfully',
|
|
122
|
+
request: {
|
|
123
|
+
id: request.id,
|
|
124
|
+
request_id: request.request_id,
|
|
125
|
+
state: request.state,
|
|
126
|
+
state_label: state_label(request.state),
|
|
127
|
+
resp_message: request.resp_message,
|
|
128
|
+
updated_at: request.updated_at
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
rescue ActiveRecord::RecordInvalid => e
|
|
132
|
+
error!("Validation error: #{e.message}", 422)
|
|
133
|
+
rescue StandardError => e
|
|
134
|
+
error!("Error: #{e.message}", 500)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
desc 'Revoke a dose response request'
|
|
138
|
+
params do
|
|
139
|
+
requires :id, type: Integer, desc: 'Request ID'
|
|
140
|
+
end
|
|
141
|
+
post ':id/revoke' do
|
|
142
|
+
request = Labimotion::DoseRespRequest.find_by(id: params[:id])
|
|
143
|
+
error!('Request not found', 404) unless request
|
|
144
|
+
|
|
145
|
+
# Check authorization
|
|
146
|
+
error!('Unauthorized', 403) unless request.created_by == current_user.id
|
|
147
|
+
|
|
148
|
+
request.revoke!
|
|
149
|
+
|
|
150
|
+
{
|
|
151
|
+
success: true,
|
|
152
|
+
message: 'Request revoked successfully',
|
|
153
|
+
request: {
|
|
154
|
+
id: request.id,
|
|
155
|
+
request_id: request.request_id,
|
|
156
|
+
state: request.state,
|
|
157
|
+
revoked_at: request.revoked_at,
|
|
158
|
+
active: request.active?
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
rescue StandardError => e
|
|
162
|
+
error!("Error: #{e.message}", 500)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
desc 'Delete a dose response request'
|
|
166
|
+
params do
|
|
167
|
+
requires :id, type: Integer, desc: 'Request ID'
|
|
168
|
+
end
|
|
169
|
+
delete ':id' do
|
|
170
|
+
request = Labimotion::DoseRespRequest.find_by(id: params[:id])
|
|
171
|
+
error!('Request not found', 404) unless request
|
|
172
|
+
|
|
173
|
+
# Check authorization
|
|
174
|
+
error!('Unauthorized', 403) unless request.created_by == current_user.id
|
|
175
|
+
|
|
176
|
+
# Soft delete if acts_as_paranoid is enabled
|
|
177
|
+
request.destroy
|
|
178
|
+
|
|
179
|
+
{
|
|
180
|
+
success: true,
|
|
181
|
+
message: 'Request deleted successfully'
|
|
182
|
+
}
|
|
183
|
+
rescue StandardError => e
|
|
184
|
+
error!("Error: #{e.message}", 500)
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
desc 'Get dose response requests by element ID'
|
|
188
|
+
params do
|
|
189
|
+
requires :element_id, type: Integer, desc: 'Element ID'
|
|
190
|
+
end
|
|
191
|
+
get 'by_element/:element_id' do
|
|
192
|
+
element = Labimotion::Element.find_by(id: params[:element_id])
|
|
193
|
+
error!('Element not found', 404) unless element
|
|
194
|
+
|
|
195
|
+
# Check if user has access to element
|
|
196
|
+
policy = ElementPolicy.new(current_user, element)
|
|
197
|
+
error!('Unauthorized', 403) unless policy.read?
|
|
198
|
+
|
|
199
|
+
requests = Labimotion::DoseRespRequest.where(element_id: params[:element_id])
|
|
200
|
+
.where(created_by: current_user.id)
|
|
201
|
+
.order(created_at: :desc)
|
|
202
|
+
|
|
203
|
+
{
|
|
204
|
+
element_id: element.id,
|
|
205
|
+
element_name: element.name,
|
|
206
|
+
requests: requests.map do |req|
|
|
207
|
+
{
|
|
208
|
+
id: req.id,
|
|
209
|
+
request_id: req.request_id,
|
|
210
|
+
state: req.state,
|
|
211
|
+
state_label: state_label(req.state),
|
|
212
|
+
expires_at: req.expires_at,
|
|
213
|
+
created_at: req.created_at,
|
|
214
|
+
access_count: req.access_count,
|
|
215
|
+
active: req.active?
|
|
216
|
+
}
|
|
217
|
+
end
|
|
218
|
+
}
|
|
219
|
+
rescue StandardError => e
|
|
220
|
+
error!("Error: #{e.message}", 500)
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
helpers do
|
|
225
|
+
def state_label(state)
|
|
226
|
+
case state
|
|
227
|
+
when Labimotion::DoseRespRequest::STATE_ERROR
|
|
228
|
+
'error'
|
|
229
|
+
when Labimotion::DoseRespRequest::STATE_INITIAL
|
|
230
|
+
'initial'
|
|
231
|
+
when Labimotion::DoseRespRequest::STATE_PROCESSING
|
|
232
|
+
'processing'
|
|
233
|
+
when Labimotion::DoseRespRequest::STATE_COMPLETED
|
|
234
|
+
'completed'
|
|
235
|
+
else
|
|
236
|
+
'unknown'
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Labimotion
|
|
4
|
+
class ElementVariationAPI < Grape::API
|
|
5
|
+
rescue_from ActiveRecord::RecordNotFound do
|
|
6
|
+
error!('404 Element not found', 404)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
resource :element_variations do
|
|
10
|
+
params do
|
|
11
|
+
requires :element_id, type: Integer, desc: 'Generic element id'
|
|
12
|
+
end
|
|
13
|
+
route_param :element_id do
|
|
14
|
+
before do
|
|
15
|
+
@element = Labimotion::Element.find(params[:element_id])
|
|
16
|
+
error!('401 Unauthorized', 401) unless ElementPolicy.new(current_user, @element).read?
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
desc 'Return element variations for a generic element'
|
|
20
|
+
get do
|
|
21
|
+
record = Labimotion::ElementVariation.find_or_initialize_by(element_id: @element.id)
|
|
22
|
+
present record, with: Labimotion::ElementVariationEntity, root: 'element_variation'
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
desc 'Upsert element variations for a generic element'
|
|
26
|
+
params do
|
|
27
|
+
requires :variations, type: Hash, desc: 'Variations keyed by row uuid'
|
|
28
|
+
optional :layout, type: Hash, desc: 'Column layout (selected/order/units/rowOrder)'
|
|
29
|
+
end
|
|
30
|
+
put do
|
|
31
|
+
error!('401 Unauthorized', 401) unless ElementPolicy.new(current_user, @element).update?
|
|
32
|
+
|
|
33
|
+
record = Labimotion::ElementVariation.find_or_initialize_by(element_id: @element.id)
|
|
34
|
+
record.variations = params[:variations] || {}
|
|
35
|
+
if params.key?(:layout) && record.class.column_names.include?('layout')
|
|
36
|
+
record.layout = params[:layout] || {}
|
|
37
|
+
end
|
|
38
|
+
record.save!
|
|
39
|
+
|
|
40
|
+
present record, with: Labimotion::ElementVariationEntity, root: 'element_variation'
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -179,10 +179,11 @@ module Labimotion
|
|
|
179
179
|
end
|
|
180
180
|
|
|
181
181
|
namespace :klass_revisions do
|
|
182
|
-
desc 'list Generic
|
|
182
|
+
desc 'list Generic Klass Revisions'
|
|
183
183
|
params do
|
|
184
184
|
requires :id, type: Integer, desc: 'Generic Element Klass Id'
|
|
185
185
|
requires :klass, type: String, desc: 'Klass', values: %w[ElementKlass SegmentKlass DatasetKlass]
|
|
186
|
+
optional :limit, type: Integer, default: 10, desc: 'Max revisions returned'
|
|
186
187
|
end
|
|
187
188
|
get do
|
|
188
189
|
list = list_klass_revisions(params)
|
|
@@ -197,6 +198,7 @@ module Labimotion
|
|
|
197
198
|
desc 'list Generic Element Revisions'
|
|
198
199
|
params do
|
|
199
200
|
requires :id, type: Integer, desc: 'Generic Element Id'
|
|
201
|
+
optional :limit, type: Integer, default: 10, desc: 'Max revisions returned'
|
|
200
202
|
end
|
|
201
203
|
get do
|
|
202
204
|
list = element_revisions(params)
|
|
@@ -241,14 +243,15 @@ module Labimotion
|
|
|
241
243
|
end
|
|
242
244
|
|
|
243
245
|
namespace :segment_revisions do
|
|
244
|
-
desc 'list Generic
|
|
246
|
+
desc 'list Generic Segment Revisions'
|
|
245
247
|
params do
|
|
246
248
|
optional :id, type: Integer, desc: 'Generic Element Id'
|
|
249
|
+
optional :limit, type: Integer, default: 10, desc: 'Max revisions returned'
|
|
247
250
|
end
|
|
248
251
|
get do
|
|
249
252
|
klass = Labimotion::Segment.find(params[:id])
|
|
250
253
|
list = klass.segments_revisions unless klass.nil?
|
|
251
|
-
present list&.order(created_at: :desc)&.limit(
|
|
254
|
+
present list&.order(created_at: :desc)&.limit(params[:limit]), with: Labimotion::SegmentRevisionEntity, root: 'revisions'
|
|
252
255
|
rescue StandardError => e
|
|
253
256
|
Labimotion.log_exception(e, current_user)
|
|
254
257
|
[]
|
|
@@ -12,5 +12,9 @@ module Labimotion
|
|
|
12
12
|
mount Labimotion::LabimotionHubAPI
|
|
13
13
|
mount Labimotion::StandardLayerAPI
|
|
14
14
|
mount Labimotion::VocabularyAPI
|
|
15
|
+
mount Labimotion::UserAPI
|
|
16
|
+
mount Labimotion::MttAPI
|
|
17
|
+
mount Labimotion::DoseRespRequestAPI
|
|
18
|
+
mount Labimotion::ElementVariationAPI
|
|
15
19
|
end
|
|
16
20
|
end
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'labimotion/version'
|
|
4
|
+
require 'labimotion/libs/export_element'
|
|
5
|
+
require 'labimotion/helpers/mtt_helpers'
|
|
6
|
+
require 'labimotion/models/dose_resp_output'
|
|
7
|
+
|
|
8
|
+
module Labimotion
|
|
9
|
+
# Generic Element API
|
|
10
|
+
class MttAPI < Grape::API
|
|
11
|
+
helpers Labimotion::ParamHelpers
|
|
12
|
+
helpers Labimotion::MttHelpers
|
|
13
|
+
|
|
14
|
+
namespace :public do
|
|
15
|
+
resource :mtt_apps do
|
|
16
|
+
route_param :token do
|
|
17
|
+
desc 'Download data from MTT app (GET endpoint)'
|
|
18
|
+
get do
|
|
19
|
+
download_json_to_external_app
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
desc 'Upload modified data from MTT app (POST endpoint)'
|
|
23
|
+
post do
|
|
24
|
+
upload_json_from_external_app
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
resource :mtt do
|
|
31
|
+
namespace :requests do
|
|
32
|
+
desc 'Get MTT requests for current user'
|
|
33
|
+
get do
|
|
34
|
+
# Get all requests created by current user
|
|
35
|
+
requests = Labimotion::DoseRespRequest
|
|
36
|
+
.includes(:dose_resp_outputs)
|
|
37
|
+
.where(created_by: current_user.id)
|
|
38
|
+
.order(created_at: :desc)
|
|
39
|
+
|
|
40
|
+
# Return formatted response
|
|
41
|
+
requests.map do |req|
|
|
42
|
+
{
|
|
43
|
+
id: req.id,
|
|
44
|
+
request_id: req.request_id,
|
|
45
|
+
element_id: req.element_id,
|
|
46
|
+
state: req.state,
|
|
47
|
+
state_name: case req.state
|
|
48
|
+
when Labimotion::DoseRespRequest::STATE_ERROR then 'error'
|
|
49
|
+
when Labimotion::DoseRespRequest::STATE_INITIAL then 'initial'
|
|
50
|
+
when Labimotion::DoseRespRequest::STATE_PROCESSING then 'processing'
|
|
51
|
+
when Labimotion::DoseRespRequest::STATE_COMPLETED then 'completed'
|
|
52
|
+
else 'unknown'
|
|
53
|
+
end,
|
|
54
|
+
created_at: req.created_at,
|
|
55
|
+
expires_at: req.expires_at,
|
|
56
|
+
expired: req.expired?,
|
|
57
|
+
revoked: req.revoked?,
|
|
58
|
+
active: req.active?,
|
|
59
|
+
resp_message: req.resp_message,
|
|
60
|
+
last_accessed_at: req.last_accessed_at,
|
|
61
|
+
access_count: req.access_count || 0,
|
|
62
|
+
outputs: req.dose_resp_outputs.map do |output|
|
|
63
|
+
{
|
|
64
|
+
id: output.id,
|
|
65
|
+
output_data: output.output_data,
|
|
66
|
+
notes: output.notes,
|
|
67
|
+
created_at: output.created_at
|
|
68
|
+
}
|
|
69
|
+
end
|
|
70
|
+
}
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
desc 'Delete one or multiple MTT requests'
|
|
75
|
+
params do
|
|
76
|
+
requires :ids, type: Array[Integer], desc: 'Array of request IDs to delete'
|
|
77
|
+
end
|
|
78
|
+
delete do
|
|
79
|
+
# Find requests belonging to current user
|
|
80
|
+
requests = Labimotion::DoseRespRequest.where(
|
|
81
|
+
id: params[:ids],
|
|
82
|
+
created_by: current_user.id
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
if requests.empty?
|
|
86
|
+
error!('No requests found or unauthorized', 404)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
deleted_count = requests.count
|
|
90
|
+
requests.destroy_all
|
|
91
|
+
|
|
92
|
+
{
|
|
93
|
+
success: true,
|
|
94
|
+
message: "Successfully deleted #{deleted_count} request(s)",
|
|
95
|
+
deleted_count: deleted_count
|
|
96
|
+
}
|
|
97
|
+
rescue StandardError => e
|
|
98
|
+
error!("Error deleting requests: #{e.message}", 500)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
desc 'Update MTT request'
|
|
102
|
+
params do
|
|
103
|
+
requires :id, type: Integer, desc: 'Request ID'
|
|
104
|
+
optional :state, type: Integer, desc: 'State (-1: error, 0: initial, 1: processing, 2: completed)', values: [-1, 0, 1, 2]
|
|
105
|
+
optional :resp_message, type: String, desc: 'Response message'
|
|
106
|
+
optional :wellplates_metadata, type: Hash, desc: 'Wellplates metadata'
|
|
107
|
+
optional :input_metadata, type: Hash, desc: 'Input metadata'
|
|
108
|
+
optional :revoked, type: Boolean, desc: 'Revoke access token'
|
|
109
|
+
end
|
|
110
|
+
patch ':id' do
|
|
111
|
+
# Find request belonging to current user
|
|
112
|
+
request = Labimotion::DoseRespRequest.find_by(
|
|
113
|
+
id: params[:id],
|
|
114
|
+
created_by: current_user.id
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
error!('Request not found or unauthorized', 404) unless request
|
|
118
|
+
|
|
119
|
+
# Prepare update attributes
|
|
120
|
+
update_attrs = {}
|
|
121
|
+
update_attrs[:state] = params[:state] if params.key?(:state)
|
|
122
|
+
update_attrs[:resp_message] = params[:resp_message] if params.key?(:resp_message)
|
|
123
|
+
update_attrs[:wellplates_metadata] = params[:wellplates_metadata] if params.key?(:wellplates_metadata)
|
|
124
|
+
update_attrs[:input_metadata] = params[:input_metadata] if params.key?(:input_metadata)
|
|
125
|
+
update_attrs[:revoked_at] = params[:revoked] ? Time.current : nil if params.key?(:revoked)
|
|
126
|
+
|
|
127
|
+
if request.update(update_attrs)
|
|
128
|
+
{
|
|
129
|
+
success: true,
|
|
130
|
+
message: 'Request updated successfully',
|
|
131
|
+
request: {
|
|
132
|
+
id: request.id,
|
|
133
|
+
request_id: request.request_id,
|
|
134
|
+
state: request.state,
|
|
135
|
+
state_name: case request.state
|
|
136
|
+
when Labimotion::DoseRespRequest::STATE_ERROR then 'error'
|
|
137
|
+
when Labimotion::DoseRespRequest::STATE_INITIAL then 'initial'
|
|
138
|
+
when Labimotion::DoseRespRequest::STATE_PROCESSING then 'processing'
|
|
139
|
+
when Labimotion::DoseRespRequest::STATE_COMPLETED then 'completed'
|
|
140
|
+
else 'unknown'
|
|
141
|
+
end,
|
|
142
|
+
resp_message: request.resp_message,
|
|
143
|
+
revoked: request.revoked?,
|
|
144
|
+
updated_at: request.updated_at
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
else
|
|
148
|
+
error!("Validation error: #{request.errors.full_messages.join(', ')}", 422)
|
|
149
|
+
end
|
|
150
|
+
rescue ActiveRecord::RecordNotFound
|
|
151
|
+
error!('Request not found', 404)
|
|
152
|
+
rescue StandardError => e
|
|
153
|
+
error!("Error updating request: #{e.message}", 500)
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
namespace :outputs do
|
|
158
|
+
desc 'Delete one or multiple MTT outputs'
|
|
159
|
+
params do
|
|
160
|
+
requires :ids, type: Array[Integer], desc: 'Array of output IDs to delete'
|
|
161
|
+
end
|
|
162
|
+
delete do
|
|
163
|
+
# Find outputs where the parent request belongs to current user
|
|
164
|
+
outputs = Labimotion::DoseRespOutput
|
|
165
|
+
.joins(:dose_resp_request)
|
|
166
|
+
.where(
|
|
167
|
+
id: params[:ids],
|
|
168
|
+
dose_resp_requests: { created_by: current_user.id }
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
if outputs.empty?
|
|
172
|
+
error!('No outputs found or unauthorized', 404)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
deleted_count = outputs.count
|
|
176
|
+
outputs.destroy_all
|
|
177
|
+
|
|
178
|
+
{
|
|
179
|
+
success: true,
|
|
180
|
+
message: "Successfully deleted #{deleted_count} output(s)",
|
|
181
|
+
deleted_count: deleted_count
|
|
182
|
+
}
|
|
183
|
+
rescue StandardError => e
|
|
184
|
+
error!("Error deleting outputs: #{e.message}", 500)
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
namespace :create_mtt_request do
|
|
189
|
+
desc 'Create MTT assay request'
|
|
190
|
+
params do
|
|
191
|
+
use :create_mtt_request_params
|
|
192
|
+
end
|
|
193
|
+
post do
|
|
194
|
+
# Find element and wellplates
|
|
195
|
+
element = Labimotion::Element.find_by(id: params[:id])
|
|
196
|
+
error!('Element not found', 404) unless element
|
|
197
|
+
#byebug
|
|
198
|
+
# Verify user has update permission
|
|
199
|
+
error!('Unauthorized', 403) unless ElementPolicy.new(current_user, element).update?
|
|
200
|
+
|
|
201
|
+
wellplates = Wellplate.where(id: params[:wellplate_ids])
|
|
202
|
+
error!('No wellplates found', 404) if wellplates.empty?
|
|
203
|
+
|
|
204
|
+
# Generate wellplates metadata
|
|
205
|
+
wellplates_metadata = generate_wellplates_metadata(wellplates)
|
|
206
|
+
|
|
207
|
+
# Create DoseRespRequest record with token
|
|
208
|
+
dose_resp_request = Labimotion::DoseRespRequest.create!(
|
|
209
|
+
element_id: element.id,
|
|
210
|
+
wellplates_metadata: { wellplates: wellplates_metadata },
|
|
211
|
+
input_metadata: {
|
|
212
|
+
wellplate_ids: params[:wellplate_ids],
|
|
213
|
+
element_id: element.id,
|
|
214
|
+
element_name: element.name,
|
|
215
|
+
created_at: Time.current
|
|
216
|
+
},
|
|
217
|
+
state: Labimotion::DoseRespRequest::STATE_INITIAL,
|
|
218
|
+
created_by: current_user&.id,
|
|
219
|
+
expires_at: TPA_EXPIRATION.from_now
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
# Generate external app URL with token
|
|
223
|
+
token_uri = token_url(dose_resp_request)
|
|
224
|
+
external_app_url = get_external_app_url
|
|
225
|
+
|
|
226
|
+
# Format: "#{@app.url}?url=#{CGI.escape(token_uri)}&type=ThirdPartyApp"
|
|
227
|
+
"#{external_app_url}?method=DoseResponse&url=#{CGI.escape(token_uri)}"
|
|
228
|
+
rescue ActiveRecord::RecordInvalid => e
|
|
229
|
+
error!("Validation error: #{e.message}", 422)
|
|
230
|
+
rescue ActiveRecord::RecordNotFound => e
|
|
231
|
+
error!("Not found: #{e.message}", 404)
|
|
232
|
+
rescue StandardError => e
|
|
233
|
+
error!("Error: #{e.message}", 500)
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
end
|