Can perform user sign in.

This commit is contained in:
Pierre HUBERT 2018-11-28 21:33:27 +01:00
parent 9f568d5ef6
commit 99724f845c
20 changed files with 787 additions and 40 deletions

View File

@ -1,14 +1,28 @@
QT += widgets
QT += widgets network
SOURCES += \
main.cpp \
helpers/accounthelper.cpp \
widgets/loginwidget.cpp
widgets/loginwidget.cpp \
utils/accountutils.cpp \
data/accountloginrequest.cpp \
data/apirequest.cpp \
data/apirequestparameter.cpp \
helpers/apihelper.cpp \
utils/jsonutils.cpp \
data/apirequestslist.cpp
HEADERS += \
helpers/accounthelper.h \
config.h \
widgets/loginwidget.h
widgets/loginwidget.h \
utils/accountutils.h \
data/accountloginrequest.h \
data/apirequest.h \
data/apirequestparameter.h \
helpers/apihelper.h \
utils/jsonutils.h \
data/apirequestslist.h
FORMS += \
widgets/loginwidget.ui

View File

@ -0,0 +1,26 @@
#include "accountloginrequest.h"
AccountLoginRequest::AccountLoginRequest()
{
}
QString AccountLoginRequest::emailAddress() const
{
return mEmailAddress;
}
void AccountLoginRequest::setEmailAddress(const QString &emailAddress)
{
mEmailAddress = emailAddress;
}
QString AccountLoginRequest::password() const
{
return mPassword;
}
void AccountLoginRequest::setPassword(const QString &password)
{
mPassword = password;
}

View File

@ -0,0 +1,43 @@
/**
* Account login request
*
* @author Pierre HUBERT
*/
#ifndef ACCOUNTLOGINREQUEST_H
#define ACCOUNTLOGINREQUEST_H
#include <QObject>
/**
* Possible login results
*/
enum LoginResult {
LOGIN_SUCCESS,
INVALID_CREDENTIALS,
TOO_MANY_REQUEST,
NETWORK_ERROR,
OTHER_ERROR
};
/**
* Login request
*/
class AccountLoginRequest
{
public:
AccountLoginRequest();
QString emailAddress() const;
void setEmailAddress(const QString &emailAddress);
QString password() const;
void setPassword(const QString &password);
private:
QString mEmailAddress;
QString mPassword;
};
#endif // ACCOUNTLOGINREQUEST_H

54
data/apirequest.cpp Normal file
View File

@ -0,0 +1,54 @@
#include <QNetworkReply>
#include "apirequest.h"
APIRequest::APIRequest(QObject *parent) : QObject(parent)
{
}
APIRequest::~APIRequest()
{
if(mNetworkReply != nullptr)
mNetworkReply->deleteLater();
}
QString APIRequest::URI() const
{
return mURI;
}
void APIRequest::setURI(const QString &uRI)
{
mURI = uRI;
}
void APIRequest::addString(QString name, QString value)
{
mArguments.append(APIRequestParameter(name, value));
}
void APIRequest::addInt(QString name, int value)
{
mArguments.append(APIRequestParameter(name, QString::number(value)));
}
void APIRequest::addBool(QString name, bool value)
{
mArguments.append(APIRequestParameter(name, value ? "true" : "false"));
}
QList<APIRequestParameter> APIRequest::arguments() const
{
return mArguments;
}
QNetworkReply *APIRequest::networkReply() const
{
return mNetworkReply;
}
void APIRequest::setNetworkReply(QNetworkReply *networkReply)
{
mNetworkReply = networkReply;
}

88
data/apirequest.h Normal file
View File

@ -0,0 +1,88 @@
/**
* This object contains information about a single
* object.
*
* @author Pierre HUBERT
*/
#ifndef APIREQUEST_H
#define APIREQUEST_H
#include <QObject>
#include <QJsonDocument>
#include "apirequestparameter.h"
class QNetworkReply;
class APIRequest : public QObject
{
Q_OBJECT
public:
explicit APIRequest(QObject *parent = nullptr);
~APIRequest();
//Get and set URI target
QString URI() const;
void setURI(const QString &URI);
/**
* Add a string parameter to the request
*
* @param name The name of the field to add
* @param value The value of the argument to add
*/
void addString(QString name, QString value);
/**
* Add an integer parameter to the request
*
* @param name The name of the field to add
* @param value The value of the argument to add
*/
void addInt(QString name, int value);
/**
* Add a boolean parameter to the request
*
* @param name The name of the field to add
* @param value The value of the argument to add
*/
void addBool(QString name, bool value);
/**
* Get the entire list of arguments of the request
*
* @return The list of arguments
*/
QList<APIRequestParameter> arguments() const;
//Get and set network reply associated with the request
QNetworkReply *networkReply() const;
void setNetworkReply(QNetworkReply *networkReply);
signals:
/**
* Signal emitted when an error occurred
*
* @param code The code of the error
*/
void error(int code);
/**
* Signal emitted once we have got the response for our request
*
* @param document JSON Response
*/
void success(const QJsonDocument &document);
public slots:
private:
QString mURI;
QList<APIRequestParameter> mArguments;
QNetworkReply *mNetworkReply = nullptr;
};
#endif // APIREQUEST_H

