#include "series_service.h"
#include <algorithm>
#include <cstdint>
#include <string>
#include <utility>
#include <mw/utils.hpp>
namespace
{
mw::E<void> rejectDuplicate(
DataSourceInterface& data_source,
const Series& candidate)
{
auto series = data_source.getSeries(
GameContentScope::INCLUDE_INTERNAL);
if(!series)
{
return std::unexpected(std::move(series.error()));
}
const bool duplicate = std::ranges::any_of(
*series,
[&candidate](const Series& existing)
{
return existing.id != candidate.id &&
existing.game_short_name == candidate.game_short_name &&
existing.name == candidate.name;
});
if(duplicate)
{
return std::unexpected(mw::httpError(
409, "A series with this name already exists for the game"));
}
return {};
}
mw::E<void> authorizeAdministrator(
DataSourceTransactionInterface& transaction,
const AuthorizationService& authorization,
std::int64_t actor_user_id)
{
auto actor = transaction.getUserForUpdate(actor_user_id);
if(!actor)
{
return std::unexpected(std::move(actor.error()));
}
if(!*actor || !(*actor)->username ||
!authorization.canAdminister(**actor))
{
return std::unexpected(mw::httpError(403, "Administrator required"));
}
return {};
}
} // namespace
SeriesService::SeriesService(DataSourceInterface& data_source)
: data_source_(data_source)
{}
mw::E<std::int64_t> SeriesService::create(
std::int64_t actor_user_id,
std::string game_short_name,
std::string name,
std::string description)
{
name = std::string(mw::strip(name));
if(name.empty())
{
return std::unexpected(mw::httpError(422, "Series name is required"));
}
if(name.size() > 200)
{
return std::unexpected(mw::httpError(422, "Series name is too long"));
}
auto rendered = markdown_renderer_.render(description);
if(!rendered)
{
return std::unexpected(std::move(rendered.error()));
}
Series series = {
0,
std::move(game_short_name),
std::move(name),
std::move(description),
};
auto unique = rejectDuplicate(data_source_, series);
if(!unique)
{
return std::unexpected(std::move(unique.error()));
}
auto transaction = data_source_.beginTransaction();
if(!transaction)
{
return std::unexpected(std::move(transaction.error()));
}
auto authorized = authorizeAdministrator(
**transaction, authorization_, actor_user_id);
if(!authorized)
{
return std::unexpected(std::move(authorized.error()));
}
auto game = (*transaction)->getGameDefinitionForUpdate(
series.game_short_name);
if(!game)
{
return std::unexpected(std::move(game.error()));
}
if(!*game)
{
return std::unexpected(mw::httpError(422, "Unknown game"));
}
auto id = (*transaction)->insertSeries(series);
if(!id)
{
return std::unexpected(std::move(id.error()));
}
auto committed = (*transaction)->commit();
if(!committed)
{
return std::unexpected(std::move(committed.error()));
}
return *id;
}
mw::E<void> SeriesService::update(
std::int64_t actor_user_id,
std::int64_t series_id,
std::string name,
std::string description)
{
auto current = data_source_.getSeries(
series_id, GameContentScope::INCLUDE_INTERNAL);
if(!current)
{
return std::unexpected(std::move(current.error()));
}
if(!*current)
{
return std::unexpected(mw::httpError(404, "Series not found"));
}
Series series = std::move(**current);
series.name = std::string(mw::strip(name));
series.description = std::move(description);
if(series.name.empty())
{
return std::unexpected(mw::httpError(422, "Series name is required"));
}
if(series.name.size() > 200)
{
return std::unexpected(mw::httpError(422, "Series name is too long"));
}
auto rendered = markdown_renderer_.render(series.description);
if(!rendered)
{
return std::unexpected(std::move(rendered.error()));
}
auto unique = rejectDuplicate(data_source_, series);
if(!unique)
{
return std::unexpected(std::move(unique.error()));
}
auto transaction = data_source_.beginTransaction();
if(!transaction)
{
return std::unexpected(std::move(transaction.error()));
}
auto authorized = authorizeAdministrator(
**transaction, authorization_, actor_user_id);
if(!authorized)
{
return std::unexpected(std::move(authorized.error()));
}
auto updated = (*transaction)->updateSeries(series);
if(!updated)
{
return std::unexpected(std::move(updated.error()));
}
return (*transaction)->commit();
}
mw::E<void> SeriesService::remove(
std::int64_t actor_user_id,
std::int64_t series_id)
{
auto current = data_source_.getSeries(
series_id, GameContentScope::INCLUDE_INTERNAL);
if(!current)
{
return std::unexpected(std::move(current.error()));
}
if(!*current)
{
return std::unexpected(mw::httpError(404, "Series not found"));
}
auto transaction = data_source_.beginTransaction();
if(!transaction)
{
return std::unexpected(std::move(transaction.error()));
}
auto authorized = authorizeAdministrator(
**transaction, authorization_, actor_user_id);
if(!authorized)
{
return std::unexpected(std::move(authorized.error()));
}
auto deleted = (*transaction)->deleteSeries(series_id);
if(!deleted)
{
return std::unexpected(std::move(deleted.error()));
}
return (*transaction)->commit();
}