diff --git a/data/settingshandler.cpp b/data/settingshandler.cpp index eb11006..691854b 100644 --- a/data/settingshandler.cpp +++ b/data/settingshandler.cpp @@ -40,4 +40,48 @@ void SettingsHandler::saveSettings(QVariantMap settingMap, QString group) { settings.sync(); } +void SettingsHandler::deleteSettings(QStringList keys, QString group) { + qInfo() << "deleting settings..."; + + QSettings settings; + if (!group.isEmpty()) { + qDebug() << "starting group:" << group; + settings.beginGroup(group); + } + + foreach (QString key, keys) { + qDebug() << "removing:" << key; + settings.remove(key); + } + if (!group.isEmpty()) { + settings.endGroup(); + } + + settings.sync(); +} + +QVariantMap SettingsHandler::getChangeset(QVariantMap newSettings, QString group) { + const QVariantMap oldSettings = getSettings(group); + + QVariantMap result; + + for (QVariantMap::const_iterator iter = newSettings.begin(); iter != newSettings.end(); ++iter) { + qDebug() << iter.key() << iter.value(); + QString key = iter.key(); + QVariant newValue = iter.value(); + QVariant oldValue = oldSettings.value(key); + + if (oldValue == newValue) { + qInfo() << "oldValue == newValue -> ignoring..."; + } else { + const QString debugString = + QString("oldValue != newValue -> adding '%1' to changeset...").arg(key); + qInfo() << debugString; + result.insert(key, newValue); + } + } + + return result; +} + SettingsHandler::SettingsHandler() {} diff --git a/data/settingshandler.h b/data/settingshandler.h index a636cf6..3488939 100644 --- a/data/settingshandler.h +++ b/data/settingshandler.h @@ -7,6 +7,9 @@ class SettingsHandler { public: static QVariantMap getSettings(QString group = ""); static void saveSettings(QVariantMap settingMap, QString group = ""); + static void deleteSettings(QStringList keys, QString group = ""); + + static QVariantMap getChangeset(QVariantMap newSettings, QString group = ""); private: SettingsHandler(); diff --git a/formats/jsonparser.cpp b/formats/jsonparser.cpp index 559ea20..b41c1e2 100644 --- a/formats/jsonparser.cpp +++ b/formats/jsonparser.cpp @@ -74,6 +74,30 @@ QJsonObject JsonParser::itemValuesToJsonObject(const ModelItemValues& itemValues return result; } +QByteArray JsonParser::userCredentialsToJsonDocument(const QString email, const QString password) { + QJsonDocument jsonDoc; + QJsonObject rootObject; + + QJsonObject userObject; + userObject.insert("email", email); + userObject.insert("password", password); + + rootObject.insert("user", userObject); + jsonDoc.setObject(rootObject); + + return jsonDoc.toJson(QJsonDocument::Compact); +} + +QVariant JsonParser::getValueFromJson(const QByteArray& jsonData, + const QString key, + const QString objectName) { + QJsonDocument doc = QJsonDocument::fromJson(jsonData); + QJsonObject rootObject = doc.object(); + QJsonObject userObject = rootObject.value(objectName).toObject(); + + return userObject.value(key); +} + JsonParser::JsonParser() {} QJsonArray JsonParser::extractItemArray(const QJsonDocument& doc, const QString& objectName) { diff --git a/formats/jsonparser.h b/formats/jsonparser.h index ba2a95f..fa805af 100644 --- a/formats/jsonparser.h +++ b/formats/jsonparser.h @@ -20,6 +20,11 @@ class JsonParser { const QString& objectName = ""); static QJsonObject itemValuesToJsonObject(const ModelItemValues& itemValues); + static QByteArray userCredentialsToJsonDocument(const QString email, const QString password); + static QVariant getValueFromJson(const QByteArray& jsonData, + const QString key, + const QString objectName = ""); + private: explicit JsonParser(); diff --git a/genericcore.cpp b/genericcore.cpp index c21c655..c5d7f76 100644 --- a/genericcore.cpp +++ b/genericcore.cpp @@ -14,6 +14,7 @@ #include "constants.h" #include "data/filehandler.h" #include "data/settingshandler.h" +#include "formats/jsonparser.h" #include "model/generalsortfiltermodel.h" #include "model/metadata.h" #include "model/tablemodel.h" @@ -126,6 +127,24 @@ QVariantMap GenericCore::getSettings(QString group) const { } void GenericCore::applySettings(QVariantMap settingMap, QString group) { + const QVariantMap changeset = SettingsHandler::getChangeset(settingMap, group); + + if (changeset.isEmpty()) { + return; + } + + if (group == "Server") { + const bool urlChanged = changeset.contains("url"); + const bool emailChanged = changeset.contains("email"); + const bool passwordChanged = changeset.contains("password"); + if (urlChanged || emailChanged || passwordChanged) { + if (!changeset.contains("authToken")) { + qInfo() << "Account settings changed, but no new token present. Deleting old token..."; + SettingsHandler::deleteSettings({"authToken"}, "Server"); + } + } + } + SettingsHandler::saveSettings(settingMap, group); if (group == "Server") { @@ -158,8 +177,32 @@ void GenericCore::saveItems() { } } +void GenericCore::onLoginSuccessful(const QByteArray jsonData) { + emit displayStatusMessage("Login successful."); + qInfo() << "Storing auth token..."; + QString token = JsonParser::getValueFromJson(jsonData, "token", "user").toString(); + SettingsHandler::saveSettings({{"authToken", token}}, "Server"); + applyServerConfiguration(); +} + +void GenericCore::onLoginFailure(const QString errorString) { + emit displayStatusMessage(QString("Error: %1").arg(errorString)); +} + +void GenericCore::onNotAuthorized(const QString /*path*/) { + const QVariantMap serverSettings = SettingsHandler::getSettings("Server"); + const QString tokenValue = serverSettings.value("authToken").toString(); + if (!tokenValue.isEmpty()) { + SettingsHandler::deleteSettings({"authToken"}, "Server"); + displayStatusMessage("Not authorized! Deleted token. Please retry."); + } else { + displayStatusMessage("Not authorized! But no token was present. Please check your settings."); + } + applyServerConfiguration(); +} + void GenericCore::onSendItemTriggered(const QByteArray& jsonData) { - m_serverCommunicator->postItems(jsonData); + m_serverCommunicator->sendItem(jsonData); } void GenericCore::onItemsFetched(const QByteArray jsonData) { @@ -173,20 +216,20 @@ void GenericCore::onItemsFetchFailure(const QString errorString) { emit displayStatusMessage(QString("Error: %1").arg(errorString)); } -void GenericCore::onPostRequestSuccessful(const QByteArray responseData) { +void GenericCore::onSendItemSuccessful(const QByteArray responseData) { const QString message = m_mainModel->updateItemsFromJson(responseData); emit displayStatusMessage(message); } -void GenericCore::onPostRequestFailure(const QString errorString) { +void GenericCore::onSendItemFailure(const QString errorString) { emit displayStatusMessage(QString("Error: %1").arg(errorString)); } -void GenericCore::onDeleteRequestSuccessful(const QByteArray responseData) { +void GenericCore::onDeleteItemSuccessful(const QByteArray responseData) { qWarning() << "TODO: Process success response!!!"; } -void GenericCore::onDeleteRequestFailure(const QString errorString) { +void GenericCore::onDeleteItemFailure(const QString errorString) { qWarning() << "TODO: Process error response!!!"; } @@ -252,23 +295,32 @@ void GenericCore::setupServerCommunication() { /// request connections connect(this, &GenericCore::fetchItemsFromServer, m_serverCommunicator.get(), &ServerCommunicator::fetchItems); - connect(this, &GenericCore::postItemToServer, this, &GenericCore::onSendItemTriggered); + connect(this, &GenericCore::sendItemToServer, m_serverCommunicator.get(), + &ServerCommunicator::sendItem); connect(this, &GenericCore::deleteItemFromServer, m_serverCommunicator.get(), &ServerCommunicator::deleteItem); /// response connections + connect(m_serverCommunicator.get(), &ServerCommunicator::loginSuccessful, this, + &GenericCore::onLoginSuccessful); + connect(m_serverCommunicator.get(), &ServerCommunicator::loginFailure, this, + &GenericCore::onLoginFailure); + + connect(m_serverCommunicator.get(), &ServerCommunicator::notAuthorized, this, + &GenericCore::onNotAuthorized); + 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); - connect(m_serverCommunicator.get(), &ServerCommunicator::deleteRequestSuccessful, this, - &GenericCore::onDeleteRequestSuccessful); - connect(m_serverCommunicator.get(), &ServerCommunicator::deleteRequestFailure, this, - &GenericCore::onDeleteRequestFailure); + connect(m_serverCommunicator.get(), &ServerCommunicator::sendItemSuccessful, this, + &GenericCore::onSendItemSuccessful); + connect(m_serverCommunicator.get(), &ServerCommunicator::sendItemFailure, this, + &GenericCore::onSendItemFailure); + connect(m_serverCommunicator.get(), &ServerCommunicator::deleteItemSuccessful, this, + &GenericCore::onDeleteItemSuccessful); + connect(m_serverCommunicator.get(), &ServerCommunicator::deleteItemFailure, this, + &GenericCore::onDeleteItemFailure); applyServerConfiguration(); } @@ -276,8 +328,10 @@ void GenericCore::setupServerCommunication() { void GenericCore::applyServerConfiguration() { const QVariantMap serverSettings = SettingsHandler::getSettings("Server"); const QString urlValue = serverSettings.value("url").toString(); - // NEXT if urlValue is empty -> remove authToken from settings? - if (!urlValue.isEmpty()) { + if (urlValue.isEmpty()) { + SettingsHandler::deleteSettings({"authToken"}, "Server"); + } else { + /// urlValue in NOT empty const QString emailValue = serverSettings.value("email").toString(); const QString passwordValue = serverSettings.value("password").toString(); const QString authTokenValue = serverSettings.value("authToken").toString(); diff --git a/genericcore.h b/genericcore.h index 722b5cd..22df81a 100644 --- a/genericcore.h +++ b/genericcore.h @@ -39,18 +39,23 @@ class GenericCore : public QObject { public slots: void saveItems(); + void onLoginSuccessful(const QByteArray jsonData); + void onLoginFailure(const QString errorString); + + void onNotAuthorized(const QString /*path*/); + void onSendItemTriggered(const QByteArray& jsonData); void onItemsFetched(const QByteArray jsonData); void onItemsFetchFailure(const QString errorString); - void onPostRequestSuccessful(const QByteArray responseData); - void onPostRequestFailure(const QString errorString); - void onDeleteRequestSuccessful(const QByteArray responseData); - void onDeleteRequestFailure(const QString errorString); + void onSendItemSuccessful(const QByteArray responseData); + void onSendItemFailure(const QString errorString); + void onDeleteItemSuccessful(const QByteArray responseData); + void onDeleteItemFailure(const QString errorString); signals: void displayStatusMessage(QString message); void fetchItemsFromServer(); - void postItemToServer(const QByteArray& jsonData); + void sendItemToServer(const QByteArray& jsonData); void deleteItemFromServer(const QString& id); private: diff --git a/network/apiroutes.h b/network/apiroutes.h index e89da3a..12b25d6 100644 --- a/network/apiroutes.h +++ b/network/apiroutes.h @@ -7,6 +7,8 @@ static const QString apiPrefix = "/api/"; +static const QString ROUTE_LOG_IN = apiPrefix + "log_in"; + static const QString ROUTE_ITEMS = apiPrefix + "items"; #endif // APIROUTES_H diff --git a/network/servercommunicator.cpp b/network/servercommunicator.cpp index 8b683a2..1684bf1 100644 --- a/network/servercommunicator.cpp +++ b/network/servercommunicator.cpp @@ -6,6 +6,8 @@ #include #include +#include "../formats/jsonparser.h" + using namespace Qt::StringLiterals; ServerCommunicator::ServerCommunicator(QObject* parent) @@ -46,67 +48,176 @@ void ServerCommunicator::setServerConfiguration(const QString url, m_password = password; m_authToken = authToken; - if (!authToken.isEmpty()) { + if (authToken.isEmpty()) { + if (validLoginCredentials()) { + const QByteArray userCredentials = + JsonParser::userCredentialsToJsonDocument(m_email, m_password); + sendPostRequest(ROUTE_LOG_IN, userCredentials); + } + } else { + /// authToken not empty: m_serviceApi->setBearerToken(authToken.toLatin1()); } } -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()); +void ServerCommunicator::fetchItems() { sendGetRequest(ROUTE_ITEMS); } +void ServerCommunicator::sendItem(const QByteArray& jsonData) { + sendPostRequest(ROUTE_ITEMS, jsonData); +} + +void ServerCommunicator::deleteItem(const QString& id) { + const QString path = QString("%1/%2").arg(ROUTE_ITEMS, id); + sendDeleteRequest(path); +} + +bool ServerCommunicator::validLoginCredentials() { + if (url().isEmpty()) { + return false; + } + if (m_email.isEmpty()) { + return false; + } + if (m_password.isEmpty()) { + return false; + } + return true; +} + +void ServerCommunicator::sendGetRequest(const QString& path) { + // TODO check for valid path, instead of emptiness + if (path.isEmpty()) { + qDebug() << "Empty path -> Not sending a request."; + return; + } + + const QNetworkRequest request = m_serviceApi->createRequest(path); + m_restManager->get(request, this, [this, path](QRestReply& reply) { + if (reply.isSuccess()) { + qInfo() << "Request successful."; + const QJsonDocument doc = reply.readJson().value(); + onGetReplySuccessful(path, doc); } else { if (reply.hasError()) { const QString errorString = reply.errorString(); - qCritical() << "ERROR:" << errorString; - emit itemsFetchFailure(errorString); + qWarning() << "Network error:" << errorString; + onGetReplyFailure(path, errorString); } else { int statusCode = reply.httpStatus(); - qCritical() << "ERROR:" << statusCode; - emit itemsFetchFailure(QString::number(statusCode)); - emit itemsFetchFailure(QString("HTTP status code: %1").arg(statusCode)); + qWarning() << "Request not successful:" << statusCode; + if (statusCode == 401) { + notAuthorized(path); + } else { + onGetReplyFailure(path, QString("HTTP status code: %1").arg(statusCode)); + } } } }); } -void ServerCommunicator::postItems(const QByteArray& jsonData) { - QNetworkReply* reply = m_restManager->post(m_serviceApi->createRequest(ROUTE_ITEMS), jsonData); +void ServerCommunicator::onGetReplySuccessful(const QString& path, const QJsonDocument doc) { + if (path == ROUTE_ITEMS) { + emit itemsFetched(doc.toJson()); + } else { + qWarning() << "Can't match request path:" << path; + } +} - QObject::connect(reply, &QNetworkReply::finished, [=]() { - if (reply->error() == QNetworkReply::NoError) { - QByteArray responseData = reply->readAll(); - const QString message = QString("POST successful! Response: %1").arg(responseData); - qInfo() << message; - emit postRequestSuccessful(responseData); +void ServerCommunicator::onGetReplyFailure(const QString& path, const QString errorString) { + if (path == ROUTE_ITEMS) { + emit itemsFetchFailure(errorString); + } else { + qWarning() << "Can't match request path:" << path; + } +} + +void ServerCommunicator::sendPostRequest(const QString& path, const QByteArray data) { + const QNetworkRequest request = m_serviceApi->createRequest(path); + + m_restManager->post(request, data, this, [this, path](QRestReply& reply) { + if (reply.isSuccess()) { + int statusCode = reply.httpStatus(); + qInfo() << "Request successful. Status code:" << statusCode; + const QJsonDocument doc = reply.readJson().value(); + onPostReplySuccessful(path, doc); } else { - const QString message = QString("Error: %1").arg(reply->errorString()); - qDebug() << message; - emit postRequestFailure(message); + if (reply.hasError()) { + const QString errorString = reply.errorString(); + qWarning() << "Network error:" << errorString; + onPostReplyFailure(path, errorString); + } else { + int statusCode = reply.httpStatus(); + qWarning() << "Request not successful:" << statusCode; + qInfo() << "Content:" << reply.readJson(); + if (statusCode == 401) { + notAuthorized(path); + } else { + onPostReplyFailure(path, QString("HTTP status code: %1").arg(statusCode)); + } + } } - reply->deleteLater(); }); } -void ServerCommunicator::deleteItem(const QString& id) { - const QString deleteRoute = QString("%1/%2").arg(ROUTE_ITEMS, id); - QNetworkReply* reply = m_restManager->deleteResource(m_serviceApi->createRequest(deleteRoute)); +void ServerCommunicator::onPostReplySuccessful(const QString& path, const QJsonDocument doc) { + if (path == ROUTE_ITEMS) { + emit sendItemSuccessful(doc.toJson()); + } else if (path == ROUTE_LOG_IN) { + qCritical() << "Login success:" << doc.toJson(QJsonDocument::Compact); + emit loginSuccessful(doc.toJson(QJsonDocument::Compact)); + } else { + qWarning() << "Can't match request path:" << path; + } +} - QObject::connect(reply, &QNetworkReply::finished, [=]() { - if (reply->error() == QNetworkReply::NoError) { - QByteArray responseData = reply->readAll(); - const QString message = QString("DELETE successful! Response: %1").arg(responseData); - qInfo() << message; - emit deleteRequestSuccessful(responseData); +void ServerCommunicator::onPostReplyFailure(const QString& path, const QString errorString) { + if (path == ROUTE_ITEMS) { + emit sendItemFailure(errorString); + } else if (path == ROUTE_LOG_IN) { + qCritical() << "Login failure:" << errorString; + emit loginFailure(errorString); + } else { + qWarning() << "Can't match request path:" << path; + } +} + +void ServerCommunicator::sendDeleteRequest(const QString& path) { + const QNetworkRequest request = m_serviceApi->createRequest(path); + m_restManager->deleteResource(request, this, [this, path](QRestReply& reply) { + if (reply.isSuccess()) { + qInfo() << "Request successful."; + const QJsonDocument doc = reply.readJson().value(); + onDeleteReplySuccessful(path, doc); } else { - const QString message = QString("Error: %1").arg(reply->errorString()); - qDebug() << message; - emit deleteRequestFailure(message); + if (reply.hasError()) { + const QString errorString = reply.errorString(); + qWarning() << "Network error:" << errorString; + onDeleteReplyFailure(path, errorString); + } else { + int statusCode = reply.httpStatus(); + qWarning() << "Request not successful:" << statusCode; + if (statusCode == 401) { + notAuthorized(path); + } else { + onDeleteReplyFailure(path, QString("HTTP status code: %1").arg(statusCode)); + } + } } - reply->deleteLater(); }); -} \ No newline at end of file +} + +void ServerCommunicator::onDeleteReplySuccessful(const QString& path, const QJsonDocument doc) { + if (path.startsWith(ROUTE_ITEMS)) { + emit deleteItemSuccessful(doc.toJson()); + } else { + qWarning() << "Can't match request path:" << path; + } +} + +void ServerCommunicator::onDeleteReplyFailure(const QString& path, const QString errorString) { + if (path.startsWith(ROUTE_ITEMS)) { + emit deleteItemFailure(errorString); + } else { + qWarning() << "Can't match request path:" << path; + } +} diff --git a/network/servercommunicator.h b/network/servercommunicator.h index 83f8be7..77b4d00 100644 --- a/network/servercommunicator.h +++ b/network/servercommunicator.h @@ -23,18 +23,26 @@ class ServerCommunicator : public QObject { public slots: void fetchItems(); - void postItems(const QByteArray& jsonData); + void sendItem(const QByteArray& jsonData); void deleteItem(const QString& id); + // NEXT editItem(const QByteArray& jsonData) signals: void urlChanged(); + void loginSuccessful(const QByteArray responseData); + void loginFailure(const QString errorString); + + void notAuthorized(const QString path); + void itemsFetched(const QByteArray jsonDoc); void itemsFetchFailure(const QString errorString); - void postRequestSuccessful(const QByteArray responseData); - void postRequestFailure(const QString errorString); - void deleteRequestSuccessful(const QByteArray responseData); - void deleteRequestFailure(const QString errorString); + + void sendItemSuccessful(const QByteArray responseData); + void sendItemFailure(const QString errorString); + + void deleteItemSuccessful(const QByteArray responseData); + void deleteItemFailure(const QString errorString); private: QNetworkAccessManager m_netManager; @@ -44,6 +52,20 @@ class ServerCommunicator : public QObject { QString m_email; QString m_password; QString m_authToken; + + bool validLoginCredentials(); + + void sendGetRequest(const QString& path); + void onGetReplySuccessful(const QString& path, const QJsonDocument doc); + void onGetReplyFailure(const QString& path, const QString errorString); + + void sendPostRequest(const QString& path, const QByteArray data); + void onPostReplySuccessful(const QString& path, const QJsonDocument doc); + void onPostReplyFailure(const QString& path, const QString errorString); + + void sendDeleteRequest(const QString& path); + void onDeleteReplySuccessful(const QString& path, const QJsonDocument doc); + void onDeleteReplyFailure(const QString& path, const QString errorString); }; #endif // SERVERCOMMUNICATOR_H