View File

@ -0,0 +1,32 @@
#include "apirequestparameter.h"
APIRequestParameter::APIRequestParameter()
{
}
APIRequestParameter::APIRequestParameter(const QString &name, const QString &value) :
mName(name), mValue(value)
{
}
QString APIRequestParameter::name() const
{
return mName;
}
void APIRequestParameter::setName(const QString &name)
{
mName = name;
}
QString APIRequestParameter::value() const
{
return mValue;
}
void APIRequestParameter::setValue(const QString &value)
{
mValue = value;
}

View File

@ -0,0 +1,29 @@
/**
* Single API request parameter
*
* @author Pierre HUBERT
*/
#ifndef APIREQUESTPARAMETER_H
#define APIREQUESTPARAMETER_H
#include <QString>
class APIRequestParameter
{
public:
APIRequestParameter();
APIRequestParameter(const QString &name, const QString &value);
QString name() const;
void setName(const QString &name);
QString value() const;
void setValue(const QString &value);
private:
QString mName;
QString mValue;
};
#endif // APIREQUESTPARAMETER_H

29
data/apirequestslist.cpp Normal file
View File

@ -0,0 +1,29 @@
#include "apirequest.h"
#include "apirequestslist.h"
APIRequestsList::APIRequestsList() : QList()
{
}
APIRequest *APIRequestsList::findForReply(QNetworkReply *reply, bool delete_from_list)
{
bool found = false;
int i;
for(i = 0; i < size(); i++){
if(at(i)->networkReply() == reply){
found = true;
break;
}
}
APIRequest *request = nullptr;
if(found)
request = at(i);
if(found && delete_from_list)
removeAt(i);
return request;
}

25
data/apirequestslist.h Normal file
View File

@ -0,0 +1,25 @@
#ifndef APIREQUESTSLIST_H
#define APIREQUESTSLIST_H
#include <QList>
class APIRequest;
class QNetworkReply;
class APIRequestsList : public QList<APIRequest *>
{
public:
APIRequestsList();
/**
* Search and return the API Request that contains a specific NetworkReply
*
* @param reply The reply to search
* @param delete_from_list Specify whether the entry should be removed
* from the list or not
* @return The request / null if none found
*/
APIRequest *findForReply(QNetworkReply *reply, bool delete_from_list);
};
#endif // APIREQUESTSLIST_H

View File

@ -1,12 +1,60 @@
#include "accounthelper.h"
#include "../data/apirequest.h"
#include "../helpers/apihelper.h"
AccountHelper::AccountHelper(QObject *parent) : QObject(parent)
{
mAPIHelper = new APIHelper;
}
bool AccountHelper::signedIn()
{
return false; //TODO : implement
}
void AccountHelper::login(const AccountLoginRequest &info)
{
//Create API request
APIRequest *request = new APIRequest;
request->setURI("account/login");
request->addString("userMail", info.emailAddress());
request->addString("userPassword", info.password());
//Make connections
connect(request, &APIRequest::success, this, &AccountHelper::requestLoginResult);
connect(request, &APIRequest::error, this, &AccountHelper::loginError);
//Execute request
mAPIHelper->execute(request);
}
void AccountHelper::loginError(int code)
{
//Delete API request
qobject_cast<APIRequest *>(sender())->deleteLater();
LoginResult result = LoginResult::OTHER_ERROR;
if(code < 100)
result = LoginResult::NETWORK_ERROR;
if(code == 401)
result = LoginResult::INVALID_CREDENTIALS;
if(code == 429)
result = LoginResult::TOO_MANY_REQUEST;
//Inform about the result
emit loginResult(result);
}
void AccountHelper::requestLoginResult(const QJsonDocument &document)
{
//Delete API request
qobject_cast<APIRequest *>(sender())->deleteLater();
//Success
emit loginResult(LoginResult::LOGIN_SUCCESS);
}

View File

