ORE Studio 0.0.4
Loading...
Searching...
No Matches
account_party_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_IAM_MESSAGING_ACCOUNT_PARTY_HANDLER_HPP
21#define ORES_IAM_MESSAGING_ACCOUNT_PARTY_HANDLER_HPP
22
23#include <stdexcept>
24#include <boost/uuid/string_generator.hpp>
25#include "ores.logging/make_logger.hpp"
26#include "ores.nats/domain/message.hpp"
27#include "ores.nats/service/client.hpp"
28#include "ores.database/domain/context.hpp"
29#include "ores.database/service/tenant_context.hpp"
30#include "ores.security/jwt/jwt_authenticator.hpp"
31#include "ores.service/messaging/handler_helpers.hpp"
32#include "ores.service/messaging/workflow_helpers.hpp"
33#include "ores.service/service/request_context.hpp"
34#include "ores.iam.api/messaging/account_party_protocol.hpp"
35#include "ores.iam.core/service/account_party_service.hpp"
36
37namespace ores::iam::messaging {
38
39namespace {
40
41inline auto& account_party_handler_lg() {
42 static auto instance = ores::logging::make_logger(
43 "ores.iam.messaging.account_party_handler");
44 return instance;
45}
46
47} // namespace
48
49using ores::service::messaging::reply;
50using ores::service::messaging::decode;
51using ores::service::messaging::stamp;
52using ores::service::messaging::error_reply;
53using ores::service::messaging::has_permission;
54using ores::service::messaging::log_handler_entry;
55using namespace ores::logging;
56
57class account_party_handler {
58public:
59 account_party_handler(ores::nats::service::client& nats,
62 : nats_(nats), ctx_(std::move(ctx)), signer_(std::move(signer)) {}
63
64 void list(ores::nats::message msg) {
65 [[maybe_unused]] const auto correlation_id =
66 log_handler_entry(account_party_handler_lg(), msg);
67 try {
68 service::account_party_service svc(ctx_);
69 auto aps = svc.list_account_parties();
70 get_account_parties_response resp;
71 resp.total_available_count =
72 static_cast<int>(aps.size());
73 resp.account_parties = std::move(aps);
74 BOOST_LOG_SEV(account_party_handler_lg(), debug)
75 << "Completed " << msg.subject;
76 reply(nats_, msg, resp);
77 } catch (const std::exception& e) {
78 BOOST_LOG_SEV(account_party_handler_lg(), error)
79 << msg.subject << " failed: " << e.what();
80 reply(nats_, msg, get_account_parties_response{});
81 }
82 }
83
84 void by_account(ores::nats::message msg) {
85 [[maybe_unused]] const auto correlation_id =
86 log_handler_entry(account_party_handler_lg(), msg);
87 auto req = decode<get_account_parties_by_account_request>(msg);
88 if (!req) {
89 BOOST_LOG_SEV(account_party_handler_lg(), warn)
90 << "Failed to decode: " << msg.subject;
91 return;
92 }
93 try {
94 service::account_party_service svc(ctx_);
95 boost::uuids::string_generator sg;
96 auto aps = svc.list_account_parties_by_account(
97 sg(req->account_id));
98 get_account_parties_by_account_response resp;
99 resp.account_parties = std::move(aps);
100 BOOST_LOG_SEV(account_party_handler_lg(), debug)
101 << "Completed " << msg.subject;
102 reply(nats_, msg, resp);
103 } catch (const std::exception& e) {
104 BOOST_LOG_SEV(account_party_handler_lg(), error)
105 << msg.subject << " failed: " << e.what();
106 reply(nats_, msg,
107 get_account_parties_by_account_response{});
108 }
109 }
110
111 void save(ores::nats::message msg) {
112 using ores::service::messaging::is_workflow_command;
113 using ores::service::messaging::extract_workflow_header;
114 using ores::service::messaging::publish_step_completion;
115 using ores::service::messaging::workflow_step_id_header;
116 using ores::service::messaging::workflow_instance_id_header;
117 using ores::service::messaging::workflow_tenant_id_header;
118
119 // Workflow step command: bypass JWT auth; use X-Tenant-Id for context.
120 if (is_workflow_command(msg)) {
121 const auto step_id = extract_workflow_header(msg, workflow_step_id_header);
122 const auto inst_id = extract_workflow_header(msg, workflow_instance_id_header);
123 const auto tenant_id = extract_workflow_header(msg, workflow_tenant_id_header);
124
125 auto req = decode<save_account_party_request>(msg);
126 if (!req) {
127 publish_step_completion(nats_, step_id, inst_id, false, "",
128 "Failed to decode save_account_party_request");
129 return;
130 }
131 try {
133 auto wf_ctx = tenant_context::with_tenant(ctx_, tenant_id);
134 service::account_party_service svc(wf_ctx);
135 for (auto ap : req->account_parties) {
136 stamp(ap, wf_ctx);
137 svc.save_account_party(ap);
138 }
139 BOOST_LOG_SEV(account_party_handler_lg(), debug)
140 << "Workflow step completed: " << msg.subject;
141 publish_step_completion(nats_, step_id, inst_id, true,
142 rfl::json::write(save_account_party_response{.success = true}), "");
143 } catch (const std::exception& e) {
144 BOOST_LOG_SEV(account_party_handler_lg(), error)
145 << "Workflow step failed: " << msg.subject
146 << " — " << e.what();
147 publish_step_completion(nats_, step_id, inst_id, false, "", e.what());
148 }
149 return;
150 }
151
152 [[maybe_unused]] const auto correlation_id =
153 log_handler_entry(account_party_handler_lg(), msg);
154 auto req = decode<save_account_party_request>(msg);
155 if (!req) {
156 BOOST_LOG_SEV(account_party_handler_lg(), warn)
157 << "Failed to decode: " << msg.subject;
158 return;
159 }
160 try {
161 auto ctx_expected = ores::service::service::make_request_context(
162 ctx_, msg, std::optional<ores::security::jwt::jwt_authenticator>{signer_});
163 if (!ctx_expected) {
164 error_reply(nats_, msg, ctx_expected.error());
165 return;
166 }
167 const auto& ctx = *ctx_expected;
168 if (!has_permission(ctx, "iam::accounts:update")) {
169 error_reply(nats_, msg, ores::service::error_code::forbidden);
170 return;
171 }
172 service::account_party_service svc(ctx);
173 for (auto ap : req->account_parties) {
174 stamp(ap, ctx);
175 svc.save_account_party(ap);
176 }
177 BOOST_LOG_SEV(account_party_handler_lg(), debug)
178 << "Completed " << msg.subject;
179 reply(nats_, msg,
180 save_account_party_response{.success = true});
181 } catch (const std::exception& e) {
182 BOOST_LOG_SEV(account_party_handler_lg(), error)
183 << msg.subject << " failed: " << e.what();
184 reply(nats_, msg, save_account_party_response{
185 .success = false, .message = e.what()});
186 }
187 }
188
189 void remove(ores::nats::message msg) {
190 [[maybe_unused]] const auto correlation_id =
191 log_handler_entry(account_party_handler_lg(), msg);
192 auto req = decode<delete_account_party_request>(msg);
193 if (!req) {
194 BOOST_LOG_SEV(account_party_handler_lg(), warn)
195 << "Failed to decode: " << msg.subject;
196 return;
197 }
198 auto ctx_expected = ores::service::service::make_request_context(
199 ctx_, msg, std::optional<ores::security::jwt::jwt_authenticator>{signer_});
200 if (!ctx_expected) {
201 error_reply(nats_, msg, ctx_expected.error());
202 return;
203 }
204 const auto& ctx = *ctx_expected;
205 if (!has_permission(ctx, "iam::accounts:update")) {
206 error_reply(nats_, msg, ores::service::error_code::forbidden);
207 return;
208 }
209 try {
210 service::account_party_service svc(ctx);
211 boost::uuids::string_generator sg;
212 for (const auto& key : req->keys)
213 svc.remove_account_party(
214 sg(key.account_id), sg(key.party_id));
215 BOOST_LOG_SEV(account_party_handler_lg(), debug)
216 << "Completed " << msg.subject;
217 reply(nats_, msg,
218 delete_account_party_response{.success = true});
219 } catch (const std::exception& e) {
220 BOOST_LOG_SEV(account_party_handler_lg(), error)
221 << msg.subject << " failed: " << e.what();
222 reply(nats_, msg, delete_account_party_response{
223 .success = false, .message = e.what()});
224 }
225 }
226
227private:
231};
232
233} // namespace ores::iam::messaging
234#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
Manages tenant context for multi-tenant database operations.
Definition tenant_context.hpp:37
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
JWT authentication primitive.
Definition jwt_authenticator.hpp:45