RESTful API Server
// Copyright (C) 2022 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause #ifndef APIBEHAVIOR_H #define APIBEHAVIOR_H #include "types.h" #include "utils.h" #include <QtHttpServer/QHttpServer> #include <QtConcurrent/qtconcurrentrun.h> #include <optional> template<typename T, typename = void> class CrudApi { }; template<typename T> class CrudApi<T, std::enable_if_t<std::conjunction_v<std::is_base_of<Jsonable, T>, std::is_base_of<Updatable, T>>>> { public: explicit CrudApi(const IdMap<T> &data, std::unique_ptr<FromJsonFactory<T>> factory) : data(data), factory(std::move(factory)) { } QFuture<QHttpServerResponse> getPaginatedList(const QHttpServerRequest &request) const { using PaginatorType = Paginator<IdMap<T>>; std::optional<qsizetype> maybePage; std::optional<qsizetype> maybePerPage; std::optional<qint64> maybeDelay; if (request.query().hasQueryItem("page")) maybePage = request.query().queryItemValue("page").toLongLong(); if (request.query().hasQueryItem("per_page")) maybePerPage = request.query().queryItemValue("per_page").toLongLong(); if (request.query().hasQueryItem("delay")) maybeDelay = request.query().queryItemValue("delay").toLongLong(); if ((maybePage && *maybePage < 1) || (maybePerPage && *maybePerPage < 1)) { return QtConcurrent::run([]() { return QHttpServerResponse(QHttpServerResponder::StatusCode::BadRequest); }); } PaginatorType paginator(data, maybePage ? *maybePage : PaginatorType::defaultPage, maybePerPage ? *maybePerPage : PaginatorType::defaultPageSize); return QtConcurrent::run([paginator = std::move(paginator), maybeDelay]() { if (maybeDelay) QThread::sleep(*maybeDelay); return paginator.isValid() ? QHttpServerResponse(paginator.toJson()) : QHttpServerResponse(QHttpServerResponder::StatusCode::NoContent); }); } QHttpServerResponse getItem(qint64 itemId) const { const auto item = data.find(itemId); return item != data.end() ? QHttpServerResponse(item->toJson()) : QHttpServerResponse(QHttpServerResponder::StatusCode::NotFound); } QHttpServerResponse postItem(const QHttpServerRequest &request) { const std::optional<QJsonObject> json = byteArrayToJsonObject(request.body()); if (!json) return QHttpServerResponse(QHttpServerResponder::StatusCode::BadRequest); const std::optional<T> item = factory->fromJson(*json); if (!item) return QHttpServerResponse(QHttpServerResponder::StatusCode::BadRequest); if (data.contains(item->id)) return QHttpServerResponse(QHttpServerResponder::StatusCode::AlreadyReported); const auto entry = data.insert(item->id, *item); return QHttpServerResponse(entry->toJson(), QHttpServerResponder::StatusCode::Created); } QHttpServerResponse updateItem(qint64 itemId, const QHttpServerRequest &request) { const std::optional<QJsonObject> json = byteArrayToJsonObject(request.body()); if (!json) return QHttpServerResponse(QHttpServerResponder::StatusCode::BadRequest); auto item = data.find(itemId); if (item == data.end()) return QHttpServerResponse(QHttpServerResponder::StatusCode::NoContent); if (!item->update(*json)) return QHttpServerResponse(QHttpServerResponder::StatusCode::BadRequest); return QHttpServerResponse(item->toJson()); } QHttpServerResponse updateItemFields(qint64 itemId, const QHttpServerRequest &request) { const std::optional<QJsonObject> json = byteArrayToJsonObject(request.body()); if (!json) return QHttpServerResponse(QHttpServerResponder::StatusCode::BadRequest); auto item = data.find(itemId); if (item == data.end()) return QHttpServerResponse(QHttpServerResponder::StatusCode::NoContent); item->updateFields(*json); return QHttpServerResponse(item->toJson()); } QHttpServerResponse deleteItem(qint64 itemId) { if (!data.remove(itemId)) return QHttpServerResponse(QHttpServerResponder::StatusCode::NoContent); return QHttpServerResponse(QHttpServerResponder::StatusCode::Ok); } private: IdMap<T> data; std::unique_ptr<FromJsonFactory<T>> factory; }; class SessionApi { public: explicit SessionApi(const IdMap<SessionEntry> &sessions, std::unique_ptr<FromJsonFactory<SessionEntry>> factory) : sessions(sessions), factory(std::move(factory)) { } QHttpServerResponse registerSession(const QHttpServerRequest &request) { const auto json = byteArrayToJsonObject(request.body()); if (!json) return QHttpServerResponse(QHttpServerResponder::StatusCode::BadRequest); const auto item = factory->fromJson(*json); if (!item) return QHttpServerResponse(QHttpServerResponder::StatusCode::BadRequest); const auto session = sessions.insert(item->id, *item); session->startSession(); return QHttpServerResponse(session->toJson()); } QHttpServerResponse login(const QHttpServerRequest &request) { const auto json = byteArrayToJsonObject(request.body()); if (!json || !json->contains("email") || !json->contains("password")) return QHttpServerResponse(QHttpServerResponder::StatusCode::BadRequest); auto maybeSession = std::find_if( sessions.begin(), sessions.end(), [email = json->value("email").toString(), password = json->value("password").toString()](const auto &it) { return it.password == password && it.email == email; }); if (maybeSession == sessions.end()) { return QHttpServerResponse(QHttpServerResponder::StatusCode::NotFound); } maybeSession->startSession(); return QHttpServerResponse(maybeSession->toJson()); } QHttpServerResponse logout(const QHttpServerRequest &request) { const auto maybeToken = getTokenFromRequest(request); if (!maybeToken) return QHttpServerResponse(QHttpServerResponder::StatusCode::BadRequest); auto maybeSession = std::find(sessions.begin(), sessions.end(), *maybeToken); if (maybeSession != sessions.end()) maybeSession->endSession(); return QHttpServerResponse(QHttpServerResponder::StatusCode::Ok); } bool authorize(const QHttpServerRequest &request) const { const auto maybeToken = getTokenFromRequest(request); if (maybeToken) { const auto maybeSession = std::find(sessions.begin(), sessions.end(), *maybeToken); return maybeSession != sessions.end() && *maybeSession == *maybeToken; } return false; } private: IdMap<SessionEntry> sessions; std::unique_ptr<FromJsonFactory<SessionEntry>> factory; }; #endif // APIBEHAVIOR_H