diff --git a/daemon/core/application/services/PackageService.h b/daemon/core/application/services/PackageService.h index 300101d..70e1a5f 100644 --- a/daemon/core/application/services/PackageService.h +++ b/daemon/core/application/services/PackageService.h @@ -59,6 +59,10 @@ class PackageService { virtual coro::task> snap(const PackageSectionDTO from_section, const PackageSectionDTO to_section) = 0; + + virtual coro::task> snap_branch(const std::string from_branch, + const std::string to_branch, + const std::string arch) = 0; }; } // namespace bxt::Core::Application diff --git a/daemon/di.h b/daemon/di.h index 4b13b1a..3908d81 100644 --- a/daemon/di.h +++ b/daemon/di.h @@ -209,6 +209,7 @@ namespace Infrastructure { bxt::Infrastructure::PackageService, kgr::dependency>, kgr::overrides {}; diff --git a/daemon/infrastructure/PackageService.cpp b/daemon/infrastructure/PackageService.cpp index 99edf41..d5e82ed 100644 --- a/daemon/infrastructure/PackageService.cpp +++ b/daemon/infrastructure/PackageService.cpp @@ -12,6 +12,7 @@ #include "core/application/errors/CrudError.h" #include "core/application/events/CommitEvent.h" #include "core/application/events/IntegrationEventBase.h" +#include "core/domain/entities/Section.h" #include "coro/task.hpp" #include "coro/when_all.hpp" #include "utilities/Error.h" @@ -19,6 +20,7 @@ #include #include +#include #include namespace bxt::Infrastructure { @@ -293,4 +295,73 @@ coro::task> std::move(to_copy))); co_return {}; } + +coro::task> + PackageService::snap_branch(const std::string from_branch, + const std::string to_branch, + const std::string arch) { + auto uow = co_await m_uow_factory(true); + + auto from_sections = co_await m_section_repository.find_async( + [from_branch, arch](const auto& section) { + return section.architecture() == arch + && section.branch() == from_branch; + }, + uow); + + if (!from_sections.has_value()) { + co_return bxt::make_error_with_source( + std::move(from_sections.error()), + CrudError::ErrorType::InternalError); + } + + auto to_sections = co_await m_section_repository.find_async( + [to_branch, arch](const auto& section) { + return section.architecture() == arch + && section.branch() == to_branch; + }, + uow); + + for (const auto& section : *to_sections) { + auto to_delete_packages = + co_await m_repository.find_by_section_async(section, uow); + if (!to_delete_packages.has_value()) { + co_return bxt::make_error_with_source( + std::move(to_delete_packages.error()), + CrudError::ErrorType::InternalError); + } + auto to_delete_ids = *to_delete_packages + | std::views::transform([](const auto& package) { + return package.id(); + }) + | std::ranges::to(); + + auto deleted = co_await m_repository.delete_async(to_delete_ids, uow); + } + + std::vector packages; + for (const auto& section : *from_sections) { + auto from_packages = + co_await m_repository.find_by_section_async(section, uow); + + if (!from_packages.has_value()) { + co_return bxt::make_error_with_source( + std::move(from_packages.error()), + CrudError::ErrorType::InternalError); + } + + packages = *from_packages | std::views::transform([&](auto&& package) { + auto package_section = package.section(); + package_section.set_branch(to_branch); + package.set_section(package_section); + return package; + }) | std::ranges::to(); + } + + auto saved = co_await m_repository.save_async(packages, uow); + + co_await uow->commit_async(); + + co_return {}; +} } // namespace bxt::Infrastructure diff --git a/daemon/infrastructure/PackageService.h b/daemon/infrastructure/PackageService.h index 8177942..60f3bd1 100644 --- a/daemon/infrastructure/PackageService.h +++ b/daemon/infrastructure/PackageService.h @@ -10,7 +10,9 @@ #include "PackageServiceOptions.h" #include "core/application/dtos/PackageDTO.h" #include "core/application/services/PackageService.h" +#include "core/domain/entities/Section.h" #include "core/domain/repositories/PackageRepositoryBase.h" +#include "core/domain/repositories/ReadOnlyRepositoryBase.h" #include "core/domain/repositories/UnitOfWorkBase.h" #include "coro/task.hpp" #include "utilities/eventbus/EventBusDispatcher.h" @@ -23,9 +25,12 @@ class PackageService : public Core::Application::PackageService { public: PackageService(Utilities::EventBusDispatcher& dispatcher, Core::Domain::PackageRepositoryBase& repository, + Core::Domain::ReadOnlyRepositoryBase& + section_repository, UnitOfWorkBaseFactory& uow_factory) : m_dispatcher(dispatcher), m_repository(repository), + m_section_repository(section_repository), m_uow_factory(uow_factory) {} virtual coro::task> @@ -41,6 +46,10 @@ class PackageService : public Core::Application::PackageService { coro::task> push(const Transaction transaction, const RequestContext context) override; + coro::task> snap_branch(const std::string from_branch, + const std::string to_branch, + const std::string arch) override; + private: coro::task> add_package(const PackageDTO package, std::shared_ptr uow); @@ -60,6 +69,7 @@ class PackageService : public Core::Application::PackageService { PackageServiceOptions m_options; Utilities::EventBusDispatcher& m_dispatcher; Core::Domain::PackageRepositoryBase& m_repository; + Core::Domain::ReadOnlyRepositoryBase
& m_section_repository; UnitOfWorkBaseFactory& m_uow_factory; }; diff --git a/daemon/presentation/messages/PackageMessages.h b/daemon/presentation/messages/PackageMessages.h index 0dc15d7..92a1792 100644 --- a/daemon/presentation/messages/PackageMessages.h +++ b/daemon/presentation/messages/PackageMessages.h @@ -19,6 +19,12 @@ struct SnapRequest { SectionRequest target; }; +struct SnapBranchRequest { + std::string source_branch; + std::string target_branch; + std::string architecture; +}; + struct PoolEntryResponse { std::string version; bool has_signature; diff --git a/daemon/presentation/web-controllers/PackageController.cpp b/daemon/presentation/web-controllers/PackageController.cpp index 78509ed..b9f55ab 100644 --- a/daemon/presentation/web-controllers/PackageController.cpp +++ b/daemon/presentation/web-controllers/PackageController.cpp @@ -291,7 +291,7 @@ drogon::Task BXT_JWT_CHECK_PERMISSIONS( (std::vector { - fmt::format("packages.snap.{}.{}.{}", source_branch.branch, + fmt::format("advanced.packages.snap.{}.{}.{}", source_branch.branch, source_branch.repository, source_branch.architecture), fmt::format("sections.{}.{}.{}", source_branch.branch, source_branch.repository, source_branch.architecture)}), @@ -318,4 +318,34 @@ drogon::Task co_return drogon_helpers::make_ok_response(); } +drogon::Task + PackageController::snap_branch(drogon::HttpRequestPtr req) { + const auto snap_request = + rfl::json::read(std::string(req->getBody())); + + if (snap_request.error()) { + co_return drogon_helpers::make_error_response("Invalid arguments"); + } + auto &source_branch = (*snap_request).source_branch; + auto &target_branch = (*snap_request).target_branch; + auto &arch = (*snap_request).architecture; + + BXT_JWT_CHECK_PERMISSIONS( + (std::vector { + fmt::format("sections.{}.*.{}", source_branch, arch), + fmt::format("packages.snap.from.{}", source_branch), + fmt::format("sections.{}.*.{}", target_branch, arch), + fmt::format("packages.snap.to.{}", target_branch)}), + req) + + const auto snap_ok = co_await m_package_service.snap_branch( + source_branch, target_branch, arch); + + if (!snap_ok.has_value()) { + co_return drogon_helpers::make_error_response( + fmt::format("Snap section failed: {}", snap_ok.error().what())); + } + + 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 c0874ca..632703b 100644 --- a/daemon/presentation/web-controllers/PackageController.h +++ b/daemon/presentation/web-controllers/PackageController.h @@ -45,10 +45,14 @@ class PackageController "/api/packages/sync", drogon::Post); - BXT_JWT_ADD_METHOD_TO(PackageController::snap, - "/api/packages/snap", + BXT_JWT_ADD_METHOD_TO(PackageController::snap_branch, + "/api/packages/snap/branch", drogon::Post); + // Methods for advanced operations. These are not exposed to the frontend. + BXT_JWT_ADD_METHOD_TO(PackageController::snap, + "/api/advanced/packages/snap", + drogon::Post); METHOD_LIST_END drogon::Task sync(drogon::HttpRequestPtr req); @@ -64,6 +68,9 @@ class PackageController drogon::Task snap(drogon::HttpRequestPtr req); + drogon::Task + snap_branch(drogon::HttpRequestPtr req); + private: Core::Application::PackageService &m_package_service; Core::Application::SyncService &m_sync_service; diff --git a/web/src/modals/SnapshotModal.tsx b/web/src/modals/SnapshotModal.tsx index 6217fc9..a0d934d 100644 --- a/web/src/modals/SnapshotModal.tsx +++ b/web/src/modals/SnapshotModal.tsx @@ -64,17 +64,17 @@ export const SnapshotModal = forwardRef( const doSnap = useCallback(async () => { if ( - !sourceSection || - !targetSection || - Object.values(sourceSection).some((el) => el === undefined) || - Object.values(targetSection).some((el) => el === undefined) + !sourceSection?.branch || + !targetSection?.branch || + !sourceSection?.architecture ) { return; } try { - await axios.post("/api/packages/snap", { - source: sourceSection, - target: targetSection + await axios.post("/api/packages/snap/branch", { + sourceBranch: sourceSection.branch, + targetBranch: targetSection.branch, + architecture: sourceSection.architecture }); internalRef?.current?.close(); } catch (error) {}