Adding UserServices backend

This commit is contained in:
Digital Artifex
2026-08-13 21:42:13 -04:00
parent 9db9ed67d8
commit abd653ef1c
7 changed files with 484 additions and 29 deletions
@@ -107,10 +107,10 @@ Rectangle
function showVideoPopup(index)
{
imagesModel.setItem(index)
imageDetailsPopup.author = searchModel.data(searchModel.index(index,0), VideoSearchModel.AuthorRole)
imageDetailsPopup.authorUrl = searchModel.data(searchModel.index(index,0), VideoSearchModel.AuthorUrlRole)
imageDetailsPopup.uuid = searchModel.data(searchModel.index(index,0), VideoSearchModel.UuidRole)
imageDetailsPopup.thumbnail = searchModel.data(searchModel.index(index,0), VideoSearchModel.ThumbnailRole)
imageDetailsPopup.author = searchModel.data(searchModel.index(index,0), ImageSearchModel.AuthorRole)
imageDetailsPopup.authorUrl = searchModel.data(searchModel.index(index,0), ImageSearchModel.AuthorUrlRole)
imageDetailsPopup.uuid = searchModel.data(searchModel.index(index,0), ImageSearchModel.UuidRole)
imageDetailsPopup.thumbnail = searchModel.data(searchModel.index(index,0), ImageSearchModel.ThumbnailRole)
imageDetailsPopup.modelIndex = index
imageDetailsPopup.model = searchModel.itemModel
imageDetailsPopup.opacity = 1
+3
View File
@@ -56,6 +56,9 @@ qt_add_qml_module(
common/shaderpack.h
packcompiler.h
packcompiler.cpp
SOURCES common/userservices.h
SOURCES common/userservices.cpp
SOURCES common/qwallet.h common/qwallet.cpp
)
target_compile_definitions(
+25 -25
View File
@@ -37,44 +37,44 @@
#ifdef KOMPLEX_LOCAL_DEV
#define KOMPLEX_API_HOST "https://komplexapi.hlab"
#else
#define KOMPLEX_API_HOST "https://api.komplex.dev"
inline const QString KOMPLEX_API_HOST = QStringLiteral("https://api.komplex.dev");
#endif
#define KOMPLEX_API_VERSION "v2"
inline const QString KOMPLEX_API_VERSION = QStringLiteral("v2");
inline QString KOMPLEX_ENDPOINT_IMAGES_SEARCH = QStringLiteral("images/search");
inline QString KOMPLEX_ENDPOINT_IMAGES_FEATURED = QStringLiteral("featured/images");
inline QString KOMPLEX_ENDPOINT_IMAGES_ITEM = QStringLiteral("images/item");
inline const QString KOMPLEX_ENDPOINT_IMAGES_SEARCH = QStringLiteral("images/search");
inline const QString KOMPLEX_ENDPOINT_IMAGES_FEATURED = QStringLiteral("featured/images");
inline const QString KOMPLEX_ENDPOINT_IMAGES_ITEM = QStringLiteral("images/item");
inline QString KOMPLEX_ENDPOINT_VIDEOS_SEARCH = QStringLiteral("videos/search");
inline QString KOMPLEX_ENDPOINT_VIDEOS_FEATURED = QStringLiteral("featured/videos");
inline QString KOMPLEX_ENDPOINT_VIDEOS_ITEM = QStringLiteral("videos/item");
inline const QString KOMPLEX_ENDPOINT_VIDEOS_SEARCH = QStringLiteral("videos/search");
inline const QString KOMPLEX_ENDPOINT_VIDEOS_FEATURED = QStringLiteral("featured/videos");
inline const QString KOMPLEX_ENDPOINT_VIDEOS_ITEM = QStringLiteral("videos/item");
inline QString KOMPLEX_ENDPOINT_SHADERS_SEARCH = QStringLiteral("shaders/search");
inline QString KOMPLEX_ENDPOINT_SHADERS_FEATURED = QStringLiteral("shaders/featured");
inline QString KOMPLEX_ENDPOINT_SHADERS_ITEM = QStringLiteral("shaders/item");
inline QString KOMPLEX_ENDPOINT_SHADERS_NEWEST = QStringLiteral("shaders/newest");
inline const QString KOMPLEX_ENDPOINT_SHADERS_SEARCH = QStringLiteral("shaders/search");
inline const QString KOMPLEX_ENDPOINT_SHADERS_FEATURED = QStringLiteral("shaders/featured");
inline const QString KOMPLEX_ENDPOINT_SHADERS_ITEM = QStringLiteral("shaders/item");
inline const QString KOMPLEX_ENDPOINT_SHADERS_NEWEST = QStringLiteral("shaders/newest");
inline QString KOMPLEX_ENDPOINT_CUBEMAPS_SEARCH = QStringLiteral("cubemaps/search");
inline QString KOMPLEX_ENDPOINT_CUBEMAPS_FEATURED = QStringLiteral("cubemaps/featured");
inline QString KOMPLEX_ENDPOINT_CUBEMAPS_ITEM = QStringLiteral("cubemaps/item");
inline QString KOMPLEX_ENDPOINT_CUBEMAPS_NEWEST = QStringLiteral("cubemaps/newest");
inline const QString KOMPLEX_ENDPOINT_CUBEMAPS_SEARCH = QStringLiteral("cubemaps/search");
inline const QString KOMPLEX_ENDPOINT_CUBEMAPS_FEATURED = QStringLiteral("cubemaps/featured");
inline const QString KOMPLEX_ENDPOINT_CUBEMAPS_ITEM = QStringLiteral("cubemaps/item");
inline const QString KOMPLEX_ENDPOINT_CUBEMAPS_NEWEST = QStringLiteral("cubemaps/newest");
inline QString KOMPLEX_ENDPOINT_PACKS_SEARCH = QStringLiteral("packs/search");
inline QString KOMPLEX_ENDPOINT_PACKS_FEATURED = QStringLiteral("packs/featured");
inline QString KOMPLEX_ENDPOINT_PACKS_ITEM = QStringLiteral("packs/item");
inline QString KOMPLEX_ENDPOINT_PACKS_NEWEST = QStringLiteral("packs/newest");
inline const QString KOMPLEX_ENDPOINT_PACKS_SEARCH = QStringLiteral("packs/search");
inline const QString KOMPLEX_ENDPOINT_PACKS_FEATURED = QStringLiteral("packs/featured");
inline const QString KOMPLEX_ENDPOINT_PACKS_ITEM = QStringLiteral("packs/item");
inline const QString KOMPLEX_ENDPOINT_PACKS_NEWEST = QStringLiteral("packs/newest");
inline QString KOMPLEX_ENDPOINT_USER_AUTH = QStringLiteral("user/auth");
inline const QString KOMPLEX_ENDPOINT_USER_AUTH = QStringLiteral("user/auth");
inline qsizetype nullsize = std::numeric_limits<qsizetype>::min();
inline std::chrono::milliseconds KOMPLEX_RATE_LIMIT(500);
inline QString KOMPLEX_KEYCHAIN_PASSWORD_KEY = QStringLiteral
inline const qsizetype nullsize = std::numeric_limits<qsizetype>::min();
inline const std::chrono::milliseconds KOMPLEX_RATE_LIMIT(500);
inline const QString KOMPLEX_KEYCHAIN_PASSWORD_KEY = QStringLiteral
(
"com.digitalartifex.komplex.keychain.password"
);
inline QString KOMPLEX_KEYCHAIN_TOKEN_KEY = QStringLiteral
inline const QString KOMPLEX_KEYCHAIN_TOKEN_KEY = QStringLiteral
(
"com.digitalartifex.komplex.keychain.token"
);
+135
View File
@@ -0,0 +1,135 @@
#include "qwallet.h"
#include "../3rdparty/qtkeychain/qtkeychain/keychain.h"
auto QWallet::read(const QString &service, const QString &key) const -> QFuture<QByteArray>
{
return QtConcurrent::run
(
[this, service(service), key(key)]() -> QByteArray
{
QEventLoop loop;
QByteArray value;
auto readCredentialJob = new QKeychain::ReadPasswordJob(service);
readCredentialJob->setAutoDelete(true);
readCredentialJob->setKey(key);
QObject::connect
(
readCredentialJob,
&QKeychain::ReadPasswordJob::finished,
this,
[this, key, &loop, &value](QKeychain::Job *job)
{
auto j = static_cast<QKeychain::ReadPasswordJob*>(job);
if (j->error() == QKeychain::NoError)
{
value = j->binaryData();
}
else
{
m_errorString = qPrintable(j->errorString());
throw std::exception();
}
loop.quit();
}
);
readCredentialJob->start();
loop.exec();
return value;
}
);
}
auto QWallet::write(const QString &service, const QString &key, const QByteArray &value) -> QFuture<void>
{
return QtConcurrent::run
(
[this, service(service), key(key), value(value)]() -> void
{
QEventLoop loop;
bool success;
auto writeCredentialJob = new QKeychain::WritePasswordJob(service);
writeCredentialJob->setAutoDelete(true);
writeCredentialJob->setKey(key);
writeCredentialJob->setBinaryData(value);
QObject::connect
(
writeCredentialJob,
&QKeychain::WritePasswordJob::finished,
this,
[this, key, &loop, &success](QKeychain::Job *job)
{
auto j = static_cast<QKeychain::ReadPasswordJob*>(job);
if (j->error() != QKeychain::NoError)
{
setErrorString(qPrintable(j->errorString()));
throw std::exception();
}
loop.quit();
}
);
writeCredentialJob->start();
loop.exec();
}
);
}
auto QWallet::remove(const QString &service, const QString &key) -> QFuture<void>
{
return QtConcurrent::run
(
[this, service(service), key(key)]() -> void
{
QEventLoop loop;
bool value;
auto deleteCredentialJob = new QKeychain::DeletePasswordJob(service);
deleteCredentialJob->setAutoDelete(true);
deleteCredentialJob->setKey(key);
QObject::connect
(
deleteCredentialJob,
&QKeychain::DeletePasswordJob::finished,
this,
[this, key, &loop, &value](QKeychain::Job *job)
{
auto j = static_cast<QKeychain::ReadPasswordJob*>(job);
if (j->error() != QKeychain::NoError)
{
m_errorString = qPrintable(j->errorString());
throw std::exception();
}
loop.quit();
}
);
deleteCredentialJob->start();
loop.exec();
}
);
}
QWallet::QWallet(QObject *parent) : QObject(parent)
{
}
QWallet::~QWallet()
{
}
auto QWallet::errorString() const -> const QString &
{
return m_errorString;
}
+100
View File
@@ -0,0 +1,100 @@
/*
* Komplex Wallpaper Engine
* Copyright (C) 2026 @DigitalArtifex
* https://digitalartifex.dev - https://github.com/DigitalArtifex
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>
*/
#ifndef QWALLET_H
#define QWALLET_H
#include <QObject>
#include <QFuture>
#include <QEventLoop>
#include <QtConcurrent/QtConcurrentRun>
#include "komplex_global.h"
/**
* @brief The QWallet class
* QWallet is a QFuture wrapper for QtKeychain to make interaction with the keychain easier
*/
class KOMPLEX_EXPORT QWallet : QObject
{
Q_OBJECT
public:
explicit QWallet(QObject *parent = nullptr);
~QWallet();
/**
* @brief read
* Reads the provided key from the system keychain/wallet
* @param service
* Service name of the application making the request
* @param key
* Key to read
* @return
* QFuture for the binary value
*/
[[nodiscard]]
auto read(const QString &service, const QString &key) const -> QFuture<QByteArray>;
/**
* @brief write
* Writes the provided value to the system kaychain/wallet
* @param service
* Service name of the application making the request
* @param key
* Key to read
* @param value
* Binary data to store
* @return
* QFuture
*/
[[nodiscard]]
auto write(const QString &service, const QString &key, const QByteArray &value) -> QFuture<void>;
/**
* @brief write
* Removes the provided value from the system kaychain/wallet
* @param service
* Service name of the application making the request
* @param key
* Key to read
* @return
*/
[[nodiscard]]
auto remove(const QString &service, const QString &key) -> QFuture<void>;
/**
* @brief errorString
* String describing the last encountered error.
* @return
*/
[[nodiscard]]
auto errorString() const -> const QString &;
private:
/**
* @brief setErrorString
* Sets the error string
* @param errorString
*/
auto setErrorString(const QString &errorString) const -> void;
mutable QString m_errorString;
};
#endif // QWALLET_H
+148
View File
@@ -0,0 +1,148 @@
#include "userservices.h"
#include "coreservices.h"
UserServices::UserServices(QObject *parent) : QObject(parent)
{
m_wallet = new QWallet(this);
}
UserServices::~UserServices()
{
}
auto UserServices::setUserCredentials(const UserCredentials &credentials) -> void
{
s_userCredentials = credentials;
Q_EMIT userCredentialsChanged();
}
auto UserServices::errorString() const -> const QString &
{
return m_errorString;
}
auto UserServices::setErrorString(const QString &errorString) -> void
{
if (m_errorString == errorString)
return;
m_errorString = errorString;
emit errorStringChanged();
}
auto UserServices::instance() const -> QWeakPointer<UserServices>
{
if(!s_pointer)
{
s_instance = new UserServices(QCoreApplication::instance());
s_pointer = QSharedPointer<UserServices>
(
s_instance,
&UserServices::deleteLater
);
}
return s_pointer.toWeakRef();
}
auto UserServices::userCredentials() -> const UserCredentials &
{
return s_userCredentials;
}
auto UserServices::login(const QString &username, const QByteArray &password) -> QFuture<UserCredentials>
{
return QtConcurrent::run
(
[this, username = std::move(username), password = std::move(password)] () -> UserCredentials
{
QSharedPointer<QNetworkAccessManager> manager = CoreServices::networkAccessManager().toStrongRef();
if(!manager)
{
Q_EMIT loginFailed();
return {};
}
QNetworkRequest request
(
QUrl
(
QStringLiteral("%1/%2/%3").arg
(
KOMPLEX_API_HOST,
KOMPLEX_API_VERSION,
KOMPLEX_ENDPOINT_USER_AUTH
)
)
);
QJsonObject loginRootObject
{
{
QString("username"),
QJsonValue::fromVariant(username.toUtf8())
},
{
QString("password"),
QJsonValue::fromVariant(password.toBase64())
}
};
QJsonDocument loginDocument(loginRootObject);
QNetworkReply *reply = manager->post(request, loginDocument.toJson(QJsonDocument::Compact));
while(!reply->isFinished())
{
std::chrono::milliseconds(10);
}
if(reply->error() != QNetworkReply::NoError)
{
setErrorString(reply->errorString());
throw std::exception();
}
QByteArray data = reply->readAll();
QJsonParseError parseError;
QJsonDocument document = QJsonDocument::fromJson(data, &parseError);
if(parseError.error != QJsonParseError::NoError)
{
setErrorString(parseError.errorString());
}
QJsonObject rootObject;
if(!rootObject.value("success").toBool())
{
QJsonObject dataObject = rootObject.value(QStringLiteral("data")).toObject();
setErrorString
(
QStringLiteral("Login failure (%1): %2").arg
(
dataObject.value(QStringLiteral("errorCode")).toString(),
dataObject.value(QStringLiteral("message")).toString()
)
);
}
UserCredentials credentials
{
.username = username.toUtf8(),
.sessionToken = rootObject.value
(
QStringLiteral("jwt")
).toString().toUtf8(),
.expiry = QDateTime::currentDateTime()
};
setUserCredentials(credentials);
Q_EMIT loginComplete();
return credentials;
}
);
}
+69
View File
@@ -0,0 +1,69 @@
#pragma once
#include <QGuiApplication>
#include <QObject>
#include <QDateTime>
#include <QSharedPointer>
#include <QFuture>
#include <QJsonDocument>
#include <QJsonArray>
#include <QJsonObject>
#include "komplex_global.h"
#include "qwallet.h"
struct UserCredentials
{
QByteArray username;
QByteArray sessionToken;
QDateTime expiry;
UserCredentials &operator = (const UserCredentials &other)
{
username = other.username;
sessionToken = other.sessionToken;
expiry = other.expiry;
return *this;
}
bool isValid()
{
return !username.isEmpty() && username.isValidUtf8() &&
!sessionToken.isEmpty() && sessionToken.isValidUtf8();
}
};
class KOMPLEX_EXPORT UserServices : public QObject
{
Q_OBJECT
public:
auto instance() const -> QWeakPointer<UserServices>;
static auto userCredentials() -> const UserCredentials &;
auto login(const QString &username, const QByteArray &password) -> QFuture<UserCredentials>;
auto errorString() const -> const QString &;
signals:
auto loginComplete() -> void;
auto loginFailed() -> void;
auto userCredentialsChanged() -> void;
auto errorStringChanged() -> void;
private:
inline static UserServices *s_instance = nullptr;
inline static QSharedPointer<UserServices> s_pointer;
explicit UserServices(QObject *parent);
~UserServices();
auto setUserCredentials(const UserCredentials &credentials) -> void;
auto setErrorString(const QString &errorString) -> void;
QWallet *m_wallet = nullptr;
QString m_errorString;
inline static UserCredentials s_userCredentials;
Q_PROPERTY(UserCredentials userCredentials READ userCredentials NOTIFY userCredentialsChanged FINAL)
Q_PROPERTY(QString errorString READ errorString WRITE setErrorString NOTIFY errorStringChanged FINAL)
};