Files
Card-Collection-Manager-3-s…/core/include/ccm/services/CollectionService.hpp
T
2026-07-23 17:58:00 +02:00

136 lines
4.9 KiB
C++

#pragma once
// CollectionService<TCard>: high-level CRUD over the per-game collection.
// Header-only template that operates on the ICollectionRepository<T> port and
// delegates image cleanup to an IImageStore on remove.
//
// This is the C++ equivalent of the Rust generic helpers in
// `templates/card_service_templates.rs` (add_entry_to_collection,
// update_entry_in_collection, get_entry_by_id, delete_entry_by_id, ...).
//
// Each TCard must expose `std::uint32_t id` and `std::vector<std::string> images`.
#include "ccm/domain/Enums.hpp"
#include "ccm/ports/ICollectionRepository.hpp"
#include "ccm/ports/IImageStore.hpp"
#include "ccm/util/Result.hpp"
#include <algorithm>
#include <cstdint>
#include <map>
#include <optional>
#include <string>
#include <utility>
#include <vector>
namespace ccm {
template <typename TCard>
class CollectionService {
public:
using Map = std::map<std::uint32_t, TCard>;
CollectionService(ICollectionRepository<TCard>& repo, IImageStore& imageStore)
: repo_(repo), imageStore_(imageStore) {}
Result<std::vector<TCard>> list(Game game) {
auto loaded = repo_.load(game);
if (!loaded) return Result<std::vector<TCard>>::err(loaded.error());
Map map = std::move(loaded).value();
std::vector<TCard> out;
out.reserve(map.size());
for (auto& [id, card] : map) {
(void)id;
out.push_back(std::move(card));
}
return Result<std::vector<TCard>>::ok(std::move(out));
}
[[nodiscard]] static std::uint32_t nextId(const Map& map) noexcept {
if (map.empty()) return 0;
return map.rbegin()->first + 1;
}
// Add a card. The `id` field on the input is overwritten with the next
// free id (matching the Rust HashMap-keyed behavior).
Result<std::uint32_t> add(Game game, TCard card) {
auto loaded = repo_.load(game);
if (!loaded) return Result<std::uint32_t>::err(loaded.error());
Map map = std::move(loaded).value();
const std::uint32_t newId = nextId(map);
card.id = newId;
map.emplace(newId, std::move(card));
auto saved = repo_.save(game, map);
if (!saved) return Result<std::uint32_t>::err(saved.error());
return Result<std::uint32_t>::ok(newId);
}
// Update an existing entry, identified by `card.id`. If the id is not
// present, an error is returned.
Result<void> update(Game game, TCard card) {
auto loaded = repo_.load(game);
if (!loaded) return Result<void>::err(loaded.error());
Map map = std::move(loaded).value();
auto it = map.find(card.id);
if (it == map.end()) {
return Result<void>::err("Card with id " + std::to_string(card.id) +
" not found in collection.");
}
it->second = std::move(card);
return repo_.save(game, map);
}
// Replace the entire collection map in one save (e.g. after bulk set-id sync).
Result<void> saveAll(Game game, std::vector<TCard> cards) {
Map map;
for (auto& card : cards) {
map.insert_or_assign(card.id, std::move(card));
}
return repo_.save(game, map);
}
// Remove the card with the given id. Also deletes any associated images
// via the IImageStore (best-effort - image removal failures are logged in
// the error string but the card itself is still purged from the JSON).
Result<void> remove(Game game, std::uint32_t id) {
auto loaded = repo_.load(game);
if (!loaded) return Result<void>::err(loaded.error());
Map map = std::move(loaded).value();
auto it = map.find(id);
if (it == map.end()) {
return Result<void>::err("Card with id " + std::to_string(id) + " not found.");
}
std::string imgErr;
for (const auto& image : it->second.images) {
auto rm = imageStore_.remove(game, image);
if (!rm) {
if (!imgErr.empty()) imgErr += "; ";
imgErr += rm.error();
}
}
map.erase(it);
auto saved = repo_.save(game, map);
if (!saved) return saved;
if (!imgErr.empty()) {
return Result<void>::err("Card removed but image cleanup had issues: " + imgErr);
}
return Result<void>::ok();
}
// Look up a card by id without mutating storage.
Result<std::optional<TCard>> findById(Game game, std::uint32_t id) {
auto loaded = repo_.load(game);
if (!loaded) return Result<std::optional<TCard>>::err(loaded.error());
const auto& m = loaded.value();
auto it = m.find(id);
if (it == m.end()) return Result<std::optional<TCard>>::ok(std::nullopt);
return Result<std::optional<TCard>>::ok(it->second);
}
private:
ICollectionRepository<TCard>& repo_;
IImageStore& imageStore_;
};
} // namespace ccm