ORE Studio 0.0.4
Loading...
Searching...
No Matches
change_management_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_CHANGE_MANAGEMENT_HANDLER_HPP
21#define ORES_DQ_CORE_MESSAGING_CHANGE_MANAGEMENT_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/change_management_protocol.hpp"
32#include "ores.dq.core/service/change_management_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& change_management_handler_lg() {
46 static auto instance = ores::logging::make_logger("ores.dq.messaging.change_management_handler");
47 return instance;
48}
49} // namespace
50
51class change_management_handler {
52public:
53 change_management_handler(
56 std::optional<ores::security::jwt::jwt_authenticator> verifier)
57 : nats_(nats), ctx_(std::move(ctx)), verifier_(std::move(verifier)) {}
58
59 void list_categories(ores::nats::message msg) {
60 BOOST_LOG_SEV(change_management_handler_lg(), debug) << "Handling " << msg.subject;
61 auto req = decode<get_change_reason_categories_request>(msg);
62 if (!req) {
63 BOOST_LOG_SEV(change_management_handler_lg(), warn) << "Failed to decode: " << msg.subject;
64 return;
65 }
66 auto ctx_expected = ores::service::service::make_request_context(
67 ctx_, msg, verifier_);
68 if (!ctx_expected) {
69 error_reply(nats_, msg, ctx_expected.error());
70 return;
71 }
72 const auto& ctx = *ctx_expected;
73 service::change_management_service svc(ctx);
74 try {
75 const auto items = svc.list_categories();
76 get_change_reason_categories_response resp;
77 resp.categories = items;
78 BOOST_LOG_SEV(change_management_handler_lg(), debug) << "Completed " << msg.subject;
79 reply(nats_, msg, resp);
80 } catch (const std::exception& e) {
81 BOOST_LOG_SEV(change_management_handler_lg(), error) << msg.subject << " failed: " << e.what();
82 reply(nats_, msg, get_change_reason_categories_response{});
83 }
84 }
85
86 void save_category(ores::nats::message msg) {
87 BOOST_LOG_SEV(change_management_handler_lg(), debug) << "Handling " << msg.subject;
88 auto req = decode<save_change_reason_category_request>(msg);
89 if (!req) {
90 BOOST_LOG_SEV(change_management_handler_lg(), warn) << "Failed to decode: " << msg.subject;
91 return;
92 }
93 auto ctx_expected = ores::service::service::make_request_context(
94 ctx_, msg, verifier_);
95 if (!ctx_expected) {
96 error_reply(nats_, msg, ctx_expected.error());
97 return;
98 }
99 const auto& ctx = *ctx_expected;
100 if (!has_permission(ctx, "dq::change_reason_categories:write")) {
101 error_reply(nats_, msg, ores::service::error_code::forbidden);
102 return;
103 }
104 service::change_management_service svc(ctx);
105 try {
106 stamp(req->data, ctx);
107 svc.save_category(req->data);
108 BOOST_LOG_SEV(change_management_handler_lg(), debug) << "Completed " << msg.subject;
109 reply(nats_, msg, save_change_reason_category_response{true, {}});
110 } catch (const std::exception& e) {
111 BOOST_LOG_SEV(change_management_handler_lg(), error) << msg.subject << " failed: " << e.what();
112 reply(nats_, msg,
113 save_change_reason_category_response{false, e.what()});
114 }
115 }
116
117 void delete_categories(ores::nats::message msg) {
118 BOOST_LOG_SEV(change_management_handler_lg(), debug) << "Handling " << msg.subject;
119 auto req = decode<delete_change_reason_category_request>(msg);
120 if (!req) {
121 BOOST_LOG_SEV(change_management_handler_lg(), warn) << "Failed to decode: " << msg.subject;
122 return;
123 }
124 auto ctx_expected = ores::service::service::make_request_context(
125 ctx_, msg, verifier_);
126 if (!ctx_expected) {
127 error_reply(nats_, msg, ctx_expected.error());
128 return;
129 }
130 const auto& ctx = *ctx_expected;
131 if (!has_permission(ctx, "dq::change_reason_categories:delete")) {
132 error_reply(nats_, msg, ores::service::error_code::forbidden);
133 return;
134 }
135 service::change_management_service svc(ctx);
136 try {
137 svc.remove_categories(req->codes);
138 BOOST_LOG_SEV(change_management_handler_lg(), debug) << "Completed " << msg.subject;
139 reply(nats_, msg, delete_change_reason_category_response{true, {}});
140 } catch (const std::exception& e) {
141 BOOST_LOG_SEV(change_management_handler_lg(), error) << msg.subject << " failed: " << e.what();
142 reply(nats_, msg,
143 delete_change_reason_category_response{false, e.what()});
144 }
145 }
146
147 void category_history(ores::nats::message msg) {
148 BOOST_LOG_SEV(change_management_handler_lg(), debug) << "Handling " << msg.subject;
149 auto req = decode<get_change_reason_category_history_request>(msg);
150 if (!req) {
151 BOOST_LOG_SEV(change_management_handler_lg(), warn) << "Failed to decode: " << msg.subject;
152 return;
153 }
154 auto ctx_expected = ores::service::service::make_request_context(
155 ctx_, msg, verifier_);
156 if (!ctx_expected) {
157 error_reply(nats_, msg, ctx_expected.error());
158 return;
159 }
160 const auto& ctx = *ctx_expected;
161 service::change_management_service svc(ctx);
162 try {
163 const auto history = svc.get_category_history(req->code);
164 get_change_reason_category_history_response resp;
165 resp.success = true;
166 resp.versions = history;
167 BOOST_LOG_SEV(change_management_handler_lg(), debug) << "Completed " << msg.subject;
168 reply(nats_, msg, resp);
169 } catch (const std::exception& e) {
170 BOOST_LOG_SEV(change_management_handler_lg(), error) << msg.subject << " failed: " << e.what();
171 get_change_reason_category_history_response resp;
172 resp.success = false;
173 resp.message = e.what();
174 reply(nats_, msg, resp);
175 }
176 }
177
178 void list_reasons(ores::nats::message msg) {
179 BOOST_LOG_SEV(change_management_handler_lg(), debug) << "Handling " << msg.subject;
180 auto req = decode<get_change_reasons_request>(msg);
181 if (!req) {
182 BOOST_LOG_SEV(change_management_handler_lg(), warn) << "Failed to decode: " << msg.subject;
183 return;
184 }
185 auto ctx_expected = ores::service::service::make_request_context(
186 ctx_, msg, verifier_);
187 if (!ctx_expected) {
188 error_reply(nats_, msg, ctx_expected.error());
189 return;
190 }
191 const auto& ctx = *ctx_expected;
192 service::change_management_service svc(ctx);
193 try {
194 const auto items = svc.list_reasons();
195 get_change_reasons_response resp;
196 resp.reasons = items;
197 BOOST_LOG_SEV(change_management_handler_lg(), debug) << "Completed " << msg.subject;
198 reply(nats_, msg, resp);
199 } catch (const std::exception& e) {
200 BOOST_LOG_SEV(change_management_handler_lg(), error) << msg.subject << " failed: " << e.what();
201 reply(nats_, msg, get_change_reasons_response{});
202 }
203 }
204
205 void save_reason(ores::nats::message msg) {
206 BOOST_LOG_SEV(change_management_handler_lg(), debug) << "Handling " << msg.subject;
207 auto req = decode<save_change_reason_request>(msg);
208 if (!req) {
209 BOOST_LOG_SEV(change_management_handler_lg(), warn) << "Failed to decode: " << msg.subject;
210 return;
211 }
212 auto ctx_expected = ores::service::service::make_request_context(
213 ctx_, msg, verifier_);
214 if (!ctx_expected) {
215 error_reply(nats_, msg, ctx_expected.error());
216 return;
217 }
218 const auto& ctx = *ctx_expected;
219 if (!has_permission(ctx, "dq::change_reasons:write")) {
220 error_reply(nats_, msg, ores::service::error_code::forbidden);
221 return;
222 }
223 service::change_management_service svc(ctx);
224 try {
225 stamp(req->data, ctx);
226 svc.save_reason(req->data);
227 BOOST_LOG_SEV(change_management_handler_lg(), debug) << "Completed " << msg.subject;
228 reply(nats_, msg, save_change_reason_response{true, {}});
229 } catch (const std::exception& e) {
230 BOOST_LOG_SEV(change_management_handler_lg(), error) << msg.subject << " failed: " << e.what();
231 reply(nats_, msg, save_change_reason_response{false, e.what()});
232 }
233 }
234
235 void delete_reasons(ores::nats::message msg) {
236 BOOST_LOG_SEV(change_management_handler_lg(), debug) << "Handling " << msg.subject;
237 auto req = decode<delete_change_reason_request>(msg);
238 if (!req) {
239 BOOST_LOG_SEV(change_management_handler_lg(), warn) << "Failed to decode: " << msg.subject;
240 return;
241 }
242 auto ctx_expected = ores::service::service::make_request_context(
243 ctx_, msg, verifier_);
244 if (!ctx_expected) {
245 error_reply(nats_, msg, ctx_expected.error());
246 return;
247 }
248 const auto& ctx = *ctx_expected;
249 if (!has_permission(ctx, "dq::change_reasons:delete")) {
250 error_reply(nats_, msg, ores::service::error_code::forbidden);
251 return;
252 }
253 service::change_management_service svc(ctx);
254 try {
255 svc.remove_reasons(req->codes);
256 BOOST_LOG_SEV(change_management_handler_lg(), debug) << "Completed " << msg.subject;
257 reply(nats_, msg, delete_change_reason_response{true, {}});
258 } catch (const std::exception& e) {
259 BOOST_LOG_SEV(change_management_handler_lg(), error) << msg.subject << " failed: " << e.what();
260 reply(nats_, msg, delete_change_reason_response{false, e.what()});
261 }
262 }
263
264 void reason_history(ores::nats::message msg) {
265 BOOST_LOG_SEV(change_management_handler_lg(), debug) << "Handling " << msg.subject;
266 auto req = decode<get_change_reason_history_request>(msg);
267 if (!req) {
268 BOOST_LOG_SEV(change_management_handler_lg(), warn) << "Failed to decode: " << msg.subject;
269 return;
270 }
271 auto ctx_expected = ores::service::service::make_request_context(
272 ctx_, msg, verifier_);
273 if (!ctx_expected) {
274 error_reply(nats_, msg, ctx_expected.error());
275 return;
276 }
277 const auto& ctx = *ctx_expected;
278 service::change_management_service svc(ctx);
279 try {
280 const auto history = svc.get_reason_history(req->code);
281 get_change_reason_history_response resp;
282 resp.success = true;
283 resp.versions = history;
284 BOOST_LOG_SEV(change_management_handler_lg(), debug) << "Completed " << msg.subject;
285 reply(nats_, msg, resp);
286 } catch (const std::exception& e) {
287 BOOST_LOG_SEV(change_management_handler_lg(), error) << msg.subject << " failed: " << e.what();
288 get_change_reason_history_response resp;
289 resp.success = false;
290 resp.message = e.what();
291 reply(nats_, msg, resp);
292 }
293 }
294
295private:
296
299 std::optional<ores::security::jwt::jwt_authenticator> verifier_;
300};
301
302} // namespace ores::dq::messaging
303
304#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