RESTful API 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