From e29cd0aebfb471b133be4feb959196f7a4a4cf45 Mon Sep 17 00:00:00 2001 From: Bent Witthold Date: Sun, 25 Jan 2026 10:47:19 +0100 Subject: [PATCH] Basic JSON RESTful client fetching items from a local server at application start and adding them to the model. --- CMakeLists.txt | 9 +++-- genericcore.cpp | 44 +++++++++++++++++++++++++ genericcore.h | 13 ++++++++ model/metadata.h | 3 ++ model/tablemodel.cpp | 6 ++++ network/apiroutes.h | 13 ++++++++ network/servercommunicator.cpp | 60 ++++++++++++++++++++++++++++++++++ network/servercommunicator.h | 33 +++++++++++++++++++ 8 files changed, 178 insertions(+), 3 deletions(-) create mode 100644 network/apiroutes.h create mode 100644 network/servercommunicator.cpp create mode 100644 network/servercommunicator.h diff --git a/CMakeLists.txt b/CMakeLists.txt index a275e08..da30dcc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,6 +12,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) find_package(QT NAMES Qt6 REQUIRED COMPONENTS Core LinguistTools) find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core LinguistTools Gui) find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Test) +find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Network) configure_file(CoreConfig.h.in CoreConfig.h) @@ -32,16 +33,18 @@ add_library(${TARGET_APP} STATIC data/filehandler.h data/filehandler.cpp model/metadata.h formats/csvparser.h formats/csvparser.cpp + model/generalsortfiltermodel.h model/generalsortfiltermodel.cpp + network/servercommunicator.h network/servercommunicator.cpp + network/apiroutes.h # 3rd party libraries ../3rdParty/rapidcsv/src/rapidcsv.h - model/generalsortfiltermodel.h model/generalsortfiltermodel.cpp ) - -target_link_libraries(GenericCore PRIVATE Qt${QT_VERSION_MAJOR}::Test) include_directories(${CMAKE_CURRENT_BINARY_DIR}) target_link_libraries(${TARGET_APP} PRIVATE Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Gui) +target_link_libraries(GenericCore PRIVATE Qt${QT_VERSION_MAJOR}::Test) +target_link_libraries(GenericCore PRIVATE Qt${QT_VERSION_MAJOR}::Network) target_compile_definitions(${TARGET_APP} PRIVATE ${TARGET_APP}_LIBRARY) diff --git a/genericcore.cpp b/genericcore.cpp index 38046a3..c26c2cc 100644 --- a/genericcore.cpp +++ b/genericcore.cpp @@ -16,6 +16,7 @@ #include "model/generalsortfiltermodel.h" #include "model/metadata.h" #include "model/tablemodel.h" +#include "network/servercommunicator.h" #include @@ -37,6 +38,7 @@ GenericCore::GenericCore() { m_modelUndoStack = new QUndoStack(this); setupModels(); + setupServerConfiguration(); } GenericCore::~GenericCore() { qDebug() << "Destroying core..."; } @@ -132,6 +134,32 @@ bool GenericCore::exportCSVFile(const QString& filePath) { return FileHandler::exportToCSVFile(itemsAsStringLists, filePath); } +bool GenericCore::isSyncServerSetup() const { + if (m_serverCommunicator) { + return true; + } else { + return false; + } +} + +void GenericCore::onItemsFetched(const QByteArray jsonDoc) { + emit displayStatusMessage("New items fetched."); + // TODO ? check compability of JSON structure beforehand? + m_mainModel->appendItems(jsonDoc); +} + +void GenericCore::onItemsFetchFailure(const QString errorString) { + emit displayStatusMessage(QString("Error: %1").arg(errorString)); +} + +void GenericCore::onPostRequestSuccessful(const QString message) { + emit displayStatusMessage(message); +} + +void GenericCore::onPostRequestFailure(const QString errorString) { + emit displayStatusMessage(QString("Error: %1").arg(errorString)); +} + void GenericCore::setupModels() { m_mainModel = make_shared(m_modelUndoStack, this); m_sortFilterModel = make_shared(m_mainModel); @@ -188,3 +216,19 @@ QString GenericCore::getMaintenanceToolFilePath() const { const QString filePath = applicationDirPath + "/" + UPDATER_EXE; return filePath; } + +void GenericCore::setupServerConfiguration() { + m_serverCommunicator = make_unique(this); + + connect(m_serverCommunicator.get(), &ServerCommunicator::itemsFetched, this, + &GenericCore::onItemsFetched); + connect(m_serverCommunicator.get(), &ServerCommunicator::itemsFetchFailure, this, + &GenericCore::onItemsFetchFailure); + connect(m_serverCommunicator.get(), &ServerCommunicator::postRequestSuccessful, this, + &GenericCore::onPostRequestSuccessful); + connect(m_serverCommunicator.get(), &ServerCommunicator::postRequestFailure, this, + &GenericCore::onPostRequestFailure); + + /// fetching items on startup to test the communication with the server + m_serverCommunicator->fetchItems(); +} diff --git a/genericcore.h b/genericcore.h index a7df414..a916647 100644 --- a/genericcore.h +++ b/genericcore.h @@ -10,6 +10,7 @@ class QString; class TableModel; class GeneralSortFilterModel; +class ServerCommunicator; class GenericCore : public QObject { Q_OBJECT @@ -32,6 +33,14 @@ class GenericCore : public QObject { void importCSVFile(const QString& filePath); bool exportCSVFile(const QString& filePath); + bool isSyncServerSetup() const; + + public slots: + void onItemsFetched(const QByteArray jsonDoc); + void onItemsFetchFailure(const QString errorString); + void onPostRequestSuccessful(const QString message); + void onPostRequestFailure(const QString errorString); + signals: void displayStatusMessage(QString message); @@ -46,6 +55,10 @@ class GenericCore : public QObject { void initModelData(); QString getMaintenanceToolFilePath() const; + + /// Network communication + std::unique_ptr m_serverCommunicator; + void setupServerConfiguration(); }; #endif // GENERICCORE_H diff --git a/model/metadata.h b/model/metadata.h index b0bb4b8..91da345 100644 --- a/model/metadata.h +++ b/model/metadata.h @@ -6,6 +6,8 @@ #include #include +// TODO add namespace + /// model data enum UserRoles { NameRole = Qt::UserRole + 1, @@ -16,6 +18,7 @@ enum UserRoles { /// helper roles ToStringRole }; + static UserRoles DEFAULT_ROLE = NameRole; static QList USER_FACING_ROLES = {NameRole, DescriptionRole, InfoRole, AmountRole, FactorRole}; diff --git a/model/tablemodel.cpp b/model/tablemodel.cpp index 08941b6..e22a151 100644 --- a/model/tablemodel.cpp +++ b/model/tablemodel.cpp @@ -193,6 +193,12 @@ void TableModel::insertItems(int startPosition, const QModelIndex& parentIndex) { const QList valueList = JsonParser::toItemValuesList(jsonDoc, ITEM_KEY_STRING); + if (valueList.empty()) { + /// don't try inserting if no values to insert + qDebug() << "No items found in JSON document. Not adding anything..."; + return; + } + insertItems(startPosition, valueList, parentIndex); } diff --git a/network/apiroutes.h b/network/apiroutes.h new file mode 100644 index 0000000..70ef34e --- /dev/null +++ b/network/apiroutes.h @@ -0,0 +1,13 @@ +#ifndef APIROUTES_H +#define APIROUTES_H + +#include + +// TODO add namespace + +static const QString baseUrl = "http://127.0.0.1:4000"; +static const QString apiPrefix = "/api/"; + +static const QString ROUTE_ITEMS = apiPrefix + "items"; + +#endif // APIROUTES_H diff --git a/network/servercommunicator.cpp b/network/servercommunicator.cpp new file mode 100644 index 0000000..89e07d8 --- /dev/null +++ b/network/servercommunicator.cpp @@ -0,0 +1,60 @@ +#include "servercommunicator.h" +#include "apiroutes.h" + +#include +#include +#include +#include + +using namespace Qt::StringLiterals; + +ServerCommunicator::ServerCommunicator(QObject* parent) + : QObject{parent} { + m_netManager.setAutoDeleteReplies(true); + m_restManager = std::make_shared(&m_netManager); + m_serviceApi = std::make_shared(); + setUrl(baseUrl); +} + +bool ServerCommunicator::sslSupported() { +#if QT_CONFIG(ssl) + return QSslSocket::supportsSsl(); +#else + return false; +#endif +} + +QUrl ServerCommunicator::url() const { return m_serviceApi->baseUrl(); } + +void ServerCommunicator::setUrl(const QUrl& url) { + if (m_serviceApi->baseUrl() == url) { + return; + } + m_serviceApi->setBaseUrl(url); + QHttpHeaders authenticationHeaders; + m_serviceApi->setCommonHeaders(authenticationHeaders); + emit urlChanged(); +} + +void ServerCommunicator::fetchItems() { + /// Set up a GET request + m_restManager->get(m_serviceApi->createRequest(ROUTE_ITEMS), this, [this](QRestReply& reply) { + if (reply.isSuccess()) { + qInfo() << "Fetching items successful."; + const QJsonDocument doc = reply.readJson().value(); + emit itemsFetched(doc.toJson()); + + } else { + if (reply.hasError()) { + const QString errorString = reply.errorString(); + qCritical() << "ERROR:" << errorString; + emit itemsFetchFailure(errorString); + } else { + int statusCode = reply.httpStatus(); + qCritical() << "ERROR:" << statusCode; + emit itemsFetchFailure(QString::number(statusCode)); + emit itemsFetchFailure(QString("HTTP status code: %1").arg(statusCode)); + } + } + }); +} diff --git a/network/servercommunicator.h b/network/servercommunicator.h new file mode 100644 index 0000000..5c23469 --- /dev/null +++ b/network/servercommunicator.h @@ -0,0 +1,33 @@ +#ifndef SERVERCOMMUNICATOR_H +#define SERVERCOMMUNICATOR_H + +#include +#include +#include +#include + +class ServerCommunicator : public QObject { + Q_OBJECT + public: + explicit ServerCommunicator(QObject* parent = nullptr); + + bool sslSupported(); + + QUrl url() const; + void setUrl(const QUrl& url); + + void fetchItems(); + + signals: + void urlChanged(); + + void itemsFetched(const QByteArray jsonDoc); + void itemsFetchFailure(const QString errorString); + + private: + QNetworkAccessManager m_netManager; + std::shared_ptr m_restManager; + std::shared_ptr m_serviceApi; +}; + +#endif // SERVERCOMMUNICATOR_H