@ -8,6 +8,11 @@
#define ACCOUNTHELPER_H
#include <QObject>
#include <QJsonDocument>
class APIHelper;
#include "../data/accountloginrequest.h"
class AccountHelper : public QObject
{
@ -22,9 +27,32 @@ public:
*/
bool signedIn();
/**
* Perform login
*
* @param info Request data (user credentials)
*/
void login(const AccountLoginRequest &info);
signals:
/**
* This signal is emitted once a login request has been completed
*
* @param result The state of the request
*/
void loginResult(LoginResult result);
public slots:
private slots:
//Login request callbacks
void loginError(int code);
void requestLoginResult(const QJsonDocument &document);
private:
APIHelper *mAPIHelper;
};
#endif // ACCOUNTHELPER_H

70
helpers/apihelper.cpp Normal file
View File

@ -0,0 +1,70 @@
#include <QUrlQuery>
#include <QJsonDocument>
#include "apihelper.h"
#include "../config.h"
APIHelper::APIHelper(QObject *parent) : QObject(parent)
{
//Make errors
QObject::connect(&mNetworkManager, &QNetworkAccessManager::sslErrors, this, &APIHelper::sslErrors);
}
void APIHelper::execute(APIRequest *request)
{
//Determine full request URL
QString requestURL = API_URL + request->URI();
//Add API credentials to parameters
request->addString("serviceName", API_SERVICE_NAME);
request->addString("serviceToken", API_SERVICE_TOKEN);
//Prepare request
//See this SO question to learn more : https://stackoverflow.com/questions/2599423
QUrlQuery queryData;
for(APIRequestParameter param : request->arguments())
queryData.addQueryItem(param.name(), param.value());
//Send request
QNetworkRequest networkRequest((QUrl(requestURL)));
networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
QNetworkReply *reply = mNetworkManager.post(networkRequest, queryData.toString(QUrl::FullyEncoded).toUtf8());
//Make connections
connect(reply, &QNetworkReply::finished, this, &APIHelper::finished);
connect(reply, static_cast<void (QNetworkReply::*)(QNetworkReply::NetworkError)>(&QNetworkReply::error), this, &APIHelper::error);
//Add the request to the list
request->setNetworkReply(reply);
mRequestsList.append(request);
}
void APIHelper::finished()
{
if(qobject_cast<QNetworkReply *>(sender())->attribute(QNetworkRequest::HttpStatusCodeAttribute) != 200)
return;
//Get the API request related to this
APIRequest *request = mRequestsList.findForReply(qobject_cast<QNetworkReply *>(sender()), true);
//Process and return response
emit request->success(QJsonDocument::fromJson(request->networkReply()->readAll()));
}
void APIHelper::error()
{
//Get the API request related to this
APIRequest *request = mRequestsList.findForReply(qobject_cast<QNetworkReply *>(sender()), true);
//Try to determine error code
int response_code = request->networkReply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
qWarning("An error occurred in an API request! (code: %d)", response_code);
emit request->error(response_code);
}
void APIHelper::sslErrors()
{
qWarning("APIHelper: the SSL/TLS session encountered an error!");
}

45
helpers/apihelper.h Normal file
View File

@ -0,0 +1,45 @@
/**
* API Helper - handles the connections between
* the server and this application
*
* @author Pierre HUBERT
*/
#ifndef APIHELPER_H
#define APIHELPER_H
#include <QObject>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include "../data/apirequest.h"
#include "../data/apirequestslist.h"
class APIHelper : public QObject
{
Q_OBJECT
public:
explicit APIHelper(QObject *parent = nullptr);
/**
* Send a request to the server
*
* @param request The request to execute
*/
void execute(APIRequest *request);
signals:
public slots:
private slots:
void finished();
void error();
void sslErrors();
private:
QNetworkAccessManager mNetworkManager;
APIRequestsList mRequestsList;
};
#endif // APIHELPER_H

16
utils/accountutils.cpp Normal file
View File

@ -0,0 +1,16 @@
#include <QRegularExpression>
#include "accountutils.h"
AccountUtils::AccountUtils()
{
}
bool AccountUtils::CheckEmailAddress(const QString &email)
{
QRegExp regex("\\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,4}\\b");
regex.setCaseSensitivity(Qt::CaseInsensitive);
regex.setPatternSyntax(QRegExp::RegExp);
return regex.exactMatch(email);
}

26
utils/accountutils.h Normal file
View File

@ -0,0 +1,26 @@
/**
* Account utilities
*
* @author Pierre HUBERT
*/
#ifndef ACCOUNTUTILS_H
#define ACCOUNTUTILS_H
#include <QObject>
class AccountUtils
{
public:
AccountUtils();
/**
* Determine whether an email address is valid or not
*
* @param email The email address to check
* @return TRUE if the email is valid / FALSE else
*/
bool static CheckEmailAddress(const QString &email);
};
#endif // ACCOUNTUTILS_H

