#pragma once
#include <memory>
#include <optional>
#include <string>
#include <vector>
#include <inja/inja.hpp>
#include <mw/crypto.hpp>
#include <mw/http_server.hpp>
#include "authentication.h"
#include "card_service.h"
#include "clock.h"
#include "collection.h"
#include "config.h"
#include "data.h"
#include "email_sender.h"
#include "game_service.h"
#include "non_secret_random.h"
#include "series_service.h"
#include "url_builder.h"
#include "user_service.h"
struct CardUpload;
struct GameFieldValidationError;
/// Card Collection HTTP application and named-route owner.
class App : public mw::HTTPServer
{
public:
/// HTTP request type supplied by libmw.
using Request = mw::HTTPServer::Request;
/// HTTP response type supplied by libmw.
using Response = mw::HTTPServer::Response;
/// Streaming request-body reader supplied by cpp-httplib.
using ContentReader = httplib::ContentReader;
/// Disable construction without validated configuration.
App() = delete;
/// Construct the application from configuration and a data source.
App(
const Config& config,
std::unique_ptr<DataSourceInterface> data_source,
std::unique_ptr<NonSecretRandom> random);
/// Return the absolute URL for a named application route.
std::string urlFor(
const std::string& name,
const std::vector<std::string>& arguments = {},
const QueryParameters& query = {}) const;
/// Render the card index.
void handleCardIndex(const Request& request, Response& response);
/// Redirect an authenticated visitor or render the public welcome page.
void handleWelcome(const Request& request, Response& response);
/// Render the passwordless email request form.
void handleAuthentication(const Request& request, Response& response);
/// Reserve and send a passwordless authentication link.
void handleAuthenticationEmail(
const Request& request, Response& response);
/// Render the neutral authentication-email acknowledgement.
void handleAuthenticationSent(
const Request& request, Response& response);
/// Render a non-consuming authentication confirmation form.
void handleAuthenticationConfirm(
const Request& request, Response& response);
/// Consume an authentication challenge and establish a session.
void handleAuthenticationConfirmPost(
const Request& request, Response& response);
/// Revoke the current session.
void handleLogout(const Request& request, Response& response);
/// Render the required initial-username form.
void handleOnboarding(const Request& request, Response& response);
/// Persist the required initial username.
void handleOnboardingPost(const Request& request, Response& response);
/// Render the current account summary.
void handleAccount(const Request& request, Response& response);
/// Render the username-change form.
void handleAccountUsername(const Request& request, Response& response);
/// Persist a username change.
void handleAccountUsernamePost(
const Request& request, Response& response);
/// Render the current user's card collection and pull balance.
void handleCollection(const Request& request, Response& response);
/// Perform one atomic collection pull.
void handleCollectionPull(const Request& request, Response& response);
/// Render the administrator's user table.
void handleAdminUsers(const Request& request, Response& response);
/// Render the administrator's installed-game inventory.
void handleAdminGames(const Request& request, Response& response);
/// Render the create-game form.
void handleGameNew(const Request& request, Response& response);
/// Create a database-defined game.
void handleGameCreate(const Request& request, Response& response);
/// Render one game and its field-definition editor.
void handleGameEdit(const Request& request, Response& response);
/// Update one game's mutable metadata.
void handleGameUpdate(const Request& request, Response& response);
/// Render a game-delete confirmation.
void handleGameDeleteConfirm(const Request& request, Response& response);
/// Delete an unused game.
void handleGameDelete(const Request& request, Response& response);
/// Render the add-field form for one game.
void handleGameFieldNew(const Request& request, Response& response);
/// Add one custom field to a game.
void handleGameFieldCreate(const Request& request, Response& response);
/// Render one custom-field editor.
void handleGameFieldEdit(const Request& request, Response& response);
/// Update one custom field and its choices.
void handleGameFieldUpdate(const Request& request, Response& response);
/// Render a custom-field delete confirmation.
void handleGameFieldDeleteConfirm(
const Request& request, Response& response);
/// Delete one unused custom field.
void handleGameFieldDelete(const Request& request, Response& response);
/// Save the complete custom-field ordering for one game.
void handleGameFieldOrder(const Request& request, Response& response);
/// Permanently promote one player to creator.
void handleAdminPromote(const Request& request, Response& response);
/// Render the create-card form.
void handleCardNew(const Request& request, Response& response);
/// Accept, process, and persist a new card upload.
void handleCardCreate(
const Request& request,
Response& response,
const ContentReader& content_reader);
/// Render one read-only card page.
void handleCardView(const Request& request, Response& response);
/// Render the edit form for one card.
void handleCardEdit(const Request& request, Response& response);
/// Accept and persist one card edit.
void handleCardUpdate(
const Request& request,
Response& response,
const ContentReader& content_reader);
/// Render the card-delete confirmation page.
void handleCardDeleteConfirm(
const Request& request,
Response& response);
/// Delete one card and redirect to the index.
void handleCardDelete(const Request& request, Response& response);
/// Render the series administration index.
void handleSeriesIndex(const Request& request, Response& response);
/// Render the create-series form.
void handleSeriesNew(const Request& request, Response& response);
/// Persist a new series.
void handleSeriesCreate(const Request& request, Response& response);
/// Render the edit-series form.
void handleSeriesEdit(const Request& request, Response& response);
/// Persist changes to one series.
void handleSeriesUpdate(const Request& request, Response& response);
/// Render the delete-series confirmation page.
void handleSeriesDeleteConfirm(
const Request& request,
Response& response);
/// Delete one series and redirect to the series index.
void handleSeriesDelete(const Request& request, Response& response);
private:
struct RequestIdentity
{
SessionContext session;
std::string raw_token;
};
/// Register implemented handlers and static mounts.
void setup() override;
/// Return the server request path for a named dynamic route.
std::string getPath(
const std::string& name,
const std::vector<std::string>& argument_names = {}) const;
/// Return the request prefix for a named static mount.
std::string getMountPath(const std::string& name) const;
/// Load and authorize one browser session, writing failures to response.
std::optional<RequestIdentity> requireIdentity(
const Request& request,
Response& response,
bool safe_get,
bool allow_onboarding = false);
/// Verify one authenticated mutation's synchronizer token.
bool verifyCsrf(
const Request& request,
const RequestIdentity& identity,
Response& response) const;
/// Add role-aware authenticated navigation links to template data.
void addNavigationData(inja::json& data, const User& user) const;
/// Render an expected mutation failure with a safe retry destination.
void respondMutationError(
Response& response,
const mw::Error& error,
std::string_view operation,
const std::string& reload_url,
const User& user,
bool files_need_reselection = false);
/// Re-render a card form after custom-field validation fails.
void respondCardFieldError(
Response& response,
const GameFieldValidationError& error,
const CardUpload& upload,
const RequestIdentity& identity,
const Card* card = nullptr);
/// Re-render a game metadata form after definition validation fails.
void respondGameDefinitionError(
Response& response,
const GameDefinitionValidationError& error,
const RequestIdentity& identity,
const std::string& short_name,
const std::string& display_name,
const std::string& description,
GameVisibility visibility,
bool editing);
/// Re-render a field editor after definition validation fails.
void respondGameFieldDefinitionError(
Response& response,
const GameDefinitionValidationError& error,
const RequestIdentity& identity,
const std::string& short_name,
std::optional<std::int64_t> field_id,
const std::string& key,
const std::string& label,
const std::string& type,
const std::vector<std::string>& choices);
/// Set or expire a host-only application cookie.
void setCookie(
Response& response,
const std::string& name,
const std::string& value,
std::int64_t maximum_age,
bool strict_same_site) const;
Config config_;
std::unique_ptr<DataSourceInterface> data_source_;
std::unique_ptr<NonSecretRandom> random_;
std::unique_ptr<ClockInterface> clock_;
std::unique_ptr<mw::CryptoInterface> crypto_;
std::unique_ptr<EmailSenderInterface> email_sender_;
std::unique_ptr<AuthenticationService> authentication_service_;
std::unique_ptr<UserService> user_service_;
std::unique_ptr<CollectionService> collection_service_;
std::unique_ptr<CardService> card_service_;
std::unique_ptr<SeriesService> series_service_;
std::unique_ptr<GameService> game_service_;
UrlBuilder url_builder_;
inja::Environment templates_;
inja::Template card_form_template_;
inja::Template card_delete_template_;
inja::Template card_index_template_;
inja::Template card_view_template_;
inja::Template game_admin_template_;
inja::Template series_delete_template_;
inja::Template series_form_template_;
inja::Template series_index_template_;
};