mirror of
https://github.com/sebastiandine/Card-Collection-Manager-3.git
synced 2026-08-28 17:01:02 +00:00
136 lines
4.9 KiB
C++
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
|