#pragma once
#include <cstdint>
#include <memory>
#include <optional>
#include <string>
#include <vector>
#include <mw/error.hpp>
#include "card.h"
#include "game.h"
#include "user.h"
/// Raw SHA-256 credential digest stored as a 32-byte SQLite BLOB.
using TokenHash = std::vector<unsigned char>;
/// One distinct card and the quantity owned by a user.
struct CollectionEntry
{
/// Current card presentation.
Card card;
/// Positive quantity owned.
std::int64_t quantity;
};
/// Delivered, unconsumed authentication challenge metadata.
struct AuthenticationChallenge
{
/// Internal challenge identity.
std::int64_t id;
/// Validated delivery spelling.
std::string email;
/// Normalized account identity.
std::string email_key;
/// Unix expiry timestamp.
std::int64_t expires_at;
};
/// Valid session joined with its current user record.
struct SessionContext
{
/// Internal session identity.
std::int64_t session_id;
/// Current user record.
User user;
/// Independent authenticated-form CSRF credential.
std::string csrf_token;
/// Non-sliding Unix expiry timestamp.
std::int64_t expires_at;
};
/// Result of atomically reserving authentication email capacity.
enum class AuthenticationReservationStatus
{
RESERVED,
EMAIL_LIMITED,
GLOBAL_LIMITED
};
/// Reservation status and retry delay for a rejected request.
struct AuthenticationReservation
{
/// Atomic reservation result.
AuthenticationReservationStatus status;
/// Seconds until another attempt may succeed.
std::int64_t retry_after;
};
/// Latest schema version implemented by this binary.
inline constexpr std::int64_t DB_SCHEMA_VERSION = 1;
/// Storage transaction used for an atomic group of persistence operations.
class DataSourceTransactionInterface
{
public:
/// Destroy the transaction, rolling it back when it remains uncommitted.
virtual ~DataSourceTransactionInterface() = default;
/// Allocate and persist the next never-reused number for one game.
virtual mw::E<std::uint64_t>
allocateGameNumber(const std::string& game_short_name) = 0;
/// Ensure a database-defined game has a persistent sequence row.
virtual mw::E<void>
ensureGameSequence(const std::string& game_short_name) = 0;
/// Read a complete game definition while holding the writer lock.
virtual mw::E<std::optional<GameDefinitionSnapshot>>
getGameDefinitionForUpdate(const std::string& short_name);
/// Return deletion-relevant usage for one game.
virtual mw::E<GameUsage> getGameUsage(const std::string& short_name);
/// Insert a game and its zero-valued number sequence.
virtual mw::E<void> insertGame(const Game& game);
/// Replace mutable game metadata and advance a matching revision.
virtual mw::E<bool> updateGame(
const std::string& short_name,
const std::string& display_name,
const std::string& description,
GameVisibility visibility,
std::int64_t expected_revision);
/// Delete an unused game, its fields, choices, and sequence.
virtual mw::E<void> deleteGame(const std::string& short_name);
/// Insert a field and its initial ordered choices.
virtual mw::E<std::int64_t> insertGameField(
const GameField& field,
const std::vector<GameChoice>& choices);
/// Replace a field label and its complete ordered choice list.
virtual mw::E<void> updateGameField(
std::int64_t field_id,
const std::string& label,
const std::vector<GameChoice>& choices);
/// Delete a field that has no stored card values.
virtual mw::E<void> deleteGameField(std::int64_t field_id);
/// Return the number of cards with a value for a field.
virtual mw::E<std::int64_t> countFieldUsage(std::int64_t field_id);
/// Return the number of cards using one exact choice.
virtual mw::E<std::int64_t> countChoiceUsage(
std::int64_t field_id, const std::string& value);
/// Replace every field position for a game.
virtual mw::E<void> updateGameFieldOrder(
const std::string& short_name,
const std::vector<std::int64_t>& field_ids);
/// Return whether every requested series belongs to one game.
virtual mw::E<bool> seriesBelongToGame(
const std::string& short_name,
const std::vector<std::int64_t>& series_ids);
/// Advance an aggregate definition revision if it is still current.
virtual mw::E<bool> incrementGameRevision(
const std::string& short_name,
std::int64_t expected_revision);
/// Return whether a loose-card number already exists.
virtual mw::E<bool> looseNumberExists(std::uint32_t number) = 0;
/// Re-read a user while the transaction lock is held.
virtual mw::E<std::optional<User>>
getUserForUpdate(std::int64_t user_id);
/// Re-read a user by normalized email while the lock is held.
virtual mw::E<std::optional<User>> getUserByEmailKeyForUpdate(
const std::string& email_key);
/// Re-read a card while the transaction lock is held.
virtual mw::E<std::optional<Card>>
getCardForUpdate(std::int64_t card_id) = 0;
/// Return whether a user owns a card while the lock is held.
virtual mw::E<bool> userOwnsCardForUpdate(
std::int64_t user_id, std::int64_t card_id);
/// Return the positive-rarity pool in card-ID order under the lock.
virtual mw::E<std::vector<Card>> getPoolCardsForUpdate();
/// Insert a newly confirmed account and return its internal ID.
virtual mw::E<std::int64_t> insertUser(const User& user);
/// Atomically replace a user's normalized username pair.
virtual mw::E<bool> updateUsername(
std::int64_t user_id,
const std::string& username,
const std::string& username_key);
/// Conditionally promote one player to creator.
virtual mw::E<bool> promoteUser(std::int64_t user_id);
/// Reserve per-email and optional global delivery capacity.
virtual mw::E<AuthenticationReservation> reserveAuthenticationEmail(
const std::string& email_key,
std::int64_t now,
bool use_global_quota,
std::int64_t utc_day,
std::uint32_t daily_limit);
/// Insert a pending authentication challenge and return its ID.
virtual mw::E<std::int64_t> insertAuthenticationChallenge(
const std::string& email,
const std::string& email_key,
const TokenHash& token_hash,
std::int64_t created_at,
std::int64_t expires_at);
/// Activate a pending challenge after successful delivery.
virtual mw::E<bool> markAuthenticationChallengeDelivered(
std::int64_t challenge_id, std::int64_t delivered_at);
/// Delete one challenge after failed delivery.
virtual mw::E<void> deleteAuthenticationChallenge(
std::int64_t challenge_id);
/// Atomically consume one valid delivered challenge.
virtual mw::E<std::optional<AuthenticationChallenge>>
consumeAuthenticationChallenge(
const TokenHash& token_hash, std::int64_t now);
/// Invalidate every other outstanding challenge for an email key.
virtual mw::E<void> invalidateAuthenticationChallenges(
const std::string& email_key,
std::int64_t except_challenge_id,
std::int64_t now);
/// Insert a four-week session and return its internal ID.
virtual mw::E<std::int64_t> insertSession(
std::int64_t user_id,
const TokenHash& token_hash,
const std::string& csrf_token,
std::int64_t created_at,
std::int64_t expires_at);
/// Delete a session by its token digest.
virtual mw::E<void> deleteSession(const TokenHash& token_hash);
/// Persist a lazily refreshed pull state.
virtual mw::E<void> updatePullState(
std::int64_t user_id,
std::uint32_t stored_pulls,
std::int64_t refresh_day);
/// Insert or increment a holding and return its new quantity.
virtual mw::E<std::int64_t> incrementHolding(
std::int64_t user_id, std::int64_t card_id);
/// Insert a card with generic custom values and series memberships.
virtual mw::E<std::int64_t> insertCard(
const Card& card,
const std::vector<GameFieldValue>& field_values,
const std::vector<std::int64_t>& series_ids);
/// Replace a card and all of its generic custom values and memberships.
virtual mw::E<void> updateCard(
const Card& card,
const std::vector<GameFieldValue>& field_values,
const std::vector<std::int64_t>& series_ids);
/// Delete a card and its dependent database rows.
virtual mw::E<void> deleteCard(std::int64_t card_id) = 0;
/// Insert a series and return its internal ID.
virtual mw::E<std::int64_t> insertSeries(const Series& series) = 0;
/// Replace a series name and description without changing its game.
virtual mw::E<void> updateSeries(const Series& series) = 0;
/// Delete a series and its membership rows.
virtual mw::E<void> deleteSeries(std::int64_t series_id) = 0;
/// Commit the transaction and release its lock.
virtual mw::E<void> commit() = 0;
};
/// Common persistence API used by the application and services.
class DataSourceInterface
{
public:
/// Destroy the data source after all transactions have ended.
virtual ~DataSourceInterface() = default;
/// Return the stored schema version.
virtual mw::E<std::int64_t> getSchemaVersion() const = 0;
/// Apply every required migration through the current schema version.
mw::E<void> migrateToLatest();
/// Migrate an empty version-0 database to schema version 1.
virtual mw::E<void> migrateSchema0To1() = 0;
/// Start an immediate transaction with exclusive mutation ownership.
virtual mw::E<std::unique_ptr<DataSourceTransactionInterface>>
beginTransaction() = 0;
/// Return all cards for the unpaginated index.
virtual mw::E<std::vector<Card>> getCards(
GameContentScope scope) const = 0;
/// Return all cards authored by one user.
virtual mw::E<std::vector<Card>> getCardsByCreator(
std::int64_t creator_user_id,
GameContentScope scope) const;
/// Return every current positive-rarity card in card-ID order.
virtual mw::E<std::vector<Card>> getPoolCards() const;
/// Return a card by its parsed identity.
virtual mw::E<std::optional<Card>>
getCard(
const CardIdentity& identity,
GameContentScope scope) const = 0;
/// Return games ordered by display name and then short name.
virtual mw::E<std::vector<Game>> getGames(
GameContentScope scope) const;
/// Return a complete, consistently read game definition.
virtual mw::E<std::optional<GameDefinitionSnapshot>>
getGameDefinition(
const std::string& short_name,
GameContentScope scope) const;
/// Return a card's definition and ordered custom values together.
virtual mw::E<std::optional<CardGameFields>>
getCardFieldValues(
std::int64_t card_id,
GameContentScope scope) const;
/// Return a user by internal identity.
virtual mw::E<std::optional<User>> getUser(
std::int64_t user_id) const;
/// Return a user by normalized immutable email identity.
virtual mw::E<std::optional<User>> getUserByEmailKey(
const std::string& email_key) const;
/// Return every user for administrator management.
virtual mw::E<std::vector<User>> getUsers() const;
/// Return a valid session joined with its current user.
virtual mw::E<std::optional<SessionContext>> getSession(
const TokenHash& token_hash, std::int64_t now) const;
/// Read one valid delivered challenge without consuming it.
virtual mw::E<std::optional<AuthenticationChallenge>>
getAuthenticationChallenge(
const TokenHash& token_hash, std::int64_t now) const;
/// Return a user's distinct collection entries.
virtual mw::E<std::vector<CollectionEntry>> getCollection(
std::int64_t user_id,
GameContentScope scope) const;
/// Return whether a user currently owns a card.
virtual mw::E<bool> userOwnsCard(
std::int64_t user_id, std::int64_t card_id) const;
/// Reconcile the one immutable configured administrator identity.
virtual mw::E<User> reconcileAdministrator(
const std::string& email,
const std::string& email_key,
std::int64_t created_at,
std::int64_t pull_refresh_day);
/// Best-effort removal of obsolete authentication persistence rows.
virtual mw::E<void> cleanupAuthentication(std::int64_t now);
/// Return all series, ordered by game and name.
virtual mw::E<std::vector<Series>> getSeries(
GameContentScope scope) const = 0;
/// Return one series by internal ID.
virtual mw::E<std::optional<Series>>
getSeries(
std::int64_t series_id,
GameContentScope scope) const = 0;
/// Return the series memberships for one card.
virtual mw::E<std::vector<std::int64_t>>
getCardSeries(std::int64_t card_id) const = 0;
protected:
/// Set the schema version inside a concrete migration transaction.
virtual mw::E<void> setSchemaVersion(std::int64_t version) = 0;
};