- Qt 6.10
- Qt HTTP Server
- Qt HTTP Server Examples
- RESTful API Server
- Qt 6.10.2 Reference Documentation

# 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