From e7ba67a6035278985d49d4215d49d2037f288d1e Mon Sep 17 00:00:00 2001 From: Artem Grinev Date: Sat, 11 May 2024 19:37:49 +0000 Subject: [PATCH] refactor: cleanup public API - Separated messages into separate structs - Refactored controllers to use reflection and shared error creation functions - Adapted frontend to API changes - Unified API paths - Updated OpenAPI spec --- .../core/application/dtos/PackageSectionDTO.h | 5 + daemon/core/domain/enums/LogEntryType.h | 2 +- daemon/presentation/messages/AuthMessages.h | 16 + .../presentation/messages/CompareMessages.h | 27 + daemon/presentation/messages/LogMessages.h | 24 + .../presentation/messages/PackageMessages.h | 33 ++ .../presentation/messages/SectionMessages.h | 17 + daemon/presentation/messages/UserMessages.h | 34 ++ .../web-controllers/AuthController.cpp | 36 +- .../web-controllers/CompareController.cpp | 74 +-- .../web-controllers/LogController.cpp | 114 ++-- .../web-controllers/PackageController.cpp | 165 +++--- .../web-controllers/PackageController.h | 10 +- .../web-controllers/SectionController.cpp | 37 +- .../web-controllers/SectionController.h | 2 +- .../web-controllers/UserController.cpp | 103 +--- daemon/presentation/web-filters/JwtFilter.cpp | 45 +- daemon/swagger/openapi.yml.in | 517 +++++++++--------- frontend/src/components/DrawerLayout.tsx | 2 +- frontend/src/components/SnapshotModal.tsx | 2 +- frontend/src/definitions/logEntry.d.ts | 2 +- frontend/src/definitions/package.d.ts | 2 +- frontend/src/hooks/BxtFsHooks.ts | 35 +- frontend/src/hooks/BxtHooks.ts | 12 +- frontend/src/pages/AdminPage.tsx | 2 +- frontend/src/pages/LogPage.tsx | 20 +- frontend/src/pages/MainPage.tsx | 2 +- 27 files changed, 689 insertions(+), 651 deletions(-) create mode 100644 daemon/presentation/messages/AuthMessages.h create mode 100644 daemon/presentation/messages/CompareMessages.h create mode 100644 daemon/presentation/messages/LogMessages.h create mode 100644 daemon/presentation/messages/PackageMessages.h create mode 100644 daemon/presentation/messages/SectionMessages.h create mode 100644 daemon/presentation/messages/UserMessages.h diff --git a/daemon/core/application/dtos/PackageSectionDTO.h b/daemon/core/application/dtos/PackageSectionDTO.h index 7bf6dd5d..085c9d87 100644 --- a/daemon/core/application/dtos/PackageSectionDTO.h +++ b/daemon/core/application/dtos/PackageSectionDTO.h @@ -65,3 +65,8 @@ template<> struct std::hash { return seed; } }; + +template<> inline std::string bxt::to_string(const PackageSectionDTO& dto) { + return fmt::format("{}/{}/{}", dto.branch, dto.repository, + dto.architecture); +} diff --git a/daemon/core/domain/enums/LogEntryType.h b/daemon/core/domain/enums/LogEntryType.h index be5cfb41..12e68360 100644 --- a/daemon/core/domain/enums/LogEntryType.h +++ b/daemon/core/domain/enums/LogEntryType.h @@ -8,6 +8,6 @@ namespace bxt::Core::Domain { -enum LogEntryType { Add, Remove, Update }; +enum struct LogEntryType { Add, Remove, Update }; } // namespace bxt::Core::Domain diff --git a/daemon/presentation/messages/AuthMessages.h b/daemon/presentation/messages/AuthMessages.h new file mode 100644 index 00000000..46e9175c --- /dev/null +++ b/daemon/presentation/messages/AuthMessages.h @@ -0,0 +1,16 @@ +/* === This file is part of bxt === + * + * SPDX-FileCopyrightText: 2024 Artem Grinev + * SPDX-License-Identifier: AGPL-3.0-or-later + * + */ +#pragma once + +#include + +namespace bxt::Presentation { +struct AuthRequest { + std::string name; + std::string password; +}; +} // namespace bxt::Presentation diff --git a/daemon/presentation/messages/CompareMessages.h b/daemon/presentation/messages/CompareMessages.h new file mode 100644 index 00000000..6f7a5c50 --- /dev/null +++ b/daemon/presentation/messages/CompareMessages.h @@ -0,0 +1,27 @@ +/* === This file is part of bxt === + * + * SPDX-FileCopyrightText: 2024 Artem Grinev + * SPDX-License-Identifier: AGPL-3.0-or-later + * + */ +#pragma once + +#include "core/application/dtos/PackageSectionDTO.h" +#include "presentation/messages/SectionMessages.h" + +#include +#include +#include + +namespace bxt::Presentation { + +using CompareRequest = std::vector; + +using LocationMap = std::unordered_map; +using SectionMap = std::unordered_map; + +struct CompareResponse { + std::vector sections; + std::unordered_map compare_table; +}; +} // namespace bxt::Presentation diff --git a/daemon/presentation/messages/LogMessages.h b/daemon/presentation/messages/LogMessages.h new file mode 100644 index 00000000..f0b12038 --- /dev/null +++ b/daemon/presentation/messages/LogMessages.h @@ -0,0 +1,24 @@ +/* === This file is part of bxt === + * + * SPDX-FileCopyrightText: 2024 Artem Grinev + * SPDX-License-Identifier: AGPL-3.0-or-later + * + */ +#pragma once + +#include "core/domain/enums/LogEntryType.h" +#include "presentation/messages/PackageMessages.h" + +#include + +namespace bxt::Presentation { + +struct LogEntryReponse { + std::string id; + uint64_t time; + Core::Domain::LogEntryType type; + PackageResponse package; +}; + +using LogResponse = std::vector; +} // namespace bxt::Presentation diff --git a/daemon/presentation/messages/PackageMessages.h b/daemon/presentation/messages/PackageMessages.h new file mode 100644 index 00000000..0dc15d7d --- /dev/null +++ b/daemon/presentation/messages/PackageMessages.h @@ -0,0 +1,33 @@ +/* === This file is part of bxt === + * + * SPDX-FileCopyrightText: 2024 Artem Grinev + * SPDX-License-Identifier: AGPL-3.0-or-later + * + */ +#pragma once + +#include "core/application/dtos/PackageSectionDTO.h" +#include "presentation/messages/SectionMessages.h" + +#include +#include + +namespace bxt::Presentation { + +struct SnapRequest { + SectionRequest source; + SectionRequest target; +}; + +struct PoolEntryResponse { + std::string version; + bool has_signature; +}; + +struct PackageResponse { + std::string name; + Core::Application::PackageSectionDTO section; + std::unordered_map pool_entries; + std::string preferred_location; +}; +} // namespace bxt::Presentation diff --git a/daemon/presentation/messages/SectionMessages.h b/daemon/presentation/messages/SectionMessages.h new file mode 100644 index 00000000..3ba2374a --- /dev/null +++ b/daemon/presentation/messages/SectionMessages.h @@ -0,0 +1,17 @@ +/* === This file is part of bxt === + * + * SPDX-FileCopyrightText: 2024 Artem Grinev + * SPDX-License-Identifier: AGPL-3.0-or-later + * + */ +#pragma once + +#include "core/application/dtos/PackageSectionDTO.h" + +namespace bxt::Presentation { + +using SectionRequest = Core::Application::PackageSectionDTO; + +using SectionReponse = Core::Application::PackageSectionDTO; + +} // namespace bxt::Presentation diff --git a/daemon/presentation/messages/UserMessages.h b/daemon/presentation/messages/UserMessages.h new file mode 100644 index 00000000..a2b93baa --- /dev/null +++ b/daemon/presentation/messages/UserMessages.h @@ -0,0 +1,34 @@ +/* === This file is part of bxt === + * + * SPDX-FileCopyrightText: 2024 Artem Grinev + * SPDX-License-Identifier: AGPL-3.0-or-later + * + */ +#pragma once + +#include +#include +#include + +namespace bxt::Presentation { +struct AddUserRequest { + std::string name; + std::string password; + std::optional> permissions; +}; + +struct UpdateUserRequest { + std::string name; + std::optional password; + std::optional> permissions; +}; + +struct RemoveUserRequest { + std::string name; +}; + +struct UserResponse { + std::string name; + std::set permissions; +}; +} // namespace bxt::Presentation diff --git a/daemon/presentation/web-controllers/AuthController.cpp b/daemon/presentation/web-controllers/AuthController.cpp index 3a99ae67..8e32c148 100644 --- a/daemon/presentation/web-controllers/AuthController.cpp +++ b/daemon/presentation/web-controllers/AuthController.cpp @@ -7,6 +7,7 @@ #include "AuthController.h" #include "drogon/HttpTypes.h" +#include "presentation/messages/AuthMessages.h" #include "utilities/drogon/Helpers.h" #include @@ -18,43 +19,34 @@ namespace bxt::Presentation { drogon::Task AuthController::auth(drogon::HttpRequestPtr req) { - if (!req->getJsonObject() || !req->getJsonObject()->isObject()) { - auto error_resp = drogon::HttpResponse::newHttpResponse(); - error_resp->setStatusCode(drogon::k400BadRequest); - co_return error_resp; + const auto auth_request = + drogon_helpers::get_request_json(req); + + if (!auth_request) { + co_return drogon_helpers::make_error_response( + fmt::format("Invalid request: {}", auth_request.error()->what())); } - auto json = *req->getJsonObject(); + const auto& [name, password] = *auth_request; - const auto name_json = json["name"]; - const auto password_json = json["password"]; + const auto check_ok = co_await m_service.auth(name, password); - if (name_json.isNull() || password_json.isNull()) { - auto error_resp = drogon::HttpResponse::newHttpResponse(); - error_resp->setStatusCode(drogon::k400BadRequest); - co_return error_resp; + if (!check_ok.has_value()) { + co_return drogon_helpers::make_error_response(check_ok.error().what(), + drogon::k401Unauthorized); } - const std::string name = name_json.asString(); - const std::string password = password_json.asString(); - - if (!co_await m_service.auth(name, password)) { - auto error_resp = drogon::HttpResponse::newHttpResponse(); - error_resp->setStatusCode(drogon::k401Unauthorized); - co_return error_resp; - } const auto token = jwt::create() .set_payload_claim("username", name) .set_issuer("auth0") .set_type("JWS") .sign(jwt::algorithm::hs256 {m_options.secret}); - - auto response = drogon::HttpResponse::newHttpResponse(); drogon::Cookie jwt_cookie("token", token); - jwt_cookie.setHttpOnly(true); + auto response = drogon::HttpResponse::newHttpResponse(); response->addCookie(jwt_cookie); + co_return response; } diff --git a/daemon/presentation/web-controllers/CompareController.cpp b/daemon/presentation/web-controllers/CompareController.cpp index 38f7f344..4dc672ee 100644 --- a/daemon/presentation/web-controllers/CompareController.cpp +++ b/daemon/presentation/web-controllers/CompareController.cpp @@ -7,86 +7,64 @@ #include "CompareController.h" #include "core/application/dtos/PackageSectionDTO.h" -#include "core/domain/enums/PoolLocation.h" #include "drogon/HttpResponse.h" #include "drogon/HttpTypes.h" +#include "presentation/messages/CompareMessages.h" +#include "utilities/drogon/Helpers.h" #include "utilities/drogon/Macro.h" +#include "utilities/to_string.h" -#include "json/value.h" +#include +#include #include +#include #include namespace bxt::Presentation { drogon::Task CompareController::compare(drogon::HttpRequestPtr req) { - Json::Value result; const auto available_sections = co_await m_section_service.get_sections(); - if (!available_sections.has_value()) { - result["error"] = "No sections available in the Box"; - result["status"] = "error"; - - auto response = drogon::HttpResponse::newHttpJsonResponse(result); - response->setStatusCode(drogon::k400BadRequest); - - co_return response; - } - for (const auto& [branch, repository, architecture] : *available_sections) { BXT_JWT_CHECK_PERMISSIONS(fmt::format("packages.compare.{}.{}.{}", branch, repository, architecture), req) } - const auto sections_json = *req->getJsonObject(); - - if (sections_json.empty()) { - result["error"] = "No sections to compare provided"; - result["status"] = "error"; + if (!available_sections.has_value()) { + co_return drogon_helpers::make_error_response("No sections available"); + } - auto response = drogon::HttpResponse::newHttpJsonResponse(result); - response->setStatusCode(drogon::k400BadRequest); + const auto sections_request = + drogon_helpers::get_request_json(req); - co_return response; + if (!sections_request) { + co_return drogon_helpers::make_error_response(fmt::format( + "Invalid request: {}", sections_request.error()->what())); } - std::vector sections; - - for (const auto& section_json : sections_json) { - sections.emplace_back(PackageSectionDTO { - .branch = section_json["branch"].asString(), - .repository = section_json["repository"].asString(), - .architecture = section_json["architecture"].asString()}); + if ((*sections_request).empty()) { + co_return drogon_helpers::make_error_response( + "No sections to compare were provided"); } - const auto compare_result = co_await m_compare_service.compare(sections); + const auto compare_result = + co_await m_compare_service.compare(*sections_request); if (compare_result->sections.empty() || compare_result->compare_table.empty()) { - result["error"] = "No compare data found (all sections are empty)"; - result["status"] = "error"; - - auto response = drogon::HttpResponse::newHttpJsonResponse(result); - response->setStatusCode(drogon::k400BadRequest); - - co_return response; + co_return drogon_helpers::make_error_response( + "No compare data found (all sections are empty)"); } - for (const auto& section : compare_result->sections) { - Json::Value section_json; - - section_json["branch"] = section.branch; - section_json["repository"] = section.repository; - section_json["architecture"] = section.architecture; - - result["sections"].append(section_json); - } + CompareResponse result {compare_result->sections}; for (const auto& [index, version] : compare_result->compare_table) { auto&& [name, section, location] = index; - result["compare_table"][name][std::string(section)] - [bxt::to_string(location)] = version; + + result.compare_table[name][bxt::to_string(section)] + [bxt::to_string(location)] = version; } - co_return drogon::HttpResponse::newHttpJsonResponse(result); + co_return drogon_helpers::make_json_response(result); } } // namespace bxt::Presentation diff --git a/daemon/presentation/web-controllers/LogController.cpp b/daemon/presentation/web-controllers/LogController.cpp index 79d9a760..897b2ecb 100644 --- a/daemon/presentation/web-controllers/LogController.cpp +++ b/daemon/presentation/web-controllers/LogController.cpp @@ -6,88 +6,64 @@ */ #include "LogController.h" +#include "core/application/dtos/PackageLogEntryDTO.h" +#include "core/domain/enums/LogEntryType.h" #include "core/domain/enums/PoolLocation.h" #include "drogon/HttpTypes.h" +#include "presentation/messages/LogMessages.h" +#include "presentation/messages/PackageMessages.h" +#include "utilities/drogon/Helpers.h" #include "utilities/log/Logging.h" +#include "utilities/reflect/PathParser.h" + +#include +#include namespace bxt::Presentation { drogon::Task LogController::get_package_logs(drogon::HttpRequestPtr req) { - Json::Value result; - BXT_JWT_CHECK_PERMISSIONS("logs", req) - auto dtos = co_await m_service.events(); + const auto dtos = co_await m_service.events(); if (dtos.empty()) { - result["status"] = "error"; - result["error"] = "No events found"; - - auto response = drogon::HttpResponse::newHttpJsonResponse(result); - response->setStatusCode(drogon::k400BadRequest); - - co_return response; - } - - for (const auto& dto : dtos) { - Json::Value json_value; - - json_value["id"] = dto.id; - json_value["time"] = dto.time; - json_value["package"]["name"] = dto.package.name; - - // Serialize section - json_value["package"]["section"]["branch"] = dto.package.section.branch; - json_value["package"]["section"]["repository"] = - dto.package.section.repository; - json_value["package"]["section"]["architecture"] = - dto.package.section.architecture; - - // Serialize pool_entries - Json::Value pool_entries_json; - for (const auto& [pool_location, pool_entry] : - dto.package.pool_entries) { - Json::Value entry_json; - entry_json["version"] = pool_entry.version; - entry_json["hasSignature"] = pool_entry.signature_path.has_value(); - - pool_entries_json.append(entry_json); - } - - json_value["package"]["pool_entries"] = pool_entries_json; - const auto preferred_location = - Core::Domain::select_preferred_pool_location( - dto.package.pool_entries); - - if (!preferred_location) { - logd("Package {} has no pool entries, skipping preferred one " - "selection", - dto.package.name); - continue; - } - - const auto preferred_candidate = - dto.package.pool_entries.at(*preferred_location); - - json_value["package"]["preferredCandidate"]["version"] = - preferred_candidate.version; - json_value["package"]["preferredCandidate"]["hasSignature"] = - preferred_candidate.signature_path.has_value() ? "true" : "false"; - - // Serialize action - json_value["action"] = [dto] { - switch (dto.type) { - case Core::Domain::Add: return "Add"; - case Core::Domain::Remove: return "Remove"; - case Core::Domain::Update: return "Update"; - default: return "Unknown"; - } - }(); - - result.append(json_value); + co_return drogon_helpers::make_error_response("No events found"); } - co_return drogon::HttpResponse::newHttpJsonResponse(result); + constexpr auto package_mapping = [](const auto& e) { + auto&& [location, entry] = e; + return std::make_pair( + bxt::to_string(location), + PoolEntryResponse {entry.version, + entry.signature_path.has_value()}); + }; + + co_return drogon_helpers::make_json_response( + dtos + | std::views::transform( + [package_mapping](const PackageLogEntryDTO& dto) { + auto&& [id, time, type, package] = dto; + LogEntryReponse result { + id, time, type, + PackageResponse { + package.name, package.section, + package.pool_entries + | std::views::transform(package_mapping) + | std::ranges::to()}}; + + if (const auto preferred_location = + Core::Domain::select_preferred_pool_location( + package.pool_entries)) { + result.package.preferred_location = + bxt::to_string(*preferred_location); + } else { + logd("Package {} has no pool entries, skipping preferred " + "one selection", + package.name); + } + return result; + }) + | std::ranges::to()); } } // namespace bxt::Presentation diff --git a/daemon/presentation/web-controllers/PackageController.cpp b/daemon/presentation/web-controllers/PackageController.cpp index f04bd20d..5caffabf 100644 --- a/daemon/presentation/web-controllers/PackageController.cpp +++ b/daemon/presentation/web-controllers/PackageController.cpp @@ -6,24 +6,28 @@ */ #include "PackageController.h" -#include "boost/algorithm/string/classification.hpp" -#include "boost/algorithm/string/split.hpp" #include "core/application/dtos/PackageDTO.h" #include "core/application/dtos/PackageSectionDTO.h" #include "core/application/services/PackageService.h" #include "core/domain/enums/PoolLocation.h" -#include "core/domain/value_objects/PackageVersion.h" -#include "drogon/HttpResponse.h" -#include "drogon/HttpTypes.h" -#include "drogon/utils/FunctionTraits.h" -#include "jwt-cpp/traits/nlohmann-json/defaults.h" +#include "presentation/messages/PackageMessages.h" +#include "utilities/drogon/Helpers.h" #include "utilities/drogon/Macro.h" #include "utilities/log/Logging.h" +#include "utilities/to_string.h" -#include "json/value.h" +#include +#include +#include #include +#include +#include #include +#include +#include +#include #include +#include #include namespace bxt::Presentation { @@ -91,12 +95,16 @@ drogon::Task if (!packages.contains(file_number)) continue; - if (parts[1] == "branch") { - packages[file_number].section.branch = param; - } else if (parts[1] == "repository") { - packages[file_number].section.repository = param; - } else if (parts[1] == "architecture") { - packages[file_number].section.architecture = param; + if (parts[1] == "section") { + auto section = + rfl::json::read( + param); + + if (!section) { + co_return drogon_helpers::make_error_response(fmt::format( + "Invalid section format: {}", section.error()->what())); + } + packages[file_number].section = *section; } } @@ -117,12 +125,10 @@ drogon::Task auto result = co_await m_package_service.commit_transaction(transaction); - Json::Value res_json; - res_json["result"] = result ? "ok" : "error"; - - auto response = drogon::HttpResponse::newHttpJsonResponse(res_json); - - co_return response; + if (!result.has_value()) { + co_return drogon_helpers::make_error_response(result.error().what()); + } + co_return drogon_helpers::make_ok_response(); } drogon::Task @@ -131,7 +137,6 @@ drogon::Task const std::string &repository, const std::string &architecture) { PackageSectionDTO section {branch, repository, architecture}; - Json::Value result; BXT_JWT_CHECK_PERMISSIONS((std::vector { fmt::format("packages.get.{}.{}.{}", branch, @@ -142,79 +147,54 @@ drogon::Task const auto packages = co_await m_package_service.get_packages(section); - if (!packages.has_value()) { - result["status"] = "error"; - result["message"] = packages.error().what(); - co_return drogon::HttpResponse::newHttpJsonResponse(result); + if (!packages) { + co_return drogon_helpers::make_error_response(packages.error().what()); } - for (const auto &package : *packages) { - Json::Value package_json; - - package_json["name"] = package.name; - package_json["section"] = Json::Value(); - package_json["section"]["branch"] = package.section.branch; - package_json["section"]["repository"] = package.section.repository; - package_json["section"]["architecture"] = package.section.architecture; - - Json::Value pool_entries_json; - for (const auto &[pool_location, pool_entry] : package.pool_entries) { - Json::Value entry_json; - entry_json["version"] = pool_entry.version; - entry_json["hasSignature"] = - pool_entry.signature_path.has_value() ? "true" : "false"; - - pool_entries_json - [Core::Domain::pool_location_names.at(pool_location).data()] = - entry_json; - } - - package_json["poolEntries"] = pool_entries_json; - - const auto preferred_location = - Core::Domain::select_preferred_pool_location(package.pool_entries); - - if (!preferred_location) { - logd("Package {} has no pool entries, skipping preferred one " - "selection", - package.name); - continue; - } - - const auto preferred_candidate = - package.pool_entries.at(*preferred_location); - - package_json["preferredCandidate"]["version"] = - preferred_candidate.version; - package_json["preferredCandidate"]["hasSignature"] = - preferred_candidate.signature_path.has_value() ? "true" : "false"; - - result.append(package_json); + { + using std::ranges::to; + using std::views::transform; + auto response = *packages | transform([](const auto &dto) { + auto &&[section, name, is_any_architecture, pool_entries] = dto; + + PackageResponse result { + name, section, + pool_entries | transform([](const auto &e) { + auto &&[location, entry] = e; + + return std::make_pair( + bxt::to_string(location), + PoolEntryResponse {entry.version, + entry.signature_path.has_value()}); + }) | to()}; + + if (const auto preferred_location = + Core::Domain::select_preferred_pool_location( + pool_entries)) { + result.preferred_location = bxt::to_string(*preferred_location); + } else { + logd("Package {} has no pool entries, skipping preferred " + "one selection", + dto.name); + } + + return result; + }) | to(); + + co_return drogon_helpers::make_json_response(response); } - - co_return drogon::HttpResponse::newHttpJsonResponse(result); } drogon::Task PackageController::snap(drogon::HttpRequestPtr req) { - const auto sections_json = *req->getJsonObject(); - Json::Value result; + const auto snap_request = + rfl::json::read(std::string(req->getBody())); - if (sections_json.empty() || sections_json["source"].empty() - || sections_json["target"].empty()) { - result["error"] = "Invalid arguments"; - result["status"] = "error"; - - auto response = drogon::HttpResponse::newHttpJsonResponse(result); - response->setStatusCode(drogon::k400BadRequest); - - co_return response; + if (snap_request.error()) { + co_return drogon_helpers::make_error_response("Invalid arguments"); } - PackageSectionDTO source_branch { - .branch = sections_json["source"]["branch"].asString(), - .repository = sections_json["source"]["repository"].asString(), - .architecture = sections_json["source"]["architecture"].asString()}; + auto &source_branch = (*snap_request).source; BXT_JWT_CHECK_PERMISSIONS( (std::vector { @@ -224,10 +204,7 @@ drogon::Task source_branch.repository, source_branch.architecture)}), req) - PackageSectionDTO target_branch { - .branch = sections_json["target"]["branch"].asString(), - .repository = sections_json["target"]["repository"].asString(), - .architecture = sections_json["target"]["architecture"].asString()}; + auto &target_branch = (*snap_request).target; BXT_JWT_CHECK_PERMISSIONS( (std::vector { @@ -241,17 +218,11 @@ drogon::Task co_await m_package_service.snap(source_branch, target_branch); if (!snap_ok.has_value()) { - result["error"] = "Snap failed"; - result["status"] = "error"; - - auto response = drogon::HttpResponse::newHttpJsonResponse(result); - response->setStatusCode(drogon::k400BadRequest); - - co_return response; + co_return drogon_helpers::make_error_response( + fmt::format("Snap failed: {}", snap_ok.error().what())); } - result["status"] = "ok"; - co_return drogon::HttpResponse::newHttpJsonResponse(result); + co_return drogon_helpers::make_ok_response(); } } // namespace bxt::Presentation diff --git a/daemon/presentation/web-controllers/PackageController.h b/daemon/presentation/web-controllers/PackageController.h index 939d6af7..c0874ca0 100644 --- a/daemon/presentation/web-controllers/PackageController.h +++ b/daemon/presentation/web-controllers/PackageController.h @@ -38,12 +38,16 @@ class PackageController BXT_JWT_ADD_METHOD_TO( PackageController::get_packages, - "/api/packages/get?branch={1}&repository={2}&architecture={3}", + "/api/packages?branch={1}&repository={2}&architecture={3}", drogon::Get); - BXT_JWT_ADD_METHOD_TO(PackageController::sync, "/api/sync", drogon::Post); + BXT_JWT_ADD_METHOD_TO(PackageController::sync, + "/api/packages/sync", + drogon::Post); - BXT_JWT_ADD_METHOD_TO(PackageController::snap, "/api/snap", drogon::Post); + BXT_JWT_ADD_METHOD_TO(PackageController::snap, + "/api/packages/snap", + drogon::Post); METHOD_LIST_END diff --git a/daemon/presentation/web-controllers/SectionController.cpp b/daemon/presentation/web-controllers/SectionController.cpp index 5b1e9025..6d05512e 100644 --- a/daemon/presentation/web-controllers/SectionController.cpp +++ b/daemon/presentation/web-controllers/SectionController.cpp @@ -6,39 +6,28 @@ */ #include "SectionController.h" +#include "presentation/messages/SectionMessages.h" +#include "utilities/drogon/Helpers.h" + #include #include +#include drogon::Task bxt::Presentation::SectionController::get_sections( drogon::HttpRequestPtr req) const { - const auto sections = co_await m_service.get_sections(); - - Json::Value result; + auto sections = co_await m_service.get_sections(); if (!sections.has_value()) { - result["status"] = "error"; - result["message"] = sections.error().what(); - - co_return drogon::HttpResponse::newHttpJsonResponse(result); + co_return drogon_helpers::make_error_response(sections.error().what()); } - for (const auto& [branch, repository, architecture] : *sections) { - if (!co_await m_permission_service.check( - fmt::format("sections.{}.{}.{}", branch, repository, - architecture), - req->getAttributes()->get("jwt_username"))) { - continue; - } - - Json::Value section_json; - - section_json["branch"] = branch; - section_json["repository"] = repository; - section_json["architecture"] = architecture; - - result.append(section_json); - } + sections = *sections | std::views::filter([this, req](auto section) { + return coro::sync_wait(m_permission_service.check( + fmt::format("sections.{}.{}.{}", section.branch, section.repository, + section.architecture), + req->getAttributes()->get("jwt_username"))); + }) | std::ranges::to(); - co_return drogon::HttpResponse::newHttpJsonResponse(result); + co_return drogon_helpers::make_json_response(*sections); } diff --git a/daemon/presentation/web-controllers/SectionController.h b/daemon/presentation/web-controllers/SectionController.h index d56e23cb..f90c2d38 100644 --- a/daemon/presentation/web-controllers/SectionController.h +++ b/daemon/presentation/web-controllers/SectionController.h @@ -25,7 +25,7 @@ class SectionController METHOD_LIST_BEGIN BXT_JWT_ADD_METHOD_TO(SectionController::get_sections, - "/api/sections/get", + "/api/sections", drogon::Get); METHOD_LIST_END diff --git a/daemon/presentation/web-controllers/UserController.cpp b/daemon/presentation/web-controllers/UserController.cpp index 1a2d6b59..426ecd6a 100644 --- a/daemon/presentation/web-controllers/UserController.cpp +++ b/daemon/presentation/web-controllers/UserController.cpp @@ -7,13 +7,12 @@ #include "UserController.h" #include "core/application/dtos/UserDTO.h" -#include "drogon/HttpTypes.h" +#include "presentation/messages/UserMessages.h" #include "utilities/drogon/Helpers.h" -#include "utilities/log/Logging.h" -#include -#include #include +#include +#include namespace bxt::Presentation { @@ -21,35 +20,15 @@ drogon::Task UserController::add_user(drogon::HttpRequestPtr req) { BXT_JWT_CHECK_PERMISSIONS("users.add", req) - if (!req->getJsonObject()) { - co_return drogon_helpers::make_error_response("Body is malformed"); - } - auto json = *req->getJsonObject(); - - Core::Application::UserDTO dto; + const auto user_request = + drogon_helpers::get_request_json(req); - if (!json["name"] || !json["name"].isString()) { + if (!user_request) { co_return drogon_helpers::make_error_response( - "No name for entity provided"); + fmt::format("Invalid request: {}", user_request.error()->what())); } - dto.name = json["name"].asString(); - if (json["password"] && json["password"].isString()) { - dto.password = json["password"].asString(); - } - - if (json["permissions"] && json["permissions"].isArray()) { - dto.permissions = std::set(); - - for (const auto& permission : json["permissions"]) { - if (permission.isString()) { - dto.permissions->emplace(permission.asString()); - } else { - logw("User Controller: trying to add a permission with wrong " - "type"); - } - } - } + auto dto = rfl::as(*user_request); const auto add_ok = co_await m_service.add_user(dto); @@ -64,36 +43,15 @@ drogon::Task UserController::update_user(drogon::HttpRequestPtr req) { BXT_JWT_CHECK_PERMISSIONS("users.update", req) - if (!req->getJsonObject()) { - co_return drogon_helpers::make_error_response("Body is malformed"); - } - - auto json = *req->getJsonObject(); - - Core::Application::UserDTO dto; + const auto user_request = + drogon_helpers::get_request_json(req); - if (!json["name"] || !json["name"].isString()) { + if (!user_request) { co_return drogon_helpers::make_error_response( - "No name for entity provided"); + fmt::format("Invalid request: {}", user_request.error()->what())); } - dto.name = json["name"].asString(); - if (json["password"] && json["password"].isString()) { - dto.password = json["password"].asString(); - } - - if (json["permissions"] && json["permissions"].isArray()) { - dto.permissions = std::set(); - - for (const auto& permission : json["permissions"]) { - if (permission.isString()) { - dto.permissions->emplace(permission.asString()); - } else { - logw("User Controller: trying to add a permission with wrong " - "type"); - } - } - } + auto dto = rfl::as(*user_request); const auto update_ok = co_await m_service.update_user(dto); @@ -108,12 +66,15 @@ drogon::Task UserController::remove_user(drogon::HttpRequestPtr req) { BXT_JWT_CHECK_PERMISSIONS("users.remove", req) - auto json = *req->getJsonObject(); + const auto user_request = + drogon_helpers::get_request_json(req); - const auto id = json["id"].asString(); - Json::Value result; + if (!user_request) { + co_return drogon_helpers::make_error_response( + fmt::format("Invalid request: {}", user_request.error()->what())); + } - const auto remove_ok = co_await m_service.remove_user(id); + const auto remove_ok = co_await m_service.remove_user((*user_request).name); if (!remove_ok.has_value()) { co_return drogon_helpers::make_error_response(remove_ok.error().what()); @@ -127,27 +88,19 @@ drogon::Task BXT_JWT_CHECK_PERMISSIONS("users.get", req) const auto users = co_await m_service.get_users(); - Json::Value result; if (!users.has_value()) { co_return drogon_helpers::make_error_response(users.error().what()); } - for (const auto& user : *users) { - Json::Value user_json; - - user_json["name"] = user.name; - - user_json["permissions"] = Json::Value(Json::arrayValue); - - for (const auto& permission : *user.permissions) { - user_json["permissions"].append(permission); - } - - result.append(user_json); - } - - co_return drogon::HttpResponse::newHttpJsonResponse(result); + co_return drogon_helpers::make_json_response( + *users + | std::views::transform([](const Core::Application::UserDTO& dto) { + auto&& [name, password, permissions] = dto; + return UserResponse { + name, permissions.value_or(std::set {})}; + }) + | std::ranges::to()); } } // namespace bxt::Presentation diff --git a/daemon/presentation/web-filters/JwtFilter.cpp b/daemon/presentation/web-filters/JwtFilter.cpp index e60e00b3..6e048d47 100644 --- a/daemon/presentation/web-filters/JwtFilter.cpp +++ b/daemon/presentation/web-filters/JwtFilter.cpp @@ -6,11 +6,21 @@ */ #include "JwtFilter.h" +#include "utilities/drogon/Helpers.h" + +#include #include namespace bxt::Presentation { using namespace drogon; +std::expected, std::exception> + decode_jwt(const std::string &token) { + try { + return jwt::decode(token); + } catch (std::exception &e) { return std::unexpected(e); } +} + void JwtFilter::doFilter(const HttpRequestPtr &request, FilterCallback &&fcb, FilterChainCallback &&fccb) { @@ -19,38 +29,33 @@ void JwtFilter::doFilter(const HttpRequestPtr &request, std::string token = request->getCookie("token"); if (token.empty()) { - Json::Value resultJson; - resultJson["error"] = - "Authentification header is not found or malformed"; - resultJson["status"] = "error"; - - auto res = HttpResponse::newHttpJsonResponse(resultJson); - res->setStatusCode(k401Unauthorized); - - return fcb(res); + return fcb(drogon_helpers::make_error_response( + "Authentification header is not found", k401Unauthorized)); } - auto decoded = jwt::decode(token); auto verifier = jwt::verify() .allow_algorithm(jwt::algorithm::hs256 {m_options.secret}) .with_issuer("auth0"); - std::error_code ec; + auto decoded = decode_jwt(token); - verifier.verify(decoded, ec); + if (!decoded.has_value()) { + return fcb(drogon_helpers::make_error_response( + fmt::format("Error while decoding the token: {}", + decoded.error().what()), + k401Unauthorized)); + } - if (ec) { - Json::Value resultJson; - resultJson["message"] = "Token is invalid!"; - resultJson["status"] = "error"; + std::error_code ec; - auto res = HttpResponse::newHttpJsonResponse(resultJson); - res->setStatusCode(k401Unauthorized); + verifier.verify(*decoded, ec); - return fcb(res); + if (ec) { + return fcb(drogon_helpers::make_error_response( + "Authentification token is invalid", k401Unauthorized)); } - auto claims = decoded.get_payload_claims(); + auto claims = decoded->get_payload_claims(); for (auto &claim : claims) request->getAttributes()->insert("jwt_" + claim.first, diff --git a/daemon/swagger/openapi.yml.in b/daemon/swagger/openapi.yml.in index e1d141d9..e930140b 100644 --- a/daemon/swagger/openapi.yml.in +++ b/daemon/swagger/openapi.yml.in @@ -2,176 +2,78 @@ openapi: 3.0.0 info: title: b[x]t API description: API for the b[x]t repository management service. - version: "0.1" + version: "MVP" servers: - url: "/" paths: /api/auth: post: - summary: Authentificate user. + summary: Authenticate user + operationId: auth requestBody: required: true content: application/json: schema: - type: object - required: - - name - - password - properties: - name: - type: string - password: - type: string + $ref: "#/components/schemas/AuthRequest" responses: "200": - description: Issued JWT after successful authentification. - content: - application/json: - schema: - type: object - properties: - token: - type: string + description: Authentication successful, JWT token set in cookie "400": - description: Data is malformed. + description: Invalid request "401": - description: Credentials are invalid. + description: Unauthorized /api/verify: get: - summary: Verify issued token. - responses: - "200": - description: JWT is valid. - content: - application/json: - schema: - type: object - properties: - status: - type: string - example: "ok" - "401": - description: Token is invalid or expired. - - /api/users/add: - post: - summary: Add a new user. - requestBody: - required: true - content: - application/json: - schema: - type: object - required: - - name - properties: - name: - type: string - password: - type: string - permissions: - type: array - items: - type: string + summary: Verify JWT token + operationId: verify responses: "200": - description: User added successfully. + description: Token is valid "400": - description: Data is missing or malformed. + description: Invalid request "401": - description: User lacks permissions. - - /api/users/update: - post: - summary: Update an existing user. - requestBody: - required: true - content: - application/json: - schema: - type: object - required: - - name - properties: - name: - type: string - password: - type: string - permissions: - type: array - items: - type: string + description: Unauthorized + /api/logs/packages: + get: + summary: Get package logs + operationId: getPackageLogs responses: "200": - description: User successfully updated. + description: List of package log entries + content: + application/json: + schema: + $ref: "#/components/schemas/LogResponse" "400": - description: Data is missing or malformed. + description: Invalid request "401": - description: Lacks permissions. - - /api/users/remove: + description: Unauthorized + /api/compare: post: - summary: Remove an existing user. + summary: Compare sections + operationId: compare requestBody: required: true content: application/json: schema: - type: object - required: - - id - properties: - id: - type: string + $ref: "#/components/schemas/CompareRequest" responses: "200": - description: User successfully removed. - "400": - description: Data is missing or malformed. - "401": - description: Lacks permissions. - - /api/users: - get: - summary: Retrieve a list of users. - responses: - "200": - description: Retrieved list of users. + description: Comparison result content: application/json: schema: - type: array - items: - $ref: "#/components/schemas/User" - "401": - description: User lacks permissions. - - /api/sections/get: - get: - summary: Retrieve accessible sections based on user permissions. - responses: - "200": - description: Retrieved sections. - content: - application/json: - schema: - type: array - items: - $ref: "#/components/schemas/Section" + $ref: "#/components/schemas/CompareResponse" + "400": + description: Invalid request "401": - description: User lacks permissions. - - /api/sync: - post: - summary: Synchronize package data across the system. - responses: - "200": - description: Synchronization started successfully. - + description: Unauthorized /api/packages/commit: post: - summary: Commit a transaction. + summary: Commit package transaction + operationId: commitTransaction requestBody: required: true content: @@ -179,53 +81,25 @@ paths: schema: type: object properties: - files: - type: array - items: - type: object - properties: - file: - type: string - format: binary - description: The binary file of the package. - signature: - type: string - format: binary - description: The binary file of the package's signature. - packageInfo: - type: array - items: - type: object - properties: - name: - type: string - description: Name identifier of the package. - branch: - type: string - description: Branch name for where the package should be committed. - repository: - type: string - description: Repository name for where the package should be committed. - architecture: - type: string - description: Architecture name for where the package should be committed. + packageFile: + type: string + format: binary + packageSignature: + type: string + format: binary + packageSection: + type: string responses: "200": - description: Transaction committed successfully. - content: - application/json: - schema: - type: object - properties: - result: - type: string - example: "ok" + description: Transaction committed successfully "400": - description: Data is missing or malformed. - - /api/packages/get: + description: Invalid request + "401": + description: Unauthorized + /api/packages: get: - summary: Retrieve packages based on specified branch, repository, and architecture. + summary: Get packages by section + operationId: getPackages parameters: - name: branch in: query @@ -244,119 +118,218 @@ paths: type: string responses: "200": - description: Retrieved packages. + description: List of packages content: application/json: schema: type: array items: - $ref: "#/components/schemas/Package" - - /api/snap: + $ref: "#/components/schemas/PackageResponse" + "400": + description: Invalid request + "401": + description: Unauthorized + /api/packages/sync: post: - summary: Snap packages from a source section to a target section. + summary: Synchronize packages + operationId: sync + responses: + "200": + description: Packages synchronized successfully + "400": + description: Invalid request + "401": + description: Unauthorized + /api/packages/snap: + post: + summary: Snap packages between branches + operationId: snap requestBody: required: true content: application/json: schema: - type: object - properties: - source: - $ref: "#/components/schemas/Section" - target: - $ref: "#/components/schemas/Section" + $ref: "#/components/schemas/SnapRequest" responses: "200": - description: Snap operation successful. - - /api/logs/packages: + description: Packages snapped successfully + "400": + description: Invalid request + "401": + description: Unauthorized + /api/sections: get: - summary: Retrieve logs for package-related actions. + summary: Get sections + operationId: getSections responses: "200": - description: Retrieved package logs. + description: List of sections content: application/json: schema: type: array items: - $ref: "#/components/schemas/LogEntry" - /api/deploy/start: + $ref: "#/components/schemas/SectionResponse" + "400": + description: Invalid request + "401": + description: Unauthorized + /api/users/add: post: - summary: Start a deployment session. + summary: Add a new user + operationId: addUser + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/AddUserRequest" responses: "200": - description: Deployment session started successfully. - content: - text/plain: - schema: - type: string - example: "Session started with ID: {session_id}" - "401": - description: No API key provided or invalid API key. + description: User added successfully "400": - description: Error message if start failed. - /api/deploy/push: + description: Invalid request + "401": + description: Unauthorized + /api/users/update: post: - summary: Push a package to the deployment session. + summary: Update an existing user + operationId: updateUser requestBody: required: true content: - multipart/form-data: + application/json: schema: - type: object - properties: - file: - type: string - format: binary - description: The binary file of the package. - signature: - type: string - format: binary - description: The binary signature of the package. - branch: - type: string - description: The branch where the package will be deployed. - repository: - type: string - description: The repository where the package will be deployed. - architecture: - type: string - description: The architecture of the package deployment. - session: - type: string - description: The session ID for the deployment. + $ref: "#/components/schemas/UpdateUserRequest" responses: "200": - description: Package pushed successfully. + description: User updated successfully "400": - description: Invalid request, missing file, signature, or invalid section data. + description: Invalid request "401": - description: Invalid or missing API key. - /api/deploy/end: + description: Unauthorized + /api/users/remove: post: - summary: End a deployment session. + summary: Remove a user + operationId: removeUser + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/RemoveUserRequest" responses: "200": - description: Deployment session ended successfully. + description: User removed successfully "400": - description: Error message if ending the session failed. + description: Invalid request "401": - description: Invalid or missing API key. + description: Unauthorized + /api/users: + get: + summary: Get list of users + operationId: getUsers + responses: + "200": + description: List of users + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/UserResponse" + "400": + description: Invalid request + "401": + description: Unauthorized components: schemas: - User: + AuthRequest: type: object properties: name: type: string - permissions: + password: + type: string + required: + - name + - password + + CompareRequest: + type: array + items: + $ref: "#/components/schemas/SectionRequest" + + CompareResponse: + type: object + properties: + sections: type: array items: - type: string - Section: + $ref: "#/components/schemas/PackageSection" + compareTable: + type: object + additionalProperties: + type: object + additionalProperties: + type: object + additionalProperties: + type: string + + LogResponse: + type: array + items: + $ref: "#/components/schemas/LogEntryResponse" + + LogEntryResponse: + type: object + properties: + id: + type: string + time: + type: integer + format: int64 + type: + $ref: "#/components/schemas/LogEntryType" + package: + $ref: "#/components/schemas/PackageResponse" + required: + - id + - time + - type + - package + + LogEntryType: + type: string + enum: + - Add + - Remove + - Update + + PackageResponse: + type: object + properties: + name: + type: string + section: + type: string + poolEntries: + type: object + additionalProperties: + $ref: "#/components/schemas/PoolEntryResponse" + preferredLocation: + type: string + + PoolEntryResponse: + type: object + properties: + version: + type: string + signaturePath: + type: boolean + + PackageSection: type: object properties: branch: @@ -365,32 +338,64 @@ components: type: string architecture: type: string - Package: + required: + - branch + - repository + - architecture + + SectionRequest: + $ref: "#/components/schemas/PackageSection" + + SectionResponse: + $ref: "#/components/schemas/PackageSection" + + SnapRequest: + type: object + properties: + source: + $ref: "#/components/schemas/SectionRequest" + target: + $ref: "#/components/schemas/SectionRequest" + required: + - source + - target + + AddUserRequest: type: object properties: name: type: string - section: - $ref: "#/components/schemas/Section" - pool_entries: + password: + type: string + permissions: type: array items: - $ref: "#/components/schemas/PoolEntry" - PoolEntry: + type: string + + UpdateUserRequest: type: object properties: - version: + name: type: string - hasSignature: - type: boolean - LogEntry: + password: + type: string + permissions: + type: array + items: + type: string + + RemoveUserRequest: type: object properties: - id: - type: string - time: + name: type: string - package: - $ref: "#/components/schemas/Package" - action: + + UserResponse: + type: object + properties: + name: type: string + permissions: + type: array + items: + type: string diff --git a/frontend/src/components/DrawerLayout.tsx b/frontend/src/components/DrawerLayout.tsx index c05c390d..e7a7d9bd 100644 --- a/frontend/src/components/DrawerLayout.tsx +++ b/frontend/src/components/DrawerLayout.tsx @@ -22,7 +22,7 @@ import { } from "@fortawesome/free-solid-svg-icons"; const triggerSync = async () => { - await axios.post("/api/sync"); + await axios.post("/api/packages/sync"); }; export default () => { diff --git a/frontend/src/components/SnapshotModal.tsx b/frontend/src/components/SnapshotModal.tsx index 0f09c717..0c3d0514 100644 --- a/frontend/src/components/SnapshotModal.tsx +++ b/frontend/src/components/SnapshotModal.tsx @@ -72,7 +72,7 @@ export default forwardRef( return; } try { - await axios.post("/api/snap", { + await axios.post("/api/packages/snap", { source: sourceSection, target: targetSection }); diff --git a/frontend/src/definitions/logEntry.d.ts b/frontend/src/definitions/logEntry.d.ts index 4b9dad21..fee6c1ad 100644 --- a/frontend/src/definitions/logEntry.d.ts +++ b/frontend/src/definitions/logEntry.d.ts @@ -7,7 +7,7 @@ interface ILogEntry { id: string; - action: string; + type: string; package: IPackage; time: Date; } diff --git a/frontend/src/definitions/package.d.ts b/frontend/src/definitions/package.d.ts index eafe04ce..135428c0 100644 --- a/frontend/src/definitions/package.d.ts +++ b/frontend/src/definitions/package.d.ts @@ -14,7 +14,7 @@ interface IPackage { section: ISection; name: string; isAnyArchitecture?: boolean; - preferredCandidate?: IPackagePoolEntry; + preferredLocation?: string; poolEntries?: { [key: string]: IPackagePoolEntry; }; diff --git a/frontend/src/hooks/BxtFsHooks.ts b/frontend/src/hooks/BxtFsHooks.ts index 6206b63f..8f7e5dd1 100644 --- a/frontend/src/hooks/BxtFsHooks.ts +++ b/frontend/src/hooks/BxtFsHooks.ts @@ -25,16 +25,13 @@ export const useFilesFromSections = ( const [packages, setPackages] = useState(); const getPackages = async (sections: ISection[], path: string[]) => { - const value = await axios.get( - `${process.env.PUBLIC_URL}/api/packages/get`, - { - params: { - branch: path[1], - repository: path[2], - architecture: path[3] - } + const value = await axios.get(`/api/packages`, { + params: { + branch: path[1], + repository: path[2], + architecture: path[3] } - ); + }); if (value.data == null) { setFiles([]); setPackages(undefined); @@ -43,20 +40,22 @@ export const useFilesFromSections = ( setPackages(value.data); setFiles( - value.data.map( - (value: any): FileData => ({ - id: `root/${path[1]}/${path[2]}/${path[3]}/${value?.name}`, - name: value.name, + value.data.map((pkg: any): FileData => { + console.log(pkg); + return { + id: `root/${path[1]}/${path[2]}/${path[3]}/${pkg?.name}`, + name: pkg.name, ext: "", isDir: false, - thumbnailUrl: - value?.preferredCandidate.hasSignature == "true" + thumbnailUrl: pkg?.preferredLocation + ? pkg?.poolEntries[pkg?.preferredLocation].hasSignature ? `${process.env.PUBLIC_URL}/signature.svg` - : `${process.env.PUBLIC_URL}/package.png`, + : `${process.env.PUBLIC_URL}/package.png` + : "", icon: ChonkyIconName.archive, color: "#8B756B" - }) - ) + }; + }) ); }; diff --git a/frontend/src/hooks/BxtHooks.ts b/frontend/src/hooks/BxtHooks.ts index e8c40692..d40ff551 100644 --- a/frontend/src/hooks/BxtHooks.ts +++ b/frontend/src/hooks/BxtHooks.ts @@ -15,11 +15,9 @@ export const useSections = (): [ISection[], IUpdateSections] => { const [sections, setSections] = useState([]); const updateSections: IUpdateSections = useCallback(() => { - axios - .get(`${process.env.PUBLIC_URL}/api/sections/get`) - .then((response) => { - setSections(response.data); - }); + axios.get(`/api/sections`).then((response) => { + setSections(response.data); + }); }, [setSections]); useEffect(() => { @@ -74,8 +72,8 @@ export const useCompareResults = (): [ const compareEntries: ICompareEntry[] = []; - Object.keys(result.data["compare_table"]).forEach((value) => { - const versions = { ...result.data["compare_table"] }[value]; + Object.keys(result.data["compareTable"]).forEach((value) => { + const versions = { ...result.data["compareTable"] }[value]; compareEntries.push({ name: value, ...versions }); }); diff --git a/frontend/src/pages/AdminPage.tsx b/frontend/src/pages/AdminPage.tsx index 6893a38a..1b1c522c 100644 --- a/frontend/src/pages/AdminPage.tsx +++ b/frontend/src/pages/AdminPage.tsx @@ -31,7 +31,7 @@ export default () => { const removeUser = useCallback( async (user: IUser) => { - await axios.post("/api/users/remove", { id: user.name }); + await axios.post("/api/users/remove", { name: user.name }); reloadUsers(); }, [reloadUsers] diff --git a/frontend/src/pages/LogPage.tsx b/frontend/src/pages/LogPage.tsx index 141db2a1..c7b9504e 100644 --- a/frontend/src/pages/LogPage.tsx +++ b/frontend/src/pages/LogPage.tsx @@ -47,16 +47,28 @@ export default (props: any) => { const columnHelper = createColumnHelper(); const columns = [ - columnHelper.accessor("action", { - header: "Action" + columnHelper.accessor("type", { + header: "Type" }), columnHelper.accessor("package.name", { header: "Name" }), - columnHelper.accessor("package.preferredCandidate.version", { - header: "Version" + columnHelper.accessor("package.poolEntries", { + header: "Version", + cell: (context) => { + const value = context.getValue(); + const preferredLocation = + context?.row?.original?.package?.preferredLocation; + + if (value && preferredLocation && value[preferredLocation]) { + return value[preferredLocation].version; + } else { + return "Unknown Version"; + } + } }), + columnHelper.accessor("package.section", { header: "Section", cell: (context) => diff --git a/frontend/src/pages/MainPage.tsx b/frontend/src/pages/MainPage.tsx index 67903064..d530490b 100644 --- a/frontend/src/pages/MainPage.tsx +++ b/frontend/src/pages/MainPage.tsx @@ -27,7 +27,7 @@ export default (props: any) => { axios.interceptors.response.use( (response) => response, (error) => { - toast.error(`Response error: ${error.response?.data?.error}`, { + toast.error(`Response error: ${error.response?.data?.message}`, { autoClose: false }); }