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 bool party_setup_required = false;
77 boost::uuids::uuid selected_party_id;
78 std::vector<PartyInfo> available_parties;
83 std::string http_base_url;
84};
85
90 bool success = false;
91 QString error_message;
92 QString username;
93};
94
99 std::vector<iam::domain::session> sessions;
100 std::uint32_t total_count = 0;
101};
102
110class ClientManager : public QObject {
111 Q_OBJECT
112
113private:
114 inline static std::string_view logger_name =
115 "ores.qt.client_manager";
116
117 [[nodiscard]] static auto& lg() {
118 using namespace ores::logging;
119 static auto instance = make_logger(logger_name);
120 return instance;
121 }
122
123public:
124 // Standard timeout for quick NATS round-trips (queries, lookups).
125 static constexpr std::chrono::seconds fast_timeout{30};
126 // Extended timeout for slow server-side operations (provisioning, imports).
127 static constexpr std::chrono::seconds slow_timeout{120};
128
129 explicit ClientManager(std::shared_ptr<eventing::service::event_bus> event_bus,
130 QObject* parent = nullptr);
131 ~ClientManager() override;
132
139 void setSubjectPrefix(const std::string& prefix) { subject_prefix_ = prefix; }
140
144 const std::string& subjectPrefix() const { return subject_prefix_; }
145
149 LoginResult connect(const std::string& host, std::uint16_t port);
150
154 LoginResult login(const std::string& username, const std::string& password);
155
160 const std::string& host,
161 std::uint16_t port,
162 const std::string& username,
163 const std::string& password);
164
169 const std::string& host,
170 std::uint16_t port,
171 const std::string& username,
172 const std::string& password);
173
178 const std::string& host,
179 std::uint16_t port,
180 const std::string& username,
181 const std::string& email,
182 const std::string& password);
183
187 void disconnect();
188
192 bool logout();
193
197 bool isConnected() const;
198
202 [[deprecated("Permission checks are now server-side via RBAC")]]
203 bool isAdmin() const { return false; }
204
208 bool isLoggedIn() const { return session_.is_logged_in(); }
209
216
220 std::string currentUsername() const {
221 if (!session_.is_logged_in()) return {};
222 return session_.auth().username;
223 }
224
228 std::string currentEmail() const { return current_email_; }
229
233 void setCurrentEmail(const std::string& email) { current_email_ = email; }
234
238 std::optional<boost::uuids::uuid> accountId() const { return current_account_id_; }
239
243 std::string serverAddress() const {
244 if (!isConnected()) return "";
245 return connected_host_ + ":" + std::to_string(connected_port_);
246 }
247
251 std::string connectedHost() const {
252 if (!isConnected()) return "";
253 return connected_host_;
254 }
255
259 std::uint16_t connectedPort() const {
260 if (!isConnected()) return 0;
261 return connected_port_;
262 }
263
267 std::string storedUsername() const { return stored_username_; }
268
272 std::string storedPassword() const { return stored_password_; }
273
277 boost::uuids::uuid currentPartyId() const { return current_party_id_; }
278
282 QString currentPartyName() const { return current_party_name_; }
283
287 QString currentPartyCategory() const { return current_party_category_; }
288
292 bool isSystemParty() const { return current_party_category_ == "System"; }
293
297 bool lastPartySetupRequired() const { return last_party_setup_required_; }
298
302 bool selectParty(const boost::uuids::uuid& party_id, const QString& party_name);
303
314 template <nats_request RequestType>
315 auto process_request(RequestType request)
316 -> std::expected<typename RequestType::response_type, std::string> {
317 using ResponseType = typename RequestType::response_type;
318 try {
319 const auto json_body = rfl::json::write(request);
320 auto msg = session_.request(RequestType::nats_subject, json_body);
321 const std::string_view data(
322 reinterpret_cast<const char*>(msg.data.data()),
323 msg.data.size());
324 auto result = rfl::json::read<ResponseType>(data);
325 if (!result) {
326 return std::unexpected(
327 std::string("Failed to deserialize response: ") +
328 result.error().what());
329 }
330 return std::move(*result);
331 } catch (const std::exception& e) {
332 return std::unexpected(std::string(e.what()));
333 }
334 }
335
346 template <nats_request RequestType>
347 auto process_authenticated_request(RequestType request,
348 std::chrono::milliseconds timeout = std::chrono::seconds(30))
349 -> std::expected<typename RequestType::response_type, std::string> {
350 using ResponseType = typename RequestType::response_type;
351 if (!session_.is_logged_in()) {
352 return std::unexpected(std::string("Not logged in"));
353 }
354 try {
355 const auto json_body = rfl::json::write(request);
356 auto msg = session_.authenticated_request(
357 RequestType::nats_subject, json_body, timeout);
358 const std::string_view data(
359 reinterpret_cast<const char*>(msg.data.data()),
360 msg.data.size());
361 auto result = rfl::json::read<ResponseType>(data);
362 if (!result) {
363 return std::unexpected(
364 std::string("Failed to deserialize response: ") +
365 result.error().what());
366 }
367 return std::move(*result);
369 using namespace ores::logging;
370 BOOST_LOG_SEV(lg(), warn) << "Session expired: " << e.what();
371 QMetaObject::invokeMethod(this, "sessionExpired", Qt::QueuedConnection);
372 return std::unexpected(std::string(e.what()));
373 } catch (const std::exception& e) {
374 return std::unexpected(std::string(e.what()));
375 }
376 }
377
381 std::optional<SessionListResult> listSessions(
382 const boost::uuids::uuid& accountId,
383 std::uint32_t limit = 100,
384 std::uint32_t offset = 0);
385
389 std::optional<std::vector<iam::domain::session>> getActiveSessions();
390
394 std::optional<std::vector<iam::messaging::session_sample_dto>>
395 getSessionSamples(const boost::uuids::uuid& sessionId);
396
397 // =========================================================================
398 // Connection Status Accessors (stubbed for NATS - not all available)
399 // =========================================================================
400
401 std::uint64_t bytesSent() const { return 0; }
402 std::uint64_t bytesReceived() const { return 0; }
403 std::uint64_t lastRttMs() const { return 0; }
404
405 std::optional<std::chrono::steady_clock::time_point> disconnectedSince() const {
406 return disconnected_since_;
407 }
408
409 // =========================================================================
410 // Session Recording (stubbed - not applicable for NATS)
411 // =========================================================================
412
413 bool enableRecording(const std::filesystem::path&) { return false; }
414 void disableRecording() {}
415 bool isRecording() const { return false; }
416 std::filesystem::path recordingFilePath() const { return {}; }
417 void setRecordingDirectory(const std::filesystem::path& dir) {
418 recording_directory_ = dir;
419 }
420 std::filesystem::path recordingDirectory() const {
421 return recording_directory_;
422 }
423
424 // =========================================================================
425 // Event Subscriptions
426 // =========================================================================
427
428 void subscribeToEvent(const std::string& subject);
429 void unsubscribeFromEvent(const std::string& subject);
430
431signals:
432 void connected();
433 void loggedIn();
434 void disconnected();
435 void reconnecting();
436 void reconnected();
437 void connectionError(const QString& message);
438
447
448 void notificationReceived(const QString& eventType, const QDateTime& timestamp,
449 const QStringList& entityIds, const QString& tenantId,
450 int payloadType, const QByteArray& payload);
451
452 void recordingStarted(const QString& filePath);
453 void recordingStopped();
454 void streamingStarted();
455 void streamingStopped();
456
457private:
458 // Fraction of token lifetime at which the proactive refresh fires
459 static constexpr double refresh_lifetime_ratio = 0.8;
460
468 void arm_refresh_timer(int lifetime_s);
469
476 void onRefreshTimer();
477
478 // NATS session for connection and authentication
480
481 // Subject prefix applied to all outbound NATS messages
482 std::string subject_prefix_;
483
484 // Event bus for publishing connection events
485 std::shared_ptr<eventing::service::event_bus> event_bus_;
486
487 // Connection details
488 std::string connected_host_;
489 std::uint16_t connected_port_{0};
490
491 // Session recording directory
492 std::filesystem::path recording_directory_;
493
494 // Time point when the connection was lost
495 std::optional<std::chrono::steady_clock::time_point> disconnected_since_;
496
497 // Stored credentials for display/reconnect
498 std::string stored_username_;
499 std::string stored_password_;
500
501 // Current account info (from login)
502 std::optional<boost::uuids::uuid> current_account_id_;
503 std::string current_email_;
504
505 // Currently selected party context
506 boost::uuids::uuid current_party_id_;
507 QString current_party_name_;
508 QString current_party_category_;
509 bool last_party_setup_required_ = false;
510
511 // Active NATS event subscriptions keyed by subject
512 std::unordered_map<std::string, nats::service::subscription> nats_subscriptions_;
513
514 // Proactive token refresh timer (fires before token expires)
515 QTimer* refresh_timer_ = nullptr;
516};
517
518}
519
520#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:72
const login_info & auth() const
Return current auth info.
Definition nats_client.cpp:76
message request(std::string_view subject, std::string_view json_body)
Unauthenticated synchronous request.
Definition nats_client.cpp:90
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:184
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:83
Result of a signup attempt.
Definition ClientManager.hpp:89
Result of a session list request.
Definition ClientManager.hpp:98
Manages the lifecycle of the NATS client and login state.
Definition ClientManager.hpp:110
bool logout()
Logout the current user without disconnecting.
Definition ClientManager.cpp:443
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:282
std::string serverAddress() const
Get the server address string.
Definition ClientManager.hpp:243
std::string currentUsername() const
Get the current logged-in user's username.
Definition ClientManager.hpp:220
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:292
boost::uuids::uuid currentPartyId() const
Get the UUID of the currently selected party.
Definition ClientManager.hpp:277
std::uint16_t connectedPort() const
Get the connected server port.
Definition ClientManager.hpp:259
bool lastPartySetupRequired() const
Whether the last selectParty call indicated the party needs setup.
Definition ClientManager.hpp:297
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:502
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:259
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:554
bool isConnected() const
Check if currently connected.
Definition ClientManager.cpp:460
std::string storedUsername() const
Get the stored username used for the current session.
Definition ClientManager.hpp:267
void disconnect()
Logout the current user and disconnect from the server.
Definition ClientManager.cpp:418
std::string connectedHost() const
Get the connected server hostname.
Definition ClientManager.hpp:251
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:312
nats::service::jetstream_admin admin()
Create a JetStream admin handle for managing streams and consumers.
Definition ClientManager.cpp:546
bool isLoggedIn() const
Check if currently logged in.
Definition ClientManager.hpp:208
std::string currentEmail() const
Get the current logged-in user's email.
Definition ClientManager.hpp:228
void setCurrentEmail(const std::string &email)
Set the current logged-in user's email.
Definition ClientManager.hpp:233
auto process_request(RequestType request) -> std::expected< typename RequestType::response_type, std::string >
Process a request that does not require authentication.
Definition ClientManager.hpp:315
void setSubjectPrefix(const std::string &prefix)
Set the NATS subject prefix used for all outbound messages.
Definition ClientManager.hpp:139
std::optional< boost::uuids::uuid > accountId() const
Get the account ID if logged in.
Definition ClientManager.hpp:238
std::string storedPassword() const
Get the stored password used for the current session.
Definition ClientManager.hpp:272
bool isAdmin() const
Definition ClientManager.hpp:203
QString currentPartyCategory() const
Get the category of the currently selected party.
Definition ClientManager.hpp:287
bool selectParty(const boost::uuids::uuid &party_id, const QString &party_name)
Select a party for the current session.
Definition ClientManager.cpp:464
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:272
const std::string & subjectPrefix() const
Get the current NATS subject prefix.
Definition ClientManager.hpp:144
std::optional< std::vector< iam::domain::session > > getActiveSessions()
Get active sessions for the current user.
Definition ClientManager.cpp:531
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:347
Concept for NATS-aware request types.
Definition ClientManager.hpp:54