ORE Studio 0.0.4
Loading...
Searching...
No Matches
ClientManager.hpp
1/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 *
3 * Copyright (C) 2025 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_QT_CLIENT_MANAGER_HPP
21#define ORES_QT_CLIENT_MANAGER_HPP
22
23#include <atomic>
24#include <chrono>
25#include <concepts>
26#include <expected>
27#include <memory>
28#include <optional>
29#include <string>
30#include <filesystem>
31#include <boost/uuid/uuid.hpp>
32#include <rfl/json.hpp>
33#include <QObject>
34#include <QTimer>
35#include "ores.utility/rfl/reflectors.hpp"
36#include <QDateTime>
37#include "ores.nats/service/nats_client.hpp"
38#include "ores.nats/service/session_expired_error.hpp"
39#include "ores.nats/service/jetstream_admin.hpp"
40#include "ores.nats/service/subscription.hpp"
41#include "ores.eventing/service/event_bus.hpp"
42#include "ores.logging/make_logger.hpp"
43#include "ores.iam.api/domain/session.hpp"
44#include "ores.iam.api/messaging/session_samples_protocol.hpp"
45
46namespace ores::qt {
47
53template <typename T>
54concept nats_request = requires {
55 { T::nats_subject } -> std::convertible_to<std::string_view>;
56 typename T::response_type;
57};
58
62struct PartyInfo {
63 boost::uuids::uuid id;
64 QString name;
65};
66
71 bool success = false;
72 QString error_message;
73 bool password_reset_required = false;
74 bool bootstrap_mode = false;
75 bool tenant_bootstrap_mode = false;
76 boost::uuids::uuid selected_party_id;
77 std::vector<PartyInfo> available_parties;
82 std::string http_base_url;
83};
84
89 bool success = false;
90 QString error_message;
91 QString username;
92};
93
98 std::vector<iam::domain::session> sessions;
99 std::uint32_t total_count = 0;
100};
101
109class ClientManager : public QObject {
110 Q_OBJECT
111
112private:
113 inline static std::string_view logger_name =
114 "ores.qt.client_manager";
115
116 [[nodiscard]] static auto& lg() {
117 using namespace ores::logging;
118 static auto instance = make_logger(logger_name);
119 return instance;
120 }
121
122public:
123 // Standard timeout for quick NATS round-trips (queries, lookups).
124 static constexpr std::chrono::seconds fast_timeout{30};
125 // Extended timeout for slow server-side operations (provisioning, imports).
126 static constexpr std::chrono::seconds slow_timeout{120};
127
128 explicit ClientManager(std::shared_ptr<eventing::service::event_bus> event_bus,
129 QObject* parent = nullptr);
130 ~ClientManager() override;
131
138 void setSubjectPrefix(const std::string& prefix) { subject_prefix_ = prefix; }
139
143 const std::string& subjectPrefix() const { return subject_prefix_; }
144
148 LoginResult connect(const std::string& host, std::uint16_t port);
149
153 LoginResult login(const std::string& username, const std::string& password);
154
159 const std::string& host,
160 std::uint16_t port,
161 const std::string& username,
162 const std::string& password);
163
168 const std::string& host,
169 std::uint16_t port,
170 const std::string& username,
171 const std::string& password);
172
177 const std::string& host,
178 std::uint16_t port,
179 const std::string& username,
180 const std::string& email,
181 const std::string& password);
182
186 void disconnect();
187
191 bool logout();
192
196 bool isConnected() const;
197
201 [[deprecated("Permission checks are now server-side via RBAC")]]
202 bool isAdmin() const { return false; }
203
207 bool isLoggedIn() const { return session_.is_logged_in(); }
208
215
219 std::string currentUsername() const {
220 if (!session_.is_logged_in()) return {};
221 return session_.auth().username;
222 }
223
227 std::string currentEmail() const { return current_email_; }
228
232 void setCurrentEmail(const std::string& email) { current_email_ = email; }
233
237 std::optional<boost::uuids::uuid> accountId() const { return current_account_id_; }
238
242 std::string serverAddress() const {
243 if (!isConnected()) return "";
244 return connected_host_ + ":" + std::to_string(connected_port_);
245 }
246
250 std::string connectedHost() const {
251 if (!isConnected()) return "";
252 return connected_host_;
253 }
254
258 std::uint16_t connectedPort() const {
259 if (!isConnected()) return 0;
260 return connected_port_;
261 }
262
266 std::string storedUsername() const { return stored_username_; }
267
271 std::string storedPassword() const { return stored_password_; }
272
276 boost::uuids::uuid currentPartyId() const { return current_party_id_; }
277
281 QString currentPartyName() const { return current_party_name_; }
282
286 QString currentPartyCategory() const { return current_party_category_; }
287
291 bool isSystemParty() const { return current_party_category_ == "System"; }
292
296 bool selectParty(const boost::uuids::uuid& party_id, const QString& party_name);
297
308 template <nats_request RequestType>
309 auto process_request(RequestType request)
310 -> std::expected<typename RequestType::response_type, std::string> {
311 using ResponseType = typename RequestType::response_type;
312 try {
313 const auto json_body = rfl::json::write(request);
314 auto msg = session_.request(RequestType::nats_subject, json_body);
315 const std::string_view data(
316 reinterpret_cast<const char*>(msg.data.data()),
317 msg.data.size());
318 auto result = rfl::json::read<ResponseType>(data);
319 if (!result) {
320 return std::unexpected(
321 std::string("Failed to deserialize response: ") +
322 result.error().what());
323 }
324 return std::move(*result);
325 } catch (const std::exception& e) {
326 return std::unexpected(std::string(e.what()));
327 }
328 }
329
340 template <nats_request RequestType>
341 auto process_authenticated_request(RequestType request,
342 std::chrono::milliseconds timeout = std::chrono::seconds(30))
343 -> std::expected<typename RequestType::response_type, std::string> {
344 using ResponseType = typename RequestType::response_type;
345 if (!session_.is_logged_in()) {
346 return std::unexpected(std::string("Not logged in"));
347 }
348 try {
349 const auto json_body = rfl::json::write(request);
350 auto msg = session_.authenticated_request(
351 RequestType::nats_subject, json_body, timeout);
352 const std::string_view data(
353 reinterpret_cast<const char*>(msg.data.data()),
354 msg.data.size());
355 auto result = rfl::json::read<ResponseType>(data);
356 if (!result) {
357 return std::unexpected(
358 std::string("Failed to deserialize response: ") +
359 result.error().what());
360 }
361 return std::move(*result);
363 using namespace ores::logging;
364 BOOST_LOG_SEV(lg(), warn) << "Session expired: " << e.what();
365 QMetaObject::invokeMethod(this, "sessionExpired", Qt::QueuedConnection);
366 return std::unexpected(std::string(e.what()));
367 } catch (const std::exception& e) {
368 return std::unexpected(std::string(e.what()));
369 }
370 }
371
375 std::optional<SessionListResult> listSessions(
376 const boost::uuids::uuid& accountId,
377 std::uint32_t limit = 100,
378 std::uint32_t offset = 0);
379
383 std::optional<std::vector<iam::domain::session>> getActiveSessions();
384
388 std::optional<std::vector<iam::messaging::session_sample_dto>>
389 getSessionSamples(const boost::uuids::uuid& sessionId);
390
391 // =========================================================================
392 // Connection Status Accessors (stubbed for NATS - not all available)
393 // =========================================================================
394
395 std::uint64_t bytesSent() const { return 0; }
396 std::uint64_t bytesReceived() const { return 0; }
397 std::uint64_t lastRttMs() const { return 0; }
398
399 std::optional<std::chrono::steady_clock::time_point> disconnectedSince() const {
400 return disconnected_since_;
401 }
402
403 // =========================================================================
404 // Session Recording (stubbed - not applicable for NATS)
405 // =========================================================================
406
407 bool enableRecording(const std::filesystem::path&) { return false; }
408 void disableRecording() {}
409 bool isRecording() const { return false; }
410 std::filesystem::path recordingFilePath() const { return {}; }
411 void setRecordingDirectory(const std::filesystem::path& dir) {
412 recording_directory_ = dir;
413 }
414 std::filesystem::path recordingDirectory() const {
415 return recording_directory_;
416 }
417
418 // =========================================================================
419 // Event Subscriptions
420 // =========================================================================
421
422 void subscribeToEvent(const std::string& subject);
423 void unsubscribeFromEvent(const std::string& subject);
424
425signals:
426 void connected();
427 void loggedIn();
428 void disconnected();
429 void reconnecting();
430 void reconnected();
431 void connectionError(const QString& message);
432
441
442 void notificationReceived(const QString& eventType, const QDateTime& timestamp,
443 const QStringList& entityIds, const QString& tenantId,
444 int payloadType, const QByteArray& payload);
445
446 void recordingStarted(const QString& filePath);
447 void recordingStopped();
448 void streamingStarted();
449 void streamingStopped();
450
451private:
452 // Fraction of token lifetime at which the proactive refresh fires
453 static constexpr double refresh_lifetime_ratio = 0.8;
454
462 void arm_refresh_timer(int lifetime_s);
463
470 void onRefreshTimer();
471
472 // NATS session for connection and authentication
474
475 // Subject prefix applied to all outbound NATS messages
476 std::string subject_prefix_;
477
478 // Event bus for publishing connection events
479 std::shared_ptr<eventing::service::event_bus> event_bus_;
480
481 // Connection details
482 std::string connected_host_;
483 std::uint16_t connected_port_{0};
484
485 // Session recording directory
486 std::filesystem::path recording_directory_;
487
488 // Time point when the connection was lost
489 std::optional<std::chrono::steady_clock::time_point> disconnected_since_;
490
491 // Stored credentials for display/reconnect
492 std::string stored_username_;
493 std::string stored_password_;
494
495 // Current account info (from login)
496 std::optional<boost::uuids::uuid> current_account_id_;
497 std::string current_email_;
498
499 // Currently selected party context
500 boost::uuids::uuid current_party_id_;
501 QString current_party_name_;
502 QString current_party_category_;
503
504 // Active NATS event subscriptions keyed by subject
505 std::unordered_map<std::string, nats::service::subscription> nats_subscriptions_;
506
507 // Proactive token refresh timer (fires before token expires)
508 QTimer* refresh_timer_ = nullptr;
509};
510
511}
512
513#endif
Implements logging infrastructure for ORE Studio.
Definition boost_severity.hpp:28
Qt-based graphical user interface for ORE Studio.
Definition AboutDialog.hpp:29
JetStream management API.
Definition jetstream_admin.hpp:53
Authenticated NATS client for both interactive and service-to-service use.
Definition nats_client.hpp:71
const login_info & auth() const
Return current auth info.
Definition nats_client.cpp:75
message request(std::string_view subject, std::string_view json_body)
Unauthenticated synchronous request.
Definition nats_client.cpp:89
message authenticated_request(std::string_view subject, std::string_view json_body, std::chrono::milliseconds timeout=std::chrono::seconds(30))
Authenticated synchronous request — string body overload.
Definition nats_client.cpp:136
Thrown when a session reaches its maximum allowed duration.
Definition session_expired_error.hpp:34
Summary of a party the user can select during login.
Definition ClientManager.hpp:62
Result of a login attempt.
Definition ClientManager.hpp:70
std::string http_base_url
HTTP base URL discovered from the HTTP server via NATS. Empty if discovery failed or the HTTP server ...
Definition ClientManager.hpp:82
Result of a signup attempt.
Definition ClientManager.hpp:88
Result of a session list request.
Definition ClientManager.hpp:97
Manages the lifecycle of the NATS client and login state.
Definition ClientManager.hpp:109
bool logout()
Logout the current user without disconnecting.
Definition ClientManager.cpp:433
LoginResult login(const std::string &username, const std::string &password)
Login on an already connected client.
Definition ClientManager.cpp:106
QString currentPartyName() const
Get the name of the currently selected party.
Definition ClientManager.hpp:281
std::string serverAddress() const
Get the server address string.
Definition ClientManager.hpp:242
std::string currentUsername() const
Get the current logged-in user's username.
Definition ClientManager.hpp:219
void sessionExpired()
Emitted when the session can no longer be refreshed.
LoginResult connect(const std::string &host, std::uint16_t port)
Connect to the NATS server without logging in.
Definition ClientManager.cpp:57
bool isSystemParty() const
Returns true if the currently selected party is the system party.
Definition ClientManager.hpp:291
boost::uuids::uuid currentPartyId() const
Get the UUID of the currently selected party.
Definition ClientManager.hpp:276
std::uint16_t connectedPort() const
Get the connected server port.
Definition ClientManager.hpp:258
std::optional< SessionListResult > listSessions(const boost::uuids::uuid &accountId, std::uint32_t limit=100, std::uint32_t offset=0)
List sessions for an account.
Definition ClientManager.cpp:490
LoginResult connectAndLogin(const std::string &host, std::uint16_t port, const std::string &username, const std::string &password)
Connect to the server and perform login.
Definition ClientManager.cpp:249
std::optional< std::vector< iam::messaging::session_sample_dto > > getSessionSamples(const boost::uuids::uuid &sessionId)
Get time-series samples for a session.
Definition ClientManager.cpp:542
bool isConnected() const
Check if currently connected.
Definition ClientManager.cpp:450
std::string storedUsername() const
Get the stored username used for the current session.
Definition ClientManager.hpp:266
void disconnect()
Logout the current user and disconnect from the server.
Definition ClientManager.cpp:408
std::string connectedHost() const
Get the connected server hostname.
Definition ClientManager.hpp:250
SignupResult signup(const std::string &host, std::uint16_t port, const std::string &username, const std::string &email, const std::string &password)
Connect to the server and attempt signup.
Definition ClientManager.cpp:302
nats::service::jetstream_admin admin()
Create a JetStream admin handle for managing streams and consumers.
Definition ClientManager.cpp:534
bool isLoggedIn() const
Check if currently logged in.
Definition ClientManager.hpp:207
std::string currentEmail() const
Get the current logged-in user's email.
Definition ClientManager.hpp:227
void setCurrentEmail(const std::string &email)
Set the current logged-in user's email.
Definition ClientManager.hpp:232
auto process_request(RequestType request) -> std::expected< typename RequestType::response_type, std::string >
Process a request that does not require authentication.
Definition ClientManager.hpp:309
void setSubjectPrefix(const std::string &prefix)
Set the NATS subject prefix used for all outbound messages.
Definition ClientManager.hpp:138
std::optional< boost::uuids::uuid > accountId() const
Get the account ID if logged in.
Definition ClientManager.hpp:237
std::string storedPassword() const
Get the stored password used for the current session.
Definition ClientManager.hpp:271
bool isAdmin() const
Definition ClientManager.hpp:202
QString currentPartyCategory() const
Get the category of the currently selected party.
Definition ClientManager.hpp:286
bool selectParty(const boost::uuids::uuid &party_id, const QString &party_name)
Select a party for the current session.
Definition ClientManager.cpp:454
LoginResult testConnection(const std::string &host, std::uint16_t port, const std::string &username, const std::string &password)
Test a connection without affecting main client state.
Definition ClientManager.cpp:262
const std::string & subjectPrefix() const
Get the current NATS subject prefix.
Definition ClientManager.hpp:143
std::optional< std::vector< iam::domain::session > > getActiveSessions()
Get active sessions for the current user.
Definition ClientManager.cpp:519
auto process_authenticated_request(RequestType request, std::chrono::milliseconds timeout=std::chrono::seconds(30)) -> std::expected< typename RequestType::response_type, std::string >
Process a request that requires authentication.
Definition ClientManager.hpp:341
Concept for NATS-aware request types.
Definition ClientManager.hpp:54