6
utils/jsonutils.cpp Normal file
View File

@ -0,0 +1,6 @@
#include "jsonutils.h"
JSONUtils::JSONUtils()
{
}

17
utils/jsonutils.h Normal file
View File

@ -0,0 +1,17 @@
/**
* JSON utilities
*
* @author Pierre HUBERT
*/
#ifndef JSONUTILS_H
#define JSONUTILS_H
class JSONUtils
{
public:
JSONUtils();
};
#endif // JSONUTILS_H

View File

@ -1,14 +1,110 @@
#include <QMessageBox>
#include "loginwidget.h"
#include "ui_loginwidget.h"
#include "../utils/accountutils.h"
#include "../helpers/accounthelper.h"
LoginWidget::LoginWidget(QWidget *parent) :
QWidget(parent),
ui(new Ui::LoginWidget)
{
ui->setupUi(this);
mAccountHelper = new AccountHelper(this);
connect(mAccountHelper, &AccountHelper::loginResult, this, &LoginWidget::loginResult);
}
LoginWidget::~LoginWidget()
{
delete ui;
}
void LoginWidget::loginResult(LoginResult result)
{
//Check if login is successfull
if(result == LoginResult::LOGIN_SUCCESS){
qDebug("User successfully signed in.");
close();
return;
}
//Release login button
ui->loginButton->setEnabled(true);
//Display appropriate error
switch (result) {
case LoginResult::INVALID_CREDENTIALS:
showError(tr("The email and / or the password was rejected by the server!"));
break;
case LoginResult::NETWORK_ERROR:
showError(tr("A network occurred. Please check your Internet connection..."));
break;
case LoginResult::TOO_MANY_REQUEST:
showError(tr("Too many login attempts from your computer. Please try again later..."));
break;
case LoginResult::OTHER_ERROR:
default:
showError(tr("Login attempt did not succeed."));
break;
}
}
void LoginWidget::on_loginButton_clicked()
{
submitForm();
}
void LoginWidget::on_emailEdit_returnPressed()
{
submitForm();
}
void LoginWidget::on_passwordEdit_returnPressed()
{
submitForm();
}
void LoginWidget::submitForm()
{
showError("");
QString emailAddress = ui->emailEdit->text();
QString password = ui->passwordEdit->text();
//Check input
if(emailAddress.length() < 5){
showError(tr("Please specify an email address!"));
return;
}
if(password.length() < 3){
showError(tr("Please specify a valid password!"));
return;
}
if(!AccountUtils::CheckEmailAddress(emailAddress)){
showError(("Please specify a valid email address!"));
return;
}
//Block login button
ui->loginButton->setEnabled(false);
//Try to sign in user
AccountLoginRequest request;
request.setEmailAddress(emailAddress);
request.setPassword(password);
mAccountHelper->login(request);
}
void LoginWidget::showError(const QString &msg)
{
ui->errorLabel->setText(msg);
}

View File

@ -3,10 +3,15 @@
#include <QWidget>
#include "../data/accountloginrequest.h"
namespace Ui {
class LoginWidget;
}
class AccountHelper;
class LoginWidget : public QWidget
{
Q_OBJECT
@ -15,8 +20,31 @@ public:
explicit LoginWidget(QWidget *parent = nullptr);
~LoginWidget();
private slots:
void loginResult(LoginResult result);
//UI Slots
void on_loginButton_clicked();
void on_emailEdit_returnPressed();
void on_passwordEdit_returnPressed();
private:
/**
* Submit login form
*/
void submitForm();
/**
* Display an error message on the widget
*
* @param msg The message to show
*/
void showError(const QString &msg);
Ui::LoginWidget *ui;
AccountHelper *mAccountHelper;
};
#endif // LOGINWIDGET_H

View File

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>402</width>
<height>107</height>
<width>274</width>
<height>397</height>
</rect>
</property>
<property name="windowTitle">
@ -15,47 +15,74 @@
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="1">
<widget class="QLineEdit" name="emailEdit">
<property name="inputMask">
<string/>
</property>
<property name="placeholderText">
<string>Your email address</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Email address</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Password</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="passwordEdit">
<property name="placeholderText">
<string>Your password</string>
</property>
</widget>
</item>
</layout>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="pushButton">
<widget class="QLabel" name="errorLabel">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string/>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="emailEdit">
<property name="inputMask">
<string/>
</property>
<property name="placeholderText">
<string>Your email address</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="passwordEdit">
<property name="echoMode">
<enum>QLineEdit::Password</enum>
</property>
<property name="placeholderText">
<string>Your password</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="loginButton">
<property name="text">
<string>Login</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<resources/>