Refactor backend to remove service command models
This commit is contained in:
@@ -10,7 +10,7 @@ internal static class AuthEndpoints
|
||||
{
|
||||
group.MapPost("/auth/register", Results<Ok<UserSummary>, BadRequest<ApiError>> (RegisterRequest request, IGameService game) =>
|
||||
{
|
||||
var result = game.Register(request.ToCommand());
|
||||
var result = game.Register(request.Username, request.Password, request.DisplayName);
|
||||
if (!result.Succeeded)
|
||||
{
|
||||
return ApiResultMapper.ToBadRequest(result.Error!);
|
||||
@@ -21,7 +21,7 @@ internal static class AuthEndpoints
|
||||
|
||||
group.MapPost("/auth/login", Results<Ok<UserSummary>, BadRequest<ApiError>> (LoginRequest request, HttpContext context, IGameService game) =>
|
||||
{
|
||||
var result = game.Login(request.ToCommand());
|
||||
var result = game.Login(request.Username, request.Password);
|
||||
if (!result.Succeeded)
|
||||
{
|
||||
return ApiResultMapper.ToBadRequest(result.Error!);
|
||||
|
||||
@@ -9,7 +9,7 @@ internal static class CampaignEndpoints
|
||||
{
|
||||
group.MapPost("/campaigns", (CreateCampaignRequest request, HttpContext context, IGameService game) =>
|
||||
{
|
||||
var result = game.CreateCampaign(context.GetRequiredSessionToken(), request.ToCommand());
|
||||
var result = game.CreateCampaign(context.GetRequiredSessionToken(), request.Name, request.RulesetId);
|
||||
return ApiResultMapper.ToApiResult(result);
|
||||
});
|
||||
|
||||
|
||||
@@ -9,13 +9,13 @@ internal static class CharacterEndpoints
|
||||
{
|
||||
group.MapPost("/characters", (CreateCharacterRequest request, HttpContext context, IGameService game) =>
|
||||
{
|
||||
var result = game.CreateCharacter(context.GetRequiredSessionToken(), request.ToCommand());
|
||||
var result = game.CreateCharacter(context.GetRequiredSessionToken(), request.Name, request.CampaignId);
|
||||
return ApiResultMapper.ToApiResult(result);
|
||||
});
|
||||
|
||||
group.MapPut("/characters/{characterId:guid}", (Guid characterId, UpdateCharacterRequest request, HttpContext context, IGameService game) =>
|
||||
{
|
||||
var result = game.UpdateCharacter(context.GetRequiredSessionToken(), characterId, request.ToCommand());
|
||||
var result = game.UpdateCharacter(context.GetRequiredSessionToken(), characterId, request.Name, request.CampaignId);
|
||||
return ApiResultMapper.ToApiResult(result);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,47 +1,5 @@
|
||||
using RpgRoller.Contracts;
|
||||
using RpgRoller.Services;
|
||||
|
||||
namespace RpgRoller.Api;
|
||||
|
||||
internal static class RequestMappings
|
||||
{
|
||||
public static RegisterCommand ToCommand(this RegisterRequest request)
|
||||
{
|
||||
return new RegisterCommand(request.Username, request.Password, request.DisplayName);
|
||||
}
|
||||
|
||||
public static LoginCommand ToCommand(this LoginRequest request)
|
||||
{
|
||||
return new LoginCommand(request.Username, request.Password);
|
||||
}
|
||||
|
||||
public static CreateCampaignCommand ToCommand(this CreateCampaignRequest request)
|
||||
{
|
||||
return new CreateCampaignCommand(request.Name, request.RulesetId);
|
||||
}
|
||||
|
||||
public static CreateCharacterCommand ToCommand(this CreateCharacterRequest request)
|
||||
{
|
||||
return new CreateCharacterCommand(request.Name, request.CampaignId);
|
||||
}
|
||||
|
||||
public static UpdateCharacterCommand ToCommand(this UpdateCharacterRequest request)
|
||||
{
|
||||
return new UpdateCharacterCommand(request.Name, request.CampaignId);
|
||||
}
|
||||
|
||||
public static CreateSkillCommand ToCommand(this CreateSkillRequest request)
|
||||
{
|
||||
return new CreateSkillCommand(request.Name, request.DiceRollDefinition);
|
||||
}
|
||||
|
||||
public static UpdateSkillCommand ToCommand(this UpdateSkillRequest request)
|
||||
{
|
||||
return new UpdateSkillCommand(request.Name, request.DiceRollDefinition);
|
||||
}
|
||||
|
||||
public static RollSkillCommand ToCommand(this RollSkillRequest request)
|
||||
{
|
||||
return new RollSkillCommand(request.Visibility);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,19 +9,19 @@ internal static class SkillEndpoints
|
||||
{
|
||||
group.MapPost("/characters/{characterId:guid}/skills", (Guid characterId, CreateSkillRequest request, HttpContext context, IGameService game) =>
|
||||
{
|
||||
var result = game.CreateSkill(context.GetRequiredSessionToken(), characterId, request.ToCommand());
|
||||
var result = game.CreateSkill(context.GetRequiredSessionToken(), characterId, request.Name, request.DiceRollDefinition);
|
||||
return ApiResultMapper.ToApiResult(result);
|
||||
});
|
||||
|
||||
group.MapPut("/skills/{skillId:guid}", (Guid skillId, UpdateSkillRequest request, HttpContext context, IGameService game) =>
|
||||
{
|
||||
var result = game.UpdateSkill(context.GetRequiredSessionToken(), skillId, request.ToCommand());
|
||||
var result = game.UpdateSkill(context.GetRequiredSessionToken(), skillId, request.Name, request.DiceRollDefinition);
|
||||
return ApiResultMapper.ToApiResult(result);
|
||||
});
|
||||
|
||||
group.MapPost("/skills/{skillId:guid}/roll", (Guid skillId, RollSkillRequest request, HttpContext context, IGameService game) =>
|
||||
{
|
||||
var result = game.RollSkill(context.GetRequiredSessionToken(), skillId, request.ToCommand());
|
||||
var result = game.RollSkill(context.GetRequiredSessionToken(), skillId, request.Visibility);
|
||||
return ApiResultMapper.ToApiResult(result);
|
||||
});
|
||||
|
||||
|
||||
@@ -35,27 +35,27 @@ public sealed class GameService : IGameService
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
public ServiceResult<UserSummary> Register(RegisterCommand request)
|
||||
public ServiceResult<UserSummary> Register(string username, string password, string displayName)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(request.Username))
|
||||
if (string.IsNullOrWhiteSpace(username))
|
||||
{
|
||||
return ServiceResult<UserSummary>.Failure("invalid_username", "Username is required.");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(request.DisplayName))
|
||||
if (string.IsNullOrWhiteSpace(displayName))
|
||||
{
|
||||
return ServiceResult<UserSummary>.Failure("invalid_display_name", "Display name is required.");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(request.Password) || request.Password.Length < 8)
|
||||
if (string.IsNullOrWhiteSpace(password) || password.Length < 8)
|
||||
{
|
||||
return ServiceResult<UserSummary>.Failure("invalid_password", "Password must be at least 8 characters.");
|
||||
}
|
||||
|
||||
lock (m_Gate)
|
||||
{
|
||||
var username = request.Username.Trim();
|
||||
var normalizedUsername = NormalizeUsername(username);
|
||||
var trimmedUsername = username.Trim();
|
||||
var normalizedUsername = NormalizeUsername(trimmedUsername);
|
||||
if (m_UserIdsByUsername.ContainsKey(normalizedUsername))
|
||||
{
|
||||
return ServiceResult<UserSummary>.Failure("duplicate_username", "Username is already taken.");
|
||||
@@ -64,14 +64,14 @@ public sealed class GameService : IGameService
|
||||
var user = new UserAccount
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Username = username,
|
||||
Username = trimmedUsername,
|
||||
UsernameNormalized = normalizedUsername,
|
||||
DisplayName = request.DisplayName.Trim(),
|
||||
DisplayName = displayName.Trim(),
|
||||
PasswordHash = string.Empty,
|
||||
ActiveCharacterId = null
|
||||
};
|
||||
|
||||
user.PasswordHash = m_PasswordHasher.HashPassword(user, request.Password);
|
||||
user.PasswordHash = m_PasswordHasher.HashPassword(user, password);
|
||||
|
||||
m_UsersById[user.Id] = user;
|
||||
m_UserIdsByUsername[user.UsernameNormalized] = user.Id;
|
||||
@@ -81,23 +81,23 @@ public sealed class GameService : IGameService
|
||||
}
|
||||
}
|
||||
|
||||
public ServiceResult<(UserSummary User, string SessionToken)> Login(LoginCommand request)
|
||||
public ServiceResult<(UserSummary User, string SessionToken)> Login(string username, string password)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(request.Username) || string.IsNullOrWhiteSpace(request.Password))
|
||||
if (string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(password))
|
||||
{
|
||||
return ServiceResult<(UserSummary User, string SessionToken)>.Failure("invalid_credentials", "Invalid username or password.");
|
||||
}
|
||||
|
||||
lock (m_Gate)
|
||||
{
|
||||
var normalizedUsername = NormalizeUsername(request.Username.Trim());
|
||||
var normalizedUsername = NormalizeUsername(username.Trim());
|
||||
if (!m_UserIdsByUsername.TryGetValue(normalizedUsername, out var userId))
|
||||
{
|
||||
return ServiceResult<(UserSummary User, string SessionToken)>.Failure("invalid_credentials", "Invalid username or password.");
|
||||
}
|
||||
|
||||
var user = m_UsersById[userId];
|
||||
var verification = m_PasswordHasher.VerifyHashedPassword(user, user.PasswordHash, request.Password);
|
||||
var verification = m_PasswordHasher.VerifyHashedPassword(user, user.PasswordHash, password);
|
||||
if (verification == PasswordVerificationResult.Failed)
|
||||
{
|
||||
return ServiceResult<(UserSummary User, string SessionToken)>.Failure("invalid_credentials", "Invalid username or password.");
|
||||
@@ -105,7 +105,7 @@ public sealed class GameService : IGameService
|
||||
|
||||
if (verification == PasswordVerificationResult.SuccessRehashNeeded)
|
||||
{
|
||||
user.PasswordHash = m_PasswordHasher.HashPassword(user, request.Password);
|
||||
user.PasswordHash = m_PasswordHasher.HashPassword(user, password);
|
||||
}
|
||||
|
||||
var session = CreateSession(userId);
|
||||
@@ -162,14 +162,14 @@ public sealed class GameService : IGameService
|
||||
}
|
||||
}
|
||||
|
||||
public ServiceResult<CampaignSummary> CreateCampaign(string sessionToken, CreateCampaignCommand request)
|
||||
public ServiceResult<CampaignSummary> CreateCampaign(string sessionToken, string name, string rulesetId)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(request.Name))
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
return ServiceResult<CampaignSummary>.Failure("invalid_campaign_name", "Campaign name is required.");
|
||||
}
|
||||
|
||||
var ruleset = DiceRules.TryParseRulesetId(request.RulesetId);
|
||||
var ruleset = DiceRules.TryParseRulesetId(rulesetId);
|
||||
if (ruleset is null)
|
||||
{
|
||||
return ServiceResult<CampaignSummary>.Failure("invalid_ruleset", "Unknown ruleset.");
|
||||
@@ -187,7 +187,7 @@ public sealed class GameService : IGameService
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
GmUserId = user.Id,
|
||||
Name = request.Name.Trim(),
|
||||
Name = name.Trim(),
|
||||
Ruleset = ruleset.Value,
|
||||
Version = 1
|
||||
};
|
||||
@@ -263,9 +263,9 @@ public sealed class GameService : IGameService
|
||||
}
|
||||
}
|
||||
|
||||
public ServiceResult<CharacterSummary> CreateCharacter(string sessionToken, CreateCharacterCommand request)
|
||||
public ServiceResult<CharacterSummary> CreateCharacter(string sessionToken, string name, Guid campaignId)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(request.Name))
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
return ServiceResult<CharacterSummary>.Failure("invalid_character_name", "Character name is required.");
|
||||
}
|
||||
@@ -278,7 +278,7 @@ public sealed class GameService : IGameService
|
||||
return ServiceResult<CharacterSummary>.Failure("unauthorized", "You must be logged in.");
|
||||
}
|
||||
|
||||
if (!m_CampaignsById.ContainsKey(request.CampaignId))
|
||||
if (!m_CampaignsById.ContainsKey(campaignId))
|
||||
{
|
||||
return ServiceResult<CharacterSummary>.Failure("campaign_not_found", "Campaign was not found.");
|
||||
}
|
||||
@@ -287,8 +287,8 @@ public sealed class GameService : IGameService
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
OwnerUserId = user.Id,
|
||||
CampaignId = request.CampaignId,
|
||||
Name = request.Name.Trim()
|
||||
CampaignId = campaignId,
|
||||
Name = name.Trim()
|
||||
};
|
||||
|
||||
m_CharactersById[character.Id] = character;
|
||||
@@ -299,9 +299,9 @@ public sealed class GameService : IGameService
|
||||
}
|
||||
}
|
||||
|
||||
public ServiceResult<CharacterSummary> UpdateCharacter(string sessionToken, Guid characterId, UpdateCharacterCommand request)
|
||||
public ServiceResult<CharacterSummary> UpdateCharacter(string sessionToken, Guid characterId, string name, Guid campaignId)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(request.Name))
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
return ServiceResult<CharacterSummary>.Failure("invalid_character_name", "Character name is required.");
|
||||
}
|
||||
@@ -319,7 +319,7 @@ public sealed class GameService : IGameService
|
||||
return ServiceResult<CharacterSummary>.Failure("character_not_found", "Character was not found.");
|
||||
}
|
||||
|
||||
if (!m_CampaignsById.TryGetValue(request.CampaignId, out var targetCampaign))
|
||||
if (!m_CampaignsById.TryGetValue(campaignId, out var targetCampaign))
|
||||
{
|
||||
return ServiceResult<CharacterSummary>.Failure("campaign_not_found", "Campaign was not found.");
|
||||
}
|
||||
@@ -334,8 +334,8 @@ public sealed class GameService : IGameService
|
||||
}
|
||||
|
||||
var sourceCampaignId = character.CampaignId;
|
||||
character.Name = request.Name.Trim();
|
||||
character.CampaignId = request.CampaignId;
|
||||
character.Name = name.Trim();
|
||||
character.CampaignId = campaignId;
|
||||
|
||||
TouchCampaignLocked(sourceCampaignId);
|
||||
if (sourceCampaignId != character.CampaignId)
|
||||
@@ -399,9 +399,9 @@ public sealed class GameService : IGameService
|
||||
}
|
||||
}
|
||||
|
||||
public ServiceResult<SkillSummary> CreateSkill(string sessionToken, Guid characterId, CreateSkillCommand request)
|
||||
public ServiceResult<SkillSummary> CreateSkill(string sessionToken, Guid characterId, string name, string diceRollDefinition)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(request.Name))
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
return ServiceResult<SkillSummary>.Failure("invalid_skill_name", "Skill name is required.");
|
||||
}
|
||||
@@ -425,7 +425,7 @@ public sealed class GameService : IGameService
|
||||
return ServiceResult<SkillSummary>.Failure("forbidden", "Only the owner or GM can edit skills.");
|
||||
}
|
||||
|
||||
var expressionValidation = DiceRules.ParseExpression(campaign.Ruleset, request.DiceRollDefinition);
|
||||
var expressionValidation = DiceRules.ParseExpression(campaign.Ruleset, diceRollDefinition);
|
||||
if (!expressionValidation.Succeeded)
|
||||
{
|
||||
return ServiceResult<SkillSummary>.Failure(expressionValidation.Error!.Code, expressionValidation.Error.Message);
|
||||
@@ -435,7 +435,7 @@ public sealed class GameService : IGameService
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
CharacterId = character.Id,
|
||||
Name = request.Name.Trim(),
|
||||
Name = name.Trim(),
|
||||
DiceRollDefinition = expressionValidation.Value!.Canonical
|
||||
};
|
||||
|
||||
@@ -447,9 +447,9 @@ public sealed class GameService : IGameService
|
||||
}
|
||||
}
|
||||
|
||||
public ServiceResult<SkillSummary> UpdateSkill(string sessionToken, Guid skillId, UpdateSkillCommand request)
|
||||
public ServiceResult<SkillSummary> UpdateSkill(string sessionToken, Guid skillId, string name, string diceRollDefinition)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(request.Name))
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
return ServiceResult<SkillSummary>.Failure("invalid_skill_name", "Skill name is required.");
|
||||
}
|
||||
@@ -474,13 +474,13 @@ public sealed class GameService : IGameService
|
||||
return ServiceResult<SkillSummary>.Failure("forbidden", "Only the owner or GM can edit skills.");
|
||||
}
|
||||
|
||||
var expressionValidation = DiceRules.ParseExpression(campaign.Ruleset, request.DiceRollDefinition);
|
||||
var expressionValidation = DiceRules.ParseExpression(campaign.Ruleset, diceRollDefinition);
|
||||
if (!expressionValidation.Succeeded)
|
||||
{
|
||||
return ServiceResult<SkillSummary>.Failure(expressionValidation.Error!.Code, expressionValidation.Error.Message);
|
||||
}
|
||||
|
||||
skill.Name = request.Name.Trim();
|
||||
skill.Name = name.Trim();
|
||||
skill.DiceRollDefinition = expressionValidation.Value!.Canonical;
|
||||
TouchCampaignLocked(campaign.Id);
|
||||
|
||||
@@ -489,7 +489,7 @@ public sealed class GameService : IGameService
|
||||
}
|
||||
}
|
||||
|
||||
public ServiceResult<RollResult> RollSkill(string sessionToken, Guid skillId, RollSkillCommand request)
|
||||
public ServiceResult<RollResult> RollSkill(string sessionToken, Guid skillId, string visibility)
|
||||
{
|
||||
lock (m_Gate)
|
||||
{
|
||||
@@ -517,10 +517,10 @@ public sealed class GameService : IGameService
|
||||
return ServiceResult<RollResult>.Failure(parsedExpression.Error!.Code, parsedExpression.Error.Message);
|
||||
}
|
||||
|
||||
var visibility = ParseVisibility(request.Visibility);
|
||||
if (!visibility.Succeeded)
|
||||
var parsedVisibility = ParseVisibility(visibility);
|
||||
if (!parsedVisibility.Succeeded)
|
||||
{
|
||||
return ServiceResult<RollResult>.Failure(visibility.Error!.Code, visibility.Error.Message);
|
||||
return ServiceResult<RollResult>.Failure(parsedVisibility.Error!.Code, parsedVisibility.Error.Message);
|
||||
}
|
||||
|
||||
var roll = ComputeRoll(parsedExpression.Value!);
|
||||
@@ -531,7 +531,7 @@ public sealed class GameService : IGameService
|
||||
CharacterId = character.Id,
|
||||
SkillId = skill.Id,
|
||||
RollerUserId = user.Id,
|
||||
Visibility = visibility.Value,
|
||||
Visibility = parsedVisibility.Value,
|
||||
Result = roll.Total,
|
||||
Breakdown = roll.Breakdown,
|
||||
TimestampUtc = DateTimeOffset.UtcNow
|
||||
|
||||
@@ -7,25 +7,25 @@ public interface IGameService
|
||||
{
|
||||
IReadOnlyList<RulesetDefinition> GetRulesets();
|
||||
|
||||
ServiceResult<UserSummary> Register(RegisterCommand request);
|
||||
ServiceResult<(UserSummary User, string SessionToken)> Login(LoginCommand request);
|
||||
ServiceResult<UserSummary> Register(string username, string password, string displayName);
|
||||
ServiceResult<(UserSummary User, string SessionToken)> Login(string username, string password);
|
||||
void Logout(string sessionToken);
|
||||
UserSummary? GetUserBySession(string sessionToken);
|
||||
ServiceResult<MeResponse> GetMe(string sessionToken);
|
||||
|
||||
ServiceResult<CampaignSummary> CreateCampaign(string sessionToken, CreateCampaignCommand request);
|
||||
ServiceResult<CampaignSummary> CreateCampaign(string sessionToken, string name, string rulesetId);
|
||||
ServiceResult<IReadOnlyList<CampaignSummary>> GetCampaigns(string sessionToken);
|
||||
ServiceResult<CampaignDetails> GetCampaign(string sessionToken, Guid campaignId);
|
||||
|
||||
ServiceResult<CharacterSummary> CreateCharacter(string sessionToken, CreateCharacterCommand request);
|
||||
ServiceResult<CharacterSummary> UpdateCharacter(string sessionToken, Guid characterId, UpdateCharacterCommand request);
|
||||
ServiceResult<CharacterSummary> CreateCharacter(string sessionToken, string name, Guid campaignId);
|
||||
ServiceResult<CharacterSummary> UpdateCharacter(string sessionToken, Guid characterId, string name, Guid campaignId);
|
||||
ServiceResult<bool> ActivateCharacter(string sessionToken, Guid characterId);
|
||||
ServiceResult<IReadOnlyList<CharacterSummary>> GetCurrentCampaignCharacters(string sessionToken);
|
||||
|
||||
ServiceResult<SkillSummary> CreateSkill(string sessionToken, Guid characterId, CreateSkillCommand request);
|
||||
ServiceResult<SkillSummary> UpdateSkill(string sessionToken, Guid skillId, UpdateSkillCommand request);
|
||||
ServiceResult<SkillSummary> CreateSkill(string sessionToken, Guid characterId, string name, string diceRollDefinition);
|
||||
ServiceResult<SkillSummary> UpdateSkill(string sessionToken, Guid skillId, string name, string diceRollDefinition);
|
||||
|
||||
ServiceResult<RollResult> RollSkill(string sessionToken, Guid skillId, RollSkillCommand request);
|
||||
ServiceResult<RollResult> RollSkill(string sessionToken, Guid skillId, string visibility);
|
||||
ServiceResult<IReadOnlyList<CampaignLogEntry>> GetCampaignLog(string sessionToken, Guid campaignId);
|
||||
|
||||
ServiceResult<long> GetCampaignVersion(string sessionToken, Guid campaignId);
|
||||
|
||||
@@ -1,13 +1 @@
|
||||
namespace RpgRoller.Services;
|
||||
|
||||
public sealed record RegisterCommand(string Username, string Password, string DisplayName);
|
||||
public sealed record LoginCommand(string Username, string Password);
|
||||
|
||||
public sealed record CreateCampaignCommand(string Name, string RulesetId);
|
||||
public sealed record CreateCharacterCommand(string Name, Guid CampaignId);
|
||||
public sealed record UpdateCharacterCommand(string Name, Guid CampaignId);
|
||||
|
||||
public sealed record CreateSkillCommand(string Name, string DiceRollDefinition);
|
||||
public sealed record UpdateSkillCommand(string Name, string DiceRollDefinition);
|
||||
|
||||
public sealed record RollSkillCommand(string Visibility);
|
||||
// Intentionally left blank after removing service command records.
|
||||
|
||||
Reference in New Issue
Block a user