ORE Studio 0.0.4
Loading...
Searching...
No Matches
dimension_handler.hpp
1/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 *
3 * Copyright (C) 2026 Marco Craveiro <marco.craveiro@gmail.com>
4 *
5 * This program is free software; you can redistribute it and/or modify it under
6 * the terms of the GNU General Public License as published by the Free Software
7 * Foundation; either version 3 of the License, or (at your option) any later
8 * version.
9 *
10 * This program is distributed in the hope that it will be useful, but WITHOUT
11 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
13 * details.
14 *
15 * You should have received a copy of the GNU General Public License along with
16 * this program; if not, write to the Free Software Foundation, Inc., 51
17 * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 *
19 */
20#ifndef ORES_DQ_CORE_MESSAGING_DIMENSION_HANDLER_HPP
21#define ORES_DQ_CORE_MESSAGING_DIMENSION_HANDLER_HPP
22
23#include <optional>
24#include <stdexcept>
25#include "ores.nats/domain/message.hpp"
26#include "ores.nats/service/client.hpp"
27#include "ores.database/domain/context.hpp"
28#include "ores.security/jwt/jwt_authenticator.hpp"
29#include "ores.service/messaging/handler_helpers.hpp"
30#include "ores.service/service/request_context.hpp"
31#include "ores.dq.api/messaging/data_organization_protocol.hpp"
32#include "ores.dq.core/service/dimension_service.hpp"
33#include "ores.logging/make_logger.hpp"
34
35namespace ores::dq::messaging {
36
37using ores::service::messaging::reply;
38using ores::service::messaging::decode;
39using ores::service::messaging::stamp;
40using ores::service::messaging::error_reply;
41using ores::service::messaging::has_permission;
42using namespace ores::logging;
43
44namespace {
45inline auto& dimension_handler_lg() {
46 static auto instance = ores::logging::make_logger("ores.dq.messaging.dimension_handler");
47 return instance;
48}
49} // namespace
50
51class dimension_handler {
52public:
53 dimension_handler(
56 std::optional<ores::security::jwt::jwt_authenticator> verifier)
57 : nats_(nats), ctx_(std::move(ctx)), verifier_(std::move(verifier)) {}
58
59 // =========================================================================
60 // Nature Dimensions
61 // =========================================================================
62
63 void list_nature_dimensions(ores::nats::message msg) {
64 BOOST_LOG_SEV(dimension_handler_lg(), debug) << "Handling " << msg.subject;
65 auto req = decode<get_nature_dimensions_request>(msg);
66 if (!req) {
67 BOOST_LOG_SEV(dimension_handler_lg(), warn) << "Failed to decode: " << msg.subject;
68 return;
69 }
70 auto ctx_expected = ores::service::service::make_request_context(
71 ctx_, msg, verifier_);
72 if (!ctx_expected) {
73 error_reply(nats_, msg, ctx_expected.error());
74 return;
75 }
76 const auto& ctx = *ctx_expected;
77 service::dimension_service svc(ctx);
78 try {
79 const auto items = svc.list_nature_dimensions();
80 get_nature_dimensions_response resp;
81 resp.nature_dimensions = items;
82 resp.total_available_count = static_cast<int>(items.size());
83 BOOST_LOG_SEV(dimension_handler_lg(), debug) << "Completed " << msg.subject;
84 reply(nats_, msg, resp);
85 } catch (const std::exception& e) {
86 BOOST_LOG_SEV(dimension_handler_lg(), error) << msg.subject << " failed: " << e.what();
87 get_nature_dimensions_response resp;
88 resp.total_available_count = 0;
89 reply(nats_, msg, resp);
90 }
91 }
92
93 void save_nature_dimension(ores::nats::message msg) {
94 BOOST_LOG_SEV(dimension_handler_lg(), debug) << "Handling " << msg.subject;
95 auto req = decode<save_nature_dimension_request>(msg);
96 if (!req) {
97 BOOST_LOG_SEV(dimension_handler_lg(), warn) << "Failed to decode: " << msg.subject;
98 return;
99 }
100 auto ctx_expected = ores::service::service::make_request_context(
101 ctx_, msg, verifier_);
102 if (!ctx_expected) {
103 error_reply(nats_, msg, ctx_expected.error());
104 return;
105 }
106 const auto& ctx = *ctx_expected;
107 if (!has_permission(ctx, "dq::nature_dimensions:write")) {
108 error_reply(nats_, msg, ores::service::error_code::forbidden);
109 return;
110 }
111 service::dimension_service svc(ctx);
112 try {
113 stamp(req->data, ctx);
114 svc.save_nature_dimension(req->data);
115 BOOST_LOG_SEV(dimension_handler_lg(), debug) << "Completed " << msg.subject;
116 reply(nats_, msg, save_nature_dimension_response{true, {}});
117 } catch (const std::exception& e) {
118 BOOST_LOG_SEV(dimension_handler_lg(), error) << msg.subject << " failed: " << e.what();
119 reply(nats_, msg, save_nature_dimension_response{false, e.what()});
120 }
121 }
122
123 void delete_nature_dimensions(ores::nats::message msg) {
124 BOOST_LOG_SEV(dimension_handler_lg(), debug) << "Handling " << msg.subject;
125 auto req = decode<delete_nature_dimension_request>(msg);
126 if (!req) {
127 BOOST_LOG_SEV(dimension_handler_lg(), warn) << "Failed to decode: " << msg.subject;
128 return;
129 }
130 auto ctx_expected = ores::service::service::make_request_context(
131 ctx_, msg, verifier_);
132 if (!ctx_expected) {
133 error_reply(nats_, msg, ctx_expected.error());
134 return;
135 }
136 const auto& ctx = *ctx_expected;
137 if (!has_permission(ctx, "dq::nature_dimensions:delete")) {
138 error_reply(nats_, msg, ores::service::error_code::forbidden);
139 return;
140 }
141 service::dimension_service svc(ctx);
142 try {
143 svc.remove_nature_dimensions(req->codes);
144 BOOST_LOG_SEV(dimension_handler_lg(), debug) << "Completed " << msg.subject;
145 reply(nats_, msg, delete_nature_dimension_response{true, {}});
146 } catch (const std::exception& e) {
147 BOOST_LOG_SEV(dimension_handler_lg(), error) << msg.subject << " failed: " << e.what();
148 reply(nats_, msg,
149 delete_nature_dimension_response{false, e.what()});
150 }
151 }
152
153 void nature_dimension_history(ores::nats::message msg) {
154 BOOST_LOG_SEV(dimension_handler_lg(), debug) << "Handling " << msg.subject;
155 auto req = decode<get_nature_dimension_history_request>(msg);
156 if (!req) {
157 BOOST_LOG_SEV(dimension_handler_lg(), warn) << "Failed to decode: " << msg.subject;
158 return;
159 }
160 auto ctx_expected = ores::service::service::make_request_context(
161 ctx_, msg, verifier_);
162 if (!ctx_expected) {
163 error_reply(nats_, msg, ctx_expected.error());
164 return;
165 }
166 const auto& ctx = *ctx_expected;
167 service::dimension_service svc(ctx);
168 try {
169 const auto history = svc.get_nature_dimension_history(req->code);
170 get_nature_dimension_history_response resp;
171 resp.success = true;
172 resp.history = history;
173 BOOST_LOG_SEV(dimension_handler_lg(), debug) << "Completed " << msg.subject;
174 reply(nats_, msg, resp);
175 } catch (const std::exception& e) {
176 BOOST_LOG_SEV(dimension_handler_lg(), error) << msg.subject << " failed: " << e.what();
177 get_nature_dimension_history_response resp;
178 resp.success = false;
179 resp.message = e.what();
180 reply(nats_, msg, resp);
181 }
182 }
183
184 // =========================================================================
185 // Origin Dimensions
186 // =========================================================================
187
188 void list_origin_dimensions(ores::nats::message msg) {
189 BOOST_LOG_SEV(dimension_handler_lg(), debug) << "Handling " << msg.subject;
190 auto req = decode<get_origin_dimensions_request>(msg);
191 if (!req) {
192 BOOST_LOG_SEV(dimension_handler_lg(), warn) << "Failed to decode: " << msg.subject;
193 return;
194 }
195 auto ctx_expected = ores::service::service::make_request_context(
196 ctx_, msg, verifier_);
197 if (!ctx_expected) {
198 error_reply(nats_, msg, ctx_expected.error());
199 return;
200 }
201 const auto& ctx = *ctx_expected;
202 service::dimension_service svc(ctx);
203 try {
204 const auto items = svc.list_origin_dimensions();
205 get_origin_dimensions_response resp;
206 resp.origin_dimensions = items;
207 resp.total_available_count = static_cast<int>(items.size());
208 BOOST_LOG_SEV(dimension_handler_lg(), debug) << "Completed " << msg.subject;
209 reply(nats_, msg, resp);
210 } catch (const std::exception& e) {
211 BOOST_LOG_SEV(dimension_handler_lg(), error) << msg.subject << " failed: " << e.what();
212 get_origin_dimensions_response resp;
213 resp.total_available_count = 0;
214 reply(nats_, msg, resp);
215 }
216 }
217
218 void save_origin_dimension(ores::nats::message msg) {
219 BOOST_LOG_SEV(dimension_handler_lg(), debug) << "Handling " << msg.subject;
220 auto req = decode<save_origin_dimension_request>(msg);
221 if (!req) {
222 BOOST_LOG_SEV(dimension_handler_lg(), warn) << "Failed to decode: " << msg.subject;
223 return;
224 }
225 auto ctx_expected = ores::service::service::make_request_context(
226 ctx_, msg, verifier_);
227 if (!ctx_expected) {
228 error_reply(nats_, msg, ctx_expected.error());
229 return;
230 }
231 const auto& ctx = *ctx_expected;
232 if (!has_permission(ctx, "dq::origin_dimensions:write")) {
233 error_reply(nats_, msg, ores::service::error_code::forbidden);
234 return;
235 }
236 service::dimension_service svc(ctx);
237 try {
238 stamp(req->data, ctx);
239 svc.save_origin_dimension(req->data);
240 BOOST_LOG_SEV(dimension_handler_lg(), debug) << "Completed " << msg.subject;
241 reply(nats_, msg, save_origin_dimension_response{true, {}});
242 } catch (const std::exception& e) {
243 BOOST_LOG_SEV(dimension_handler_lg(), error) << msg.subject << " failed: " << e.what();
244 reply(nats_, msg, save_origin_dimension_response{false, e.what()});
245 }
246 }
247
248 void delete_origin_dimensions(ores::nats::message msg) {
249 BOOST_LOG_SEV(dimension_handler_lg(), debug) << "Handling " << msg.subject;
250 auto req = decode<delete_origin_dimension_request>(msg);
251 if (!req) {
252 BOOST_LOG_SEV(dimension_handler_lg(), warn) << "Failed to decode: " << msg.subject;
253 return;
254 }
255 auto ctx_expected = ores::service::service::make_request_context(
256 ctx_, msg, verifier_);
257 if (!ctx_expected) {
258 error_reply(nats_, msg, ctx_expected.error());
259 return;
260 }
261 const auto& ctx = *ctx_expected;
262 if (!has_permission(ctx, "dq::origin_dimensions:delete")) {
263 error_reply(nats_, msg, ores::service::error_code::forbidden);
264 return;
265 }
266 service::dimension_service svc(ctx);
267 try {
268 svc.remove_origin_dimensions(req->codes);
269 BOOST_LOG_SEV(dimension_handler_lg(), debug) << "Completed " << msg.subject;
270 reply(nats_, msg, delete_origin_dimension_response{true, {}});
271 } catch (const std::exception& e) {
272 BOOST_LOG_SEV(dimension_handler_lg(), error) << msg.subject << " failed: " << e.what();
273 reply(nats_, msg,
274 delete_origin_dimension_response{false, e.what()});
275 }
276 }
277
278 void origin_dimension_history(ores::nats::message msg) {
279 BOOST_LOG_SEV(dimension_handler_lg(), debug) << "Handling " << msg.subject;
280 auto req = decode<get_origin_dimension_history_request>(msg);
281 if (!req) {
282 BOOST_LOG_SEV(dimension_handler_lg(), warn) << "Failed to decode: " << msg.subject;
283 return;
284 }
285 auto ctx_expected = ores::service::service::make_request_context(
286 ctx_, msg, verifier_);
287 if (!ctx_expected) {
288 error_reply(nats_, msg, ctx_expected.error());
289 return;
290 }
291 const auto& ctx = *ctx_expected;
292 service::dimension_service svc(ctx);
293 try {
294 const auto history = svc.get_origin_dimension_history(req->code);
295 get_origin_dimension_history_response resp;
296 resp.success = true;
297 resp.history = history;
298 BOOST_LOG_SEV(dimension_handler_lg(), debug) << "Completed " << msg.subject;
299 reply(nats_, msg, resp);
300 } catch (const std::exception& e) {
301 BOOST_LOG_SEV(dimension_handler_lg(), error) << msg.subject << " failed: " << e.what();
302 get_origin_dimension_history_response resp;
303 resp.success = false;
304 resp.message = e.what();
305 reply(nats_, msg, resp);
306 }
307 }
308
309 // =========================================================================
310 // Treatment Dimensions
311 // =========================================================================
312
313 void list_treatment_dimensions(ores::nats::message msg) {
314 BOOST_LOG_SEV(dimension_handler_lg(), debug) << "Handling " << msg.subject;
315 auto req = decode<get_treatment_dimensions_request>(msg);
316 if (!req) {
317 BOOST_LOG_SEV(dimension_handler_lg(), warn) << "Failed to decode: " << msg.subject;
318 return;
319 }
320 auto ctx_expected = ores::service::service::make_request_context(
321 ctx_, msg, verifier_);
322 if (!ctx_expected) {
323 error_reply(nats_, msg, ctx_expected.error());
324 return;
325 }
326 const auto& ctx = *ctx_expected;
327 service::dimension_service svc(ctx);
328 try {
329 const auto items = svc.list_treatment_dimensions();
330 get_treatment_dimensions_response resp;
331 resp.treatment_dimensions = items;
332 resp.total_available_count = static_cast<int>(items.size());
333 BOOST_LOG_SEV(dimension_handler_lg(), debug) << "Completed " << msg.subject;
334 reply(nats_, msg, resp);
335 } catch (const std::exception& e) {
336 BOOST_LOG_SEV(dimension_handler_lg(), error) << msg.subject << " failed: " << e.what();
337 get_treatment_dimensions_response resp;
338 resp.total_available_count = 0;
339 reply(nats_, msg, resp);
340 }
341 }
342
343 void save_treatment_dimension(ores::nats::message msg) {
344 BOOST_LOG_SEV(dimension_handler_lg(), debug) << "Handling " << msg.subject;
345 auto req = decode<save_treatment_dimension_request>(msg);
346 if (!req) {
347 BOOST_LOG_SEV(dimension_handler_lg(), warn) << "Failed to decode: " << msg.subject;
348 return;
349 }
350 auto ctx_expected = ores::service::service::make_request_context(
351 ctx_, msg, verifier_);
352 if (!ctx_expected) {
353 error_reply(nats_, msg, ctx_expected.error());
354 return;
355 }
356 const auto& ctx = *ctx_expected;
357 if (!has_permission(ctx, "dq::treatment_dimensions:write")) {
358 error_reply(nats_, msg, ores::service::error_code::forbidden);
359 return;
360 }
361 service::dimension_service svc(ctx);
362 try {
363 stamp(req->data, ctx);
364 svc.save_treatment_dimension(req->data);
365 BOOST_LOG_SEV(dimension_handler_lg(), debug) << "Completed " << msg.subject;
366 reply(nats_, msg, save_treatment_dimension_response{true, {}});
367 } catch (const std::exception& e) {
368 BOOST_LOG_SEV(dimension_handler_lg(), error) << msg.subject << " failed: " << e.what();
369 reply(nats_, msg,
370 save_treatment_dimension_response{false, e.what()});
371 }
372 }
373
374 void delete_treatment_dimensions(ores::nats::message msg) {
375 BOOST_LOG_SEV(dimension_handler_lg(), debug) << "Handling " << msg.subject;
376 auto req = decode<delete_treatment_dimension_request>(msg);
377 if (!req) {
378 BOOST_LOG_SEV(dimension_handler_lg(), warn) << "Failed to decode: " << msg.subject;
379 return;
380 }
381 auto ctx_expected = ores::service::service::make_request_context(
382 ctx_, msg, verifier_);
383 if (!ctx_expected) {
384 error_reply(nats_, msg, ctx_expected.error());
385 return;
386 }
387 const auto& ctx = *ctx_expected;
388 if (!has_permission(ctx, "dq::treatment_dimensions:delete")) {
389 error_reply(nats_, msg, ores::service::error_code::forbidden);
390 return;
391 }
392 service::dimension_service svc(ctx);
393 try {
394 svc.remove_treatment_dimensions(req->codes);
395 BOOST_LOG_SEV(dimension_handler_lg(), debug) << "Completed " << msg.subject;
396 reply(nats_, msg, delete_treatment_dimension_response{true, {}});
397 } catch (const std::exception& e) {
398 BOOST_LOG_SEV(dimension_handler_lg(), error) << msg.subject << " failed: " << e.what();
399 reply(nats_, msg,
400 delete_treatment_dimension_response{false, e.what()});
401 }
402 }
403
404 void treatment_dimension_history(ores::nats::message msg) {
405 BOOST_LOG_SEV(dimension_handler_lg(), debug) << "Handling " << msg.subject;
406 auto req = decode<get_treatment_dimension_history_request>(msg);
407 if (!req) {
408 BOOST_LOG_SEV(dimension_handler_lg(), warn) << "Failed to decode: " << msg.subject;
409 return;
410 }
411 auto ctx_expected = ores::service::service::make_request_context(
412 ctx_, msg, verifier_);
413 if (!ctx_expected) {
414 error_reply(nats_, msg, ctx_expected.error());
415 return;
416 }
417 const auto& ctx = *ctx_expected;
418 service::dimension_service svc(ctx);
419 try {
420 const auto history =
421 svc.get_treatment_dimension_history(req->code);
422 get_treatment_dimension_history_response resp;
423 resp.success = true;
424 resp.history = history;
425 BOOST_LOG_SEV(dimension_handler_lg(), debug) << "Completed " << msg.subject;
426 reply(nats_, msg, resp);
427 } catch (const std::exception& e) {
428 BOOST_LOG_SEV(dimension_handler_lg(), error) << msg.subject << " failed: " << e.what();
429 get_treatment_dimension_history_response resp;
430 resp.success = false;
431 resp.message = e.what();
432 reply(nats_, msg, resp);
433 }
434 }
435
436private:
437
440 std::optional<ores::security::jwt::jwt_authenticator> verifier_;
441};
442
443} // namespace ores::dq::messaging
444
445#endif
STL namespace.
Implements logging infrastructure for ORE Studio.
Definition boost_severity.hpp:28
@ forbidden
The caller is authenticated but lacks the required permission.
Context for the operations on a postgres database.
Definition context.hpp:47
A received NATS message.
Definition message.hpp:40
std::string subject
The subject the message was published to.
Definition message.hpp:44
NATS client: connection, pub/sub, request/reply, and JetStream.
Definition client.hpp:73