ORE Studio 0.0.4
Loading...
Searching...
No Matches
badge_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_BADGE_HANDLER_HPP
21#define ORES_DQ_CORE_MESSAGING_BADGE_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/badge_protocol.hpp"
32#include "ores.dq.core/service/badge_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& badge_handler_lg() {
46 static auto instance = ores::logging::make_logger("ores.dq.messaging.badge_handler");
47 return instance;
48}
49} // namespace
50
51class badge_handler {
52public:
53 badge_handler(
56 std::optional<ores::security::jwt::jwt_authenticator> verifier)
57 : nats_(nats), ctx_(std::move(ctx)), verifier_(std::move(verifier)) {}
58
59 // =========================================================================
60 // Badge Severity
61 // =========================================================================
62
63 void list_severities(ores::nats::message msg) {
64 BOOST_LOG_SEV(badge_handler_lg(), debug) << "Handling " << msg.subject;
65 auto req = decode<get_badge_severities_request>(msg);
66 if (!req) {
67 BOOST_LOG_SEV(badge_handler_lg(), warn) << "Failed to decode: " << msg.subject;
68 return;
69 }
70 auto ctx_expected = ores::service::service::make_request_context(ctx_, msg, verifier_);
71 if (!ctx_expected) { error_reply(nats_, msg, ctx_expected.error()); return; }
72 service::badge_service svc(*ctx_expected);
73 try {
74 const auto items = svc.list_severities();
75 get_badge_severities_response resp;
76 resp.badge_severities = items;
77 resp.total_available_count = static_cast<int>(items.size());
78 BOOST_LOG_SEV(badge_handler_lg(), debug) << "Completed " << msg.subject;
79 reply(nats_, msg, resp);
80 } catch (const std::exception& e) {
81 BOOST_LOG_SEV(badge_handler_lg(), error) << msg.subject << " failed: " << e.what();
82 reply(nats_, msg, get_badge_severities_response{});
83 }
84 }
85
86 void save_severity(ores::nats::message msg) {
87 BOOST_LOG_SEV(badge_handler_lg(), debug) << "Handling " << msg.subject;
88 auto req = decode<save_badge_severity_request>(msg);
89 if (!req) {
90 BOOST_LOG_SEV(badge_handler_lg(), warn) << "Failed to decode: " << msg.subject;
91 return;
92 }
93 auto ctx_expected = ores::service::service::make_request_context(ctx_, msg, verifier_);
94 if (!ctx_expected) { error_reply(nats_, msg, ctx_expected.error()); return; }
95 if (!has_permission(*ctx_expected, "dq::badges:write")) {
96 error_reply(nats_, msg, ores::service::error_code::forbidden);
97 return;
98 }
99 service::badge_service svc(*ctx_expected);
100 try {
101 stamp(req->data, *ctx_expected);
102 svc.save_severity(req->data);
103 BOOST_LOG_SEV(badge_handler_lg(), debug) << "Completed " << msg.subject;
104 reply(nats_, msg, save_badge_severity_response{true, {}});
105 } catch (const std::exception& e) {
106 BOOST_LOG_SEV(badge_handler_lg(), error) << msg.subject << " failed: " << e.what();
107 reply(nats_, msg, save_badge_severity_response{false, e.what()});
108 }
109 }
110
111 void delete_severities(ores::nats::message msg) {
112 BOOST_LOG_SEV(badge_handler_lg(), debug) << "Handling " << msg.subject;
113 auto req = decode<delete_badge_severity_request>(msg);
114 if (!req) {
115 BOOST_LOG_SEV(badge_handler_lg(), warn) << "Failed to decode: " << msg.subject;
116 return;
117 }
118 auto ctx_expected = ores::service::service::make_request_context(ctx_, msg, verifier_);
119 if (!ctx_expected) { error_reply(nats_, msg, ctx_expected.error()); return; }
120 if (!has_permission(*ctx_expected, "dq::badges:delete")) {
121 error_reply(nats_, msg, ores::service::error_code::forbidden);
122 return;
123 }
124 service::badge_service svc(*ctx_expected);
125 try {
126 svc.remove_severities(req->codes);
127 BOOST_LOG_SEV(badge_handler_lg(), debug) << "Completed " << msg.subject;
128 reply(nats_, msg, delete_badge_severity_response{true, {}});
129 } catch (const std::exception& e) {
130 BOOST_LOG_SEV(badge_handler_lg(), error) << msg.subject << " failed: " << e.what();
131 reply(nats_, msg, delete_badge_severity_response{false, e.what()});
132 }
133 }
134
135 void severity_history(ores::nats::message msg) {
136 BOOST_LOG_SEV(badge_handler_lg(), debug) << "Handling " << msg.subject;
137 auto req = decode<get_badge_severity_history_request>(msg);
138 if (!req) {
139 BOOST_LOG_SEV(badge_handler_lg(), warn) << "Failed to decode: " << msg.subject;
140 return;
141 }
142 auto ctx_expected = ores::service::service::make_request_context(ctx_, msg, verifier_);
143 if (!ctx_expected) { error_reply(nats_, msg, ctx_expected.error()); return; }
144 service::badge_service svc(*ctx_expected);
145 try {
146 const auto history = svc.get_severity_history(req->code);
147 get_badge_severity_history_response resp;
148 resp.success = true;
149 resp.history = history;
150 BOOST_LOG_SEV(badge_handler_lg(), debug) << "Completed " << msg.subject;
151 reply(nats_, msg, resp);
152 } catch (const std::exception& e) {
153 BOOST_LOG_SEV(badge_handler_lg(), error) << msg.subject << " failed: " << e.what();
154 get_badge_severity_history_response resp;
155 resp.success = false;
156 resp.message = e.what();
157 reply(nats_, msg, resp);
158 }
159 }
160
161 // =========================================================================
162 // Code Domain
163 // =========================================================================
164
165 void list_code_domains(ores::nats::message msg) {
166 BOOST_LOG_SEV(badge_handler_lg(), debug) << "Handling " << msg.subject;
167 auto req = decode<get_code_domains_request>(msg);
168 if (!req) {
169 BOOST_LOG_SEV(badge_handler_lg(), warn) << "Failed to decode: " << msg.subject;
170 return;
171 }
172 auto ctx_expected = ores::service::service::make_request_context(ctx_, msg, verifier_);
173 if (!ctx_expected) { error_reply(nats_, msg, ctx_expected.error()); return; }
174 service::badge_service svc(*ctx_expected);
175 try {
176 const auto items = svc.list_code_domains();
177 get_code_domains_response resp;
178 resp.code_domains = items;
179 resp.total_available_count = static_cast<int>(items.size());
180 BOOST_LOG_SEV(badge_handler_lg(), debug) << "Completed " << msg.subject;
181 reply(nats_, msg, resp);
182 } catch (const std::exception& e) {
183 BOOST_LOG_SEV(badge_handler_lg(), error) << msg.subject << " failed: " << e.what();
184 reply(nats_, msg, get_code_domains_response{});
185 }
186 }
187
188 void save_code_domain(ores::nats::message msg) {
189 BOOST_LOG_SEV(badge_handler_lg(), debug) << "Handling " << msg.subject;
190 auto req = decode<save_code_domain_request>(msg);
191 if (!req) {
192 BOOST_LOG_SEV(badge_handler_lg(), warn) << "Failed to decode: " << msg.subject;
193 return;
194 }
195 auto ctx_expected = ores::service::service::make_request_context(ctx_, msg, verifier_);
196 if (!ctx_expected) { error_reply(nats_, msg, ctx_expected.error()); return; }
197 if (!has_permission(*ctx_expected, "dq::badges:write")) {
198 error_reply(nats_, msg, ores::service::error_code::forbidden);
199 return;
200 }
201 service::badge_service svc(*ctx_expected);
202 try {
203 stamp(req->data, *ctx_expected);
204 svc.save_code_domain(req->data);
205 BOOST_LOG_SEV(badge_handler_lg(), debug) << "Completed " << msg.subject;
206 reply(nats_, msg, save_code_domain_response{true, {}});
207 } catch (const std::exception& e) {
208 BOOST_LOG_SEV(badge_handler_lg(), error) << msg.subject << " failed: " << e.what();
209 reply(nats_, msg, save_code_domain_response{false, e.what()});
210 }
211 }
212
213 void delete_code_domains(ores::nats::message msg) {
214 BOOST_LOG_SEV(badge_handler_lg(), debug) << "Handling " << msg.subject;
215 auto req = decode<delete_code_domain_request>(msg);
216 if (!req) {
217 BOOST_LOG_SEV(badge_handler_lg(), warn) << "Failed to decode: " << msg.subject;
218 return;
219 }
220 auto ctx_expected = ores::service::service::make_request_context(ctx_, msg, verifier_);
221 if (!ctx_expected) { error_reply(nats_, msg, ctx_expected.error()); return; }
222 if (!has_permission(*ctx_expected, "dq::badges:delete")) {
223 error_reply(nats_, msg, ores::service::error_code::forbidden);
224 return;
225 }
226 service::badge_service svc(*ctx_expected);
227 try {
228 svc.remove_code_domains(req->codes);
229 BOOST_LOG_SEV(badge_handler_lg(), debug) << "Completed " << msg.subject;
230 reply(nats_, msg, delete_code_domain_response{true, {}});
231 } catch (const std::exception& e) {
232 BOOST_LOG_SEV(badge_handler_lg(), error) << msg.subject << " failed: " << e.what();
233 reply(nats_, msg, delete_code_domain_response{false, e.what()});
234 }
235 }
236
237 void code_domain_history(ores::nats::message msg) {
238 BOOST_LOG_SEV(badge_handler_lg(), debug) << "Handling " << msg.subject;
239 auto req = decode<get_code_domain_history_request>(msg);
240 if (!req) {
241 BOOST_LOG_SEV(badge_handler_lg(), warn) << "Failed to decode: " << msg.subject;
242 return;
243 }
244 auto ctx_expected = ores::service::service::make_request_context(ctx_, msg, verifier_);
245 if (!ctx_expected) { error_reply(nats_, msg, ctx_expected.error()); return; }
246 service::badge_service svc(*ctx_expected);
247 try {
248 const auto history = svc.get_code_domain_history(req->code);
249 get_code_domain_history_response resp;
250 resp.success = true;
251 resp.history = history;
252 BOOST_LOG_SEV(badge_handler_lg(), debug) << "Completed " << msg.subject;
253 reply(nats_, msg, resp);
254 } catch (const std::exception& e) {
255 BOOST_LOG_SEV(badge_handler_lg(), error) << msg.subject << " failed: " << e.what();
256 get_code_domain_history_response resp;
257 resp.success = false;
258 resp.message = e.what();
259 reply(nats_, msg, resp);
260 }
261 }
262
263 // =========================================================================
264 // Badge Definition
265 // =========================================================================
266
267 void list_definitions(ores::nats::message msg) {
268 BOOST_LOG_SEV(badge_handler_lg(), debug) << "Handling " << msg.subject;
269 auto req = decode<get_badge_definitions_request>(msg);
270 if (!req) {
271 BOOST_LOG_SEV(badge_handler_lg(), warn) << "Failed to decode: " << msg.subject;
272 return;
273 }
274 auto ctx_expected = ores::service::service::make_request_context(ctx_, msg, verifier_);
275 if (!ctx_expected) { error_reply(nats_, msg, ctx_expected.error()); return; }
276 service::badge_service svc(*ctx_expected);
277 try {
278 const auto items = svc.list_definitions();
279 get_badge_definitions_response resp;
280 resp.definitions = items;
281 resp.total_available_count = static_cast<int>(items.size());
282 BOOST_LOG_SEV(badge_handler_lg(), debug) << "Completed " << msg.subject;
283 reply(nats_, msg, resp);
284 } catch (const std::exception& e) {
285 BOOST_LOG_SEV(badge_handler_lg(), error) << msg.subject << " failed: " << e.what();
286 reply(nats_, msg, get_badge_definitions_response{});
287 }
288 }
289
290 void save_definition(ores::nats::message msg) {
291 BOOST_LOG_SEV(badge_handler_lg(), debug) << "Handling " << msg.subject;
292 auto req = decode<save_badge_definition_request>(msg);
293 if (!req) {
294 BOOST_LOG_SEV(badge_handler_lg(), warn) << "Failed to decode: " << msg.subject;
295 return;
296 }
297 auto ctx_expected = ores::service::service::make_request_context(ctx_, msg, verifier_);
298 if (!ctx_expected) { error_reply(nats_, msg, ctx_expected.error()); return; }
299 if (!has_permission(*ctx_expected, "dq::badges:write")) {
300 error_reply(nats_, msg, ores::service::error_code::forbidden);
301 return;
302 }
303 service::badge_service svc(*ctx_expected);
304 try {
305 stamp(req->data, *ctx_expected);
306 svc.save_definition(req->data);
307 BOOST_LOG_SEV(badge_handler_lg(), debug) << "Completed " << msg.subject;
308 reply(nats_, msg, save_badge_definition_response{true, {}});
309 } catch (const std::exception& e) {
310 BOOST_LOG_SEV(badge_handler_lg(), error) << msg.subject << " failed: " << e.what();
311 reply(nats_, msg, save_badge_definition_response{false, e.what()});
312 }
313 }
314
315 void delete_definitions(ores::nats::message msg) {
316 BOOST_LOG_SEV(badge_handler_lg(), debug) << "Handling " << msg.subject;
317 auto req = decode<delete_badge_definition_request>(msg);
318 if (!req) {
319 BOOST_LOG_SEV(badge_handler_lg(), warn) << "Failed to decode: " << msg.subject;
320 return;
321 }
322 auto ctx_expected = ores::service::service::make_request_context(ctx_, msg, verifier_);
323 if (!ctx_expected) { error_reply(nats_, msg, ctx_expected.error()); return; }
324 if (!has_permission(*ctx_expected, "dq::badges:delete")) {
325 error_reply(nats_, msg, ores::service::error_code::forbidden);
326 return;
327 }
328 service::badge_service svc(*ctx_expected);
329 try {
330 svc.remove_definitions(req->codes);
331 BOOST_LOG_SEV(badge_handler_lg(), debug) << "Completed " << msg.subject;
332 reply(nats_, msg, delete_badge_definition_response{true, {}});
333 } catch (const std::exception& e) {
334 BOOST_LOG_SEV(badge_handler_lg(), error) << msg.subject << " failed: " << e.what();
335 reply(nats_, msg, delete_badge_definition_response{false, e.what()});
336 }
337 }
338
339 void definition_history(ores::nats::message msg) {
340 BOOST_LOG_SEV(badge_handler_lg(), debug) << "Handling " << msg.subject;
341 auto req = decode<get_badge_definition_history_request>(msg);
342 if (!req) {
343 BOOST_LOG_SEV(badge_handler_lg(), warn) << "Failed to decode: " << msg.subject;
344 return;
345 }
346 auto ctx_expected = ores::service::service::make_request_context(ctx_, msg, verifier_);
347 if (!ctx_expected) { error_reply(nats_, msg, ctx_expected.error()); return; }
348 service::badge_service svc(*ctx_expected);
349 try {
350 const auto history = svc.get_definition_history(req->code);
351 get_badge_definition_history_response resp;
352 resp.success = true;
353 resp.history = history;
354 BOOST_LOG_SEV(badge_handler_lg(), debug) << "Completed " << msg.subject;
355 reply(nats_, msg, resp);
356 } catch (const std::exception& e) {
357 BOOST_LOG_SEV(badge_handler_lg(), error) << msg.subject << " failed: " << e.what();
358 get_badge_definition_history_response resp;
359 resp.success = false;
360 resp.message = e.what();
361 reply(nats_, msg, resp);
362 }
363 }
364
365 // =========================================================================
366 // Badge Mapping (read-only)
367 // =========================================================================
368
369 void list_mappings(ores::nats::message msg) {
370 BOOST_LOG_SEV(badge_handler_lg(), debug) << "Handling " << msg.subject;
371 auto req = decode<get_badge_mappings_request>(msg);
372 if (!req) {
373 BOOST_LOG_SEV(badge_handler_lg(), warn) << "Failed to decode: " << msg.subject;
374 return;
375 }
376 auto ctx_expected = ores::service::service::make_request_context(ctx_, msg, verifier_);
377 if (!ctx_expected) { error_reply(nats_, msg, ctx_expected.error()); return; }
378 service::badge_service svc(*ctx_expected);
379 try {
380 const auto items = svc.list_mappings();
381 get_badge_mappings_response resp;
382 resp.mappings = items;
383 BOOST_LOG_SEV(badge_handler_lg(), debug) << "Completed " << msg.subject;
384 reply(nats_, msg, resp);
385 } catch (const std::exception& e) {
386 BOOST_LOG_SEV(badge_handler_lg(), error) << msg.subject << " failed: " << e.what();
387 reply(nats_, msg, get_badge_mappings_response{});
388 }
389 }
390
391private:
394 std::optional<ores::security::jwt::jwt_authenticator> verifier_;
395};
396
397} // namespace ores::dq::messaging
398
399#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