major: initial release

* initial development

* pipeline

* pipeline

* pipeline

* pipeline

* pipeline

* pipeline

* pipeline

* pipeline

* pipeline

* pipeline

* pipeline

* pipeline

* pipeline

* pipeline

* ci/cd

* ci/cd

* ci/cd

* ci/cd

* ci/cd

* ci/cd

* ci/cd

* pokemon

* pokemon

* pokemon

* pokemon

* pokemon

* pokemon

* improvements

* improvements

* ci/cd

* ci/cd

* improvements

* improvements

* improvements

* improvements

* improvements

* improvements

* improvements

* improvements

* improvements

---------

Co-authored-by: sdine <sdine@sdine.com>
This commit is contained in:
Sebastian Dine
2026-05-09 11:05:47 +02:00
committed by GitHub
parent 13262fa015
commit 55ace147bc
149 changed files with 12611 additions and 0 deletions
+47
View File
@@ -0,0 +1,47 @@
#pragma once
// IGameModule + ISetSource: the seam that lets the UI handle Magic and Pokemon
// uniformly. New game support is "implement these interfaces and register the
// module in the composition root" - see the Rust `templates/` module for the
// pattern this is modeled after.
#include "ccm/domain/Enums.hpp"
#include "ccm/domain/Set.hpp"
#include "ccm/ports/ICardPreviewSource.hpp"
#include "ccm/util/Result.hpp"
#include <string>
#include <vector>
namespace ccm {
class ISetSource {
public:
virtual ~ISetSource() = default;
// Fetch the canonical set list from the game's external API.
// Implementations return a vector that has already been filtered
// (e.g. no digital-only sets) and sorted by release date ascending.
virtual Result<std::vector<Set>> fetchAll() = 0;
};
class IGameModule {
public:
virtual ~IGameModule() = default;
[[nodiscard]] virtual Game id() const noexcept = 0;
// Subdirectory name used inside the data storage root. Matches the Rust
// string literals "magic" / "pokemon".
[[nodiscard]] virtual std::string dirName() const = 0;
[[nodiscard]] virtual std::string displayName() const = 0;
virtual ISetSource& setSource() = 0;
// Optional preview source. Returning nullptr signals the game has no
// remote preview API (the UI then renders only locally stored images).
// The default keeps existing modules compiling without forcing every
// game to provide one.
virtual ICardPreviewSource* cardPreviewSource() noexcept { return nullptr; }
};
} // namespace ccm
@@ -0,0 +1,42 @@
#pragma once
// MagicCardPreviewSource: ICardPreviewSource implementation for Magic the
// Gathering. Calls Scryfall's search endpoint at
// https://api.scryfall.com/cards/search?q=name:"<name>" AND set:<setCode>
// and returns `data[0].image_uris.normal`. Mirrors the established
// `src/components/magic/SelectedMtgPanel.tsx::getImage`, including the
// `&` -> `and` substitution in the card name.
#include "ccm/ports/ICardPreviewSource.hpp"
#include "ccm/ports/IHttpClient.hpp"
#include <string>
#include <string_view>
namespace ccm {
class MagicCardPreviewSource final : public ICardPreviewSource {
public:
explicit MagicCardPreviewSource(IHttpClient& http);
Result<std::string> fetchImageUrl(std::string_view name,
std::string_view setId,
std::string_view setNo) override;
// Build the fully URL-encoded Scryfall search URL for the given card.
// Exposed for unit testing and to keep encoding rules in one place.
static std::string buildSearchUrl(std::string_view name,
std::string_view setId);
// Parse a Scryfall /cards/search response body and pull out the
// `data[0].image_uris.normal` URL. Returns an error result when no
// matching printing is found, when the JSON is malformed, or when the
// entry has no top-level `image_uris` (double-faced cards expose them
// on a face object - no fallback in this compatibility behavior either).
static Result<std::string> parseResponse(const std::string& body);
private:
IHttpClient& http_;
};
} // namespace ccm
@@ -0,0 +1,29 @@
#pragma once
// MagicGameModule: IGameModule for Magic the Gathering. Owns its set source
// and card preview source, reports the canonical "magic" subdirectory name
// used on disk.
#include "ccm/games/IGameModule.hpp"
#include "ccm/games/magic/MagicCardPreviewSource.hpp"
#include "ccm/games/magic/MagicSetSource.hpp"
namespace ccm {
class MagicGameModule final : public IGameModule {
public:
explicit MagicGameModule(IHttpClient& http);
[[nodiscard]] Game id() const noexcept override { return Game::Magic; }
[[nodiscard]] std::string dirName() const override { return "magic"; }
[[nodiscard]] std::string displayName() const override { return "Magic"; }
ISetSource& setSource() override { return setSource_; }
ICardPreviewSource* cardPreviewSource() noexcept override { return &previewSource_; }
private:
MagicSetSource setSource_;
MagicCardPreviewSource previewSource_;
};
} // namespace ccm
@@ -0,0 +1,28 @@
#pragma once
// MagicSetSource: ISetSource implementation for Magic the Gathering.
// Calls the Scryfall API at https://api.scryfall.com/sets, drops digital-only
// sets, maps the response into our `Set` domain type, and sorts by release date
// ascending. Behavior matches `magic/set_services.rs::update_sets`.
#include "ccm/games/IGameModule.hpp"
#include "ccm/ports/IHttpClient.hpp"
namespace ccm {
class MagicSetSource final : public ISetSource {
public:
static constexpr const char* kEndpoint = "https://api.scryfall.com/sets";
explicit MagicSetSource(IHttpClient& http);
Result<std::vector<Set>> fetchAll() override;
// Pure parser exposed for unit testing without a network round-trip.
static Result<std::vector<Set>> parseResponse(const std::string& body);
private:
IHttpClient& http_;
};
} // namespace ccm
@@ -0,0 +1,42 @@
#pragma once
// PokemonCardPreviewSource: ICardPreviewSource implementation for the Pokemon
// TCG. Calls the Pokemon TCG search endpoint at
// https://api.pokemontcg.io/v2/cards?q=name:"<name>" set.id:<setId> number:<setNo>
// and returns `data[0].images.large` (with `images.small` as a graceful
// fallback). Mirrors the established `getImage` flow in
// `src/components/pokemon/SelectedPokemonPanel.tsx`.
#include "ccm/ports/ICardPreviewSource.hpp"
#include "ccm/ports/IHttpClient.hpp"
#include <string>
#include <string_view>
namespace ccm {
class PokemonCardPreviewSource final : public ICardPreviewSource {
public:
explicit PokemonCardPreviewSource(IHttpClient& http);
Result<std::string> fetchImageUrl(std::string_view name,
std::string_view setId,
std::string_view setNo) override;
// Build the fully URL-encoded Pokemon TCG search URL for the given card.
// Exposed for unit testing and to keep encoding rules in one place.
static std::string buildSearchUrl(std::string_view name,
std::string_view setId,
std::string_view setNo);
// Parse a Pokemon TCG /v2/cards response body and pull out the image URL
// for the first matching card. Prefers `images.large`, falls back to
// `images.small`, and returns an error result if neither is present, the
// data array is empty, or the JSON is malformed.
static Result<std::string> parseResponse(const std::string& body);
private:
IHttpClient& http_;
};
} // namespace ccm
@@ -0,0 +1,28 @@
#pragma once
// PokemonGameModule: IGameModule for the Pokemon TCG. Owns its set source
// and card preview source, both backed by api.pokemontcg.io/v2.
#include "ccm/games/IGameModule.hpp"
#include "ccm/games/pokemon/PokemonCardPreviewSource.hpp"
#include "ccm/games/pokemon/PokemonSetSource.hpp"
namespace ccm {
class PokemonGameModule final : public IGameModule {
public:
explicit PokemonGameModule(IHttpClient& http);
[[nodiscard]] Game id() const noexcept override { return Game::Pokemon; }
[[nodiscard]] std::string dirName() const override { return "pokemon"; }
[[nodiscard]] std::string displayName() const override { return "Pokemon"; }
ISetSource& setSource() override { return setSource_; }
ICardPreviewSource* cardPreviewSource() noexcept override { return &previewSource_; }
private:
PokemonSetSource setSource_;
PokemonCardPreviewSource previewSource_;
};
} // namespace ccm
@@ -0,0 +1,30 @@
#pragma once
// PokemonSetSource: ISetSource implementation for the Pokemon TCG.
// Calls the Pokemon TCG API at https://api.pokemontcg.io/v2/sets, maps the
// response into our `Set` domain type, and sorts by release date ascending.
// The Pokemon TCG API already returns `releaseDate` in `YYYY/MM/DD` format,
// so no rewriting is needed (unlike Scryfall's `released_at`).
// Behavior matches `pokemon/set_services.rs::update_sets`.
#include "ccm/games/IGameModule.hpp"
#include "ccm/ports/IHttpClient.hpp"
namespace ccm {
class PokemonSetSource final : public ISetSource {
public:
static constexpr const char* kEndpoint = "https://api.pokemontcg.io/v2/sets";
explicit PokemonSetSource(IHttpClient& http);
Result<std::vector<Set>> fetchAll() override;
// Pure parser exposed for unit testing without a network round-trip.
static Result<std::vector<Set>> parseResponse(const std::string& body);
private:
IHttpClient& http_;
};
} // namespace ccm