RESTful Color Palette Server
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#ifndef TYPES_H
#define TYPES_H
#include <QtGui/QColor>
#include <QtCore/QDateTime>
#include <QtCore/QJsonArray>
#include <QtCore/QJsonObject>
#include <QtCore/QJsonParseError>
#include <QtCore/QString>
#include <QtCore/qtypes.h>
#include <algorithm>
#include <optional>
struct Jsonable
{
virtual QJsonObject toJson() const = 0;
virtual ~Jsonable() = default;
};
struct Updatable
{
virtual bool update(const QJsonObject &json) = 0;
virtual void updateFields(const QJsonObject &json) = 0;
virtual ~Updatable() = default;
};
template<typename T>
struct FromJsonFactory
{
virtual std::optional<T> fromJson(const QJsonObject &json) const = 0;
virtual ~FromJsonFactory() = default;
};
struct User : public Jsonable, public Updatable
{
qint64 id;
QString email;
QString firstName;
QString lastName;
QUrl avatarUrl;
QDateTime createdAt;
QDateTime updatedAt;
explicit User(const QString &email, const QString &firstName, const QString &lastName,
const QUrl &avatarUrl,
const QDateTime &createdAt = QDateTime::currentDateTimeUtc(),
const QDateTime &updatedAt = QDateTime::currentDateTimeUtc())
: id(nextId()),
email(email),
firstName(firstName),
lastName(lastName),
avatarUrl(avatarUrl),
createdAt(createdAt),
updatedAt(updatedAt)
{
}
bool update(const QJsonObject &json) override
{
if (!json.contains("email") || !json.contains("first_name") || !json.contains("last_name")
|| !json.contains("avatar"))
return false;
email = json.value("email").toString();
firstName = json.value("first_name").toString();
lastName = json.value("last_name").toString();
avatarUrl.setPath(json.value("avatar").toString());
updateTimestamp();
return true;
}
void updateFields(const QJsonObject &json) override
{
if (json.contains("email"))
email = json.value("email").toString();
if (json.contains("first_name"))
firstName = json.value("first_name").toString();
if (json.contains("last_name"))
lastName = json.value("last_name").toString();
if (json.contains("avatar"))
avatarUrl.setPath(json.value("avatar").toString());
updateTimestamp();
}
QJsonObject toJson() const override
{
return QJsonObject{ { "id", id },
{ "email", email },
{ "first_name", firstName },
{ "last_name", lastName },
{ "avatar", avatarUrl.toString() },
{ "createdAt", createdAt.toString(Qt::ISODateWithMs) },
{ "updatedAt", updatedAt.toString(Qt::ISODateWithMs) } };
}
private:
void updateTimestamp() { updatedAt = QDateTime::currentDateTimeUtc(); }
static qint64 nextId()
{
static qint64 lastId = 1;
return lastId++;
}
};
struct UserFactory : public FromJsonFactory<User>
{
UserFactory(const QString &scheme, const QString &hostName, int port)
: scheme(scheme), hostName(hostName), port(port)
{
}
std::optional<User> fromJson(const QJsonObject &json) const override
{
if (!json.contains("email") || !json.contains("first_name") || !json.contains("last_name")
|| !json.contains("avatar")) {
return std::nullopt;
}
if (json.contains("createdAt") && json.contains("updatedAt")) {
return User(
json.value("email").toString(), json.value("first_name").toString(),
json.value("last_name").toString(), json.value("avatar").toString(),
QDateTime::fromString(json.value("createdAt").toString(), Qt::ISODateWithMs),
QDateTime::fromString(json.value("updatedAt").toString(), Qt::ISODateWithMs));
}
QUrl avatarUrl(json.value("avatar").toString());
if (!avatarUrl.isValid()) {
avatarUrl.setPath(json.value("avatar").toString());
}
avatarUrl.setScheme(scheme);
avatarUrl.setHost(hostName);
avatarUrl.setPort(port);
return User(json.value("email").toString(), json.value("first_name").toString(),
json.value("last_name").toString(), avatarUrl);
}
private:
QString scheme;
QString hostName;
int port;
};
struct Color : public Jsonable, public Updatable
{
qint64 id;
QString name;
QColor color;
QString pantone;
QDateTime createdAt;
QDateTime updatedAt;
explicit Color(const QString &name, const QString &color, const QString &pantone,
const QDateTime &createdAt = QDateTime::currentDateTimeUtc(),
const QDateTime &updatedAt = QDateTime::currentDateTimeUtc())
: id(nextId()),
name(name),
color(QColor(color)),
pantone(pantone),
createdAt(createdAt),
updatedAt(updatedAt)
{
}
QJsonObject toJson() const override
{
return QJsonObject{ { "id", id },
{ "name", name },
{ "color", color.name() },
{ "pantone_value", pantone },
{ "createdAt", createdAt.toString(Qt::ISODateWithMs) },
{ "updatedAt", updatedAt.toString(Qt::ISODateWithMs) } };
}
bool update(const QJsonObject &json) override
{
if (!json.contains("name") || !json.contains("color") || !json.contains("pantone_value"))
return false;
name = json.value("name").toString();
color = QColor(json.value("color").toString());
pantone = json.value("pantone_value").toString();
updateTimestamp();
return true;
}
void updateFields(const QJsonObject &json) override
{
if (json.contains("name"))
name = json.value("name").toString();
if (json.contains("color"))
color = QColor(json.value("color").toString());
if (json.contains("pantone_value"))
pantone = json.value("pantone_value").toString();
updateTimestamp();
}
private:
void updateTimestamp() { updatedAt = QDateTime::currentDateTimeUtc(); }
static qint64 nextId()
{
static qint64 lastId = 1;
return lastId++;
}
};
struct ColorFactory : public FromJsonFactory<Color>
{
std::optional<Color> fromJson(const QJsonObject &json) const override
{
if (!json.contains("name") || !json.contains("color") || !json.contains("pantone_value"))
return std::nullopt;
if (json.contains("createdAt") && json.contains("updatedAt")) {
return Color(
json.value("name").toString(), json.value("color").toString(),
json.value("pantone_value").toString(),
QDateTime::fromString(json.value("createdAt").toString(), Qt::ISODateWithMs),
QDateTime::fromString(json.value("updatedAt").toString(), Qt::ISODateWithMs));
}
return Color(json.value("name").toString(), json.value("color").toString(),
json.value("pantone_value").toString());
}
};
struct SessionEntry : public Jsonable
{
qint64 id;
QString email;
QString password;
std::optional<QUuid> token;
explicit SessionEntry(const QString &email, const QString &password)
: id(nextId()), email(email), password(password)
{
}
void startSession() { token = generateToken(); }
void endSession() { token = std::nullopt; }
QJsonObject toJson() const override
{
return token
? QJsonObject{ { "id", id },
{ "token", token->toString(QUuid::StringFormat::WithoutBraces) } }
: QJsonObject{};
}
bool operator==(const QString &otherToken) const
{
return token && *token == QUuid::fromString(otherToken);
}
private:
QUuid generateToken() { return QUuid::createUuid(); }
static qint64 nextId()
{
static qint64 lastId = 1;
return lastId++;
}
};
struct SessionEntryFactory : public FromJsonFactory<SessionEntry>
{
std::optional<SessionEntry> fromJson(const QJsonObject &json) const override
{
if (!json.contains("email") || !json.contains("password"))
return std::nullopt;
return SessionEntry(json.value("email").toString(), json.value("password").toString());
}
};
template<typename T>
class IdMap : public QMap<qint64, T>
{
public:
IdMap() = default;
explicit IdMap(const FromJsonFactory<T> &factory, const QJsonArray &array) : QMap<qint64, T>()
{
for (const auto &jsonValue : array) {
if (jsonValue.isObject()) {
const auto maybeT = factory.fromJson(jsonValue.toObject());
if (maybeT) {
QMap<qint64, T>::insert(maybeT->id, *maybeT);
}
}
}
}
};
template<typename T>
class Paginator : public Jsonable
{
public:
static constexpr qsizetype defaultPage = 1;
static constexpr qsizetype defaultPageSize = 6;
explicit Paginator(const T &container, qsizetype page, qsizetype size)
{
const auto containerSize = container.size();
const auto pageIndex = page - 1;
const auto pageSize = qMin(size, containerSize);
const auto totalPages = (containerSize % pageSize) == 0 ? (containerSize / pageSize)
: (containerSize / pageSize) + 1;
valid = containerSize > (pageIndex * pageSize);
if (valid) {
QJsonArray data;
auto iter = container.begin();
std::advance(iter, std::min(pageIndex * pageSize, containerSize));
for (qsizetype i = 0; i < pageSize && iter != container.end(); ++i, ++iter) {
data.push_back(iter->toJson());
}
json = QJsonObject{ { "page", pageIndex + 1 },
{ "per_page", pageSize },
{ "total", containerSize },
{ "total_pages", totalPages },
{ "data", data } };
} else {
json = QJsonObject{};
}
}
QJsonObject toJson() const { return json; }
constexpr bool isValid() const { return valid; }
private:
QJsonObject json;
bool valid;
};
#endif // TYPES_H