Refactor backend to remove service command models
This commit is contained in:
4
FAQ.md
4
FAQ.md
@@ -32,6 +32,6 @@ No. The backend loads state from SQLite once during startup into in-memory state
|
||||
|
||||
Coverage now includes the entire backend project (`RpgRoller`), including API/hosting/bootstrap code and services. It is no longer restricted to `RpgRoller.Services.*`.
|
||||
|
||||
## Why do backend services use `*Command` types instead of API request DTOs?
|
||||
## Why do backend services avoid API request DTO dependencies?
|
||||
|
||||
Service workflows now consume service-layer command models (for example, `CreateCampaignCommand`) so endpoint transport contracts stay isolated in the API layer. This reduces coupling and keeps service code reusable when input shapes evolve at the HTTP boundary.
|
||||
Service workflows accept explicit parameters (for example, `CreateCampaign(sessionToken, name, rulesetId)`) instead of API request DTOs. This keeps the service layer independent from HTTP transport contracts while avoiding extra service-only wrapper command types.
|
||||
|
||||
@@ -19,8 +19,8 @@ Backend:
|
||||
|
||||
- `RpgRoller/Program.cs`: thin app bootstrap only
|
||||
- `RpgRoller/Hosting/`: service registration + startup initialization
|
||||
- `RpgRoller/Api/`: endpoint mapping modules, API-to-service request mapping, auth/session filter helpers
|
||||
- `RpgRoller/Services/`: game workflows and service-layer command models (`*Command` records)
|
||||
- `RpgRoller/Api/`: endpoint mapping modules and auth/session filter helpers
|
||||
- `RpgRoller/Services/`: game workflows with explicit method parameters (no API DTO dependencies)
|
||||
|
||||
Frontend:
|
||||
|
||||
|
||||
@@ -8,11 +8,11 @@ public sealed class ServiceAuthTests
|
||||
using var harness = ServiceTestSupport.CreateHarness();
|
||||
var service = harness.Service;
|
||||
|
||||
var invalidUsername = service.Register(new RegisterCommand("", "Password123", "Display"));
|
||||
var invalidDisplay = service.Register(new RegisterCommand("user", "Password123", ""));
|
||||
var invalidPassword = service.Register(new RegisterCommand("user", "short", "Display"));
|
||||
var valid = service.Register(new RegisterCommand("user", "Password123", "Display"));
|
||||
var duplicate = service.Register(new RegisterCommand("user", "Password123", "Display 2"));
|
||||
var invalidUsername = service.Register("", "Password123", "Display");
|
||||
var invalidDisplay = service.Register("user", "Password123", "");
|
||||
var invalidPassword = service.Register("user", "short", "Display");
|
||||
var valid = service.Register("user", "Password123", "Display");
|
||||
var duplicate = service.Register("user", "Password123", "Display 2");
|
||||
|
||||
Assert.False(invalidUsername.Succeeded);
|
||||
Assert.False(invalidDisplay.Succeeded);
|
||||
@@ -26,11 +26,11 @@ public sealed class ServiceAuthTests
|
||||
{
|
||||
using var harness = ServiceTestSupport.CreateHarness();
|
||||
var service = harness.Service;
|
||||
service.Register(new RegisterCommand("user", "Password123", "Display"));
|
||||
service.Register("user", "Password123", "Display");
|
||||
|
||||
var invalidUser = service.Login(new LoginCommand("missing", "Password123"));
|
||||
var invalidPassword = service.Login(new LoginCommand("user", "bad-password"));
|
||||
var valid = service.Login(new LoginCommand("user", "Password123"));
|
||||
var invalidUser = service.Login("missing", "Password123");
|
||||
var invalidPassword = service.Login("user", "bad-password");
|
||||
var valid = service.Login("user", "Password123");
|
||||
|
||||
Assert.False(invalidUser.Succeeded);
|
||||
Assert.False(invalidPassword.Succeeded);
|
||||
@@ -50,8 +50,8 @@ public sealed class ServiceAuthTests
|
||||
using var harness = ServiceTestSupport.CreateHarness(hasher);
|
||||
var service = harness.Service;
|
||||
|
||||
service.Register(new RegisterCommand("user", "Password123", "Display"));
|
||||
var login = service.Login(new LoginCommand("user", "Password123"));
|
||||
service.Register("user", "Password123", "Display");
|
||||
var login = service.Login("user", "Password123");
|
||||
|
||||
Assert.True(login.Succeeded);
|
||||
Assert.Equal(2, hasher.HashCalls);
|
||||
|
||||
@@ -8,20 +8,20 @@ public sealed class ServiceCampaignTests
|
||||
using var harness = ServiceTestSupport.CreateHarness();
|
||||
var service = harness.Service;
|
||||
|
||||
var unauthorizedCampaign = service.CreateCampaign("missing", new CreateCampaignCommand("Name", "d6"));
|
||||
var unauthorizedCampaign = service.CreateCampaign("missing", "Name", "d6");
|
||||
Assert.False(unauthorizedCampaign.Succeeded);
|
||||
|
||||
service.Register(new RegisterCommand("gm", "Password123", "GM"));
|
||||
var gmSession = ServiceTestSupport.GetValue(service.Login(new LoginCommand("gm", "Password123"))).SessionToken;
|
||||
var campaign = ServiceTestSupport.GetValue(service.CreateCampaign(gmSession, new CreateCampaignCommand("Name", "d6")));
|
||||
service.Register("gm", "Password123", "GM");
|
||||
var gmSession = ServiceTestSupport.GetValue(service.Login("gm", "Password123")).SessionToken;
|
||||
var campaign = ServiceTestSupport.GetValue(service.CreateCampaign(gmSession, "Name", "d6"));
|
||||
|
||||
var invalidRuleset = service.CreateCampaign(gmSession, new CreateCampaignCommand("Name 2", "unknown"));
|
||||
var invalidRuleset = service.CreateCampaign(gmSession, "Name 2", "unknown");
|
||||
Assert.False(invalidRuleset.Succeeded);
|
||||
|
||||
var noCampaignCharacter = service.CreateCharacter(gmSession, new CreateCharacterCommand("Hero", Guid.NewGuid()));
|
||||
var noCampaignCharacter = service.CreateCharacter(gmSession, "Hero", Guid.NewGuid());
|
||||
Assert.False(noCampaignCharacter.Succeeded);
|
||||
|
||||
var character = ServiceTestSupport.GetValue(service.CreateCharacter(gmSession, new CreateCharacterCommand("Hero", campaign.Id)));
|
||||
var character = ServiceTestSupport.GetValue(service.CreateCharacter(gmSession, "Hero", campaign.Id));
|
||||
|
||||
var missingCharacterActivate = service.ActivateCharacter(gmSession, Guid.NewGuid());
|
||||
Assert.False(missingCharacterActivate.Succeeded);
|
||||
@@ -42,8 +42,8 @@ public sealed class ServiceCampaignTests
|
||||
{
|
||||
using var harness = ServiceTestSupport.CreateHarness();
|
||||
var service = harness.Service;
|
||||
service.Register(new RegisterCommand("user", "Password123", "User"));
|
||||
var sessionToken = ServiceTestSupport.GetValue(service.Login(new LoginCommand("user", "Password123"))).SessionToken;
|
||||
service.Register("user", "Password123", "User");
|
||||
var sessionToken = ServiceTestSupport.GetValue(service.Login("user", "Password123")).SessionToken;
|
||||
|
||||
var result = service.GetCurrentCampaignCharacters(sessionToken);
|
||||
Assert.False(result.Succeeded);
|
||||
@@ -54,14 +54,14 @@ public sealed class ServiceCampaignTests
|
||||
{
|
||||
using var harness = ServiceTestSupport.CreateHarness();
|
||||
var service = harness.Service;
|
||||
service.Register(new RegisterCommand("gm", "Password123", "GM"));
|
||||
service.Register(new RegisterCommand("player", "Password123", "Player"));
|
||||
var gmSession = ServiceTestSupport.GetValue(service.Login(new LoginCommand("gm", "Password123"))).SessionToken;
|
||||
var playerSession = ServiceTestSupport.GetValue(service.Login(new LoginCommand("player", "Password123"))).SessionToken;
|
||||
service.Register("gm", "Password123", "GM");
|
||||
service.Register("player", "Password123", "Player");
|
||||
var gmSession = ServiceTestSupport.GetValue(service.Login("gm", "Password123")).SessionToken;
|
||||
var playerSession = ServiceTestSupport.GetValue(service.Login("player", "Password123")).SessionToken;
|
||||
|
||||
var gmCampaign = ServiceTestSupport.GetValue(service.CreateCampaign(gmSession, new CreateCampaignCommand("Owned", "d6")));
|
||||
_ = ServiceTestSupport.GetValue(service.CreateCampaign(gmSession, new CreateCampaignCommand("Owned 2", "d6")));
|
||||
_ = service.CreateCharacter(playerSession, new CreateCharacterCommand("Joiner", gmCampaign.Id));
|
||||
var gmCampaign = ServiceTestSupport.GetValue(service.CreateCampaign(gmSession, "Owned", "d6"));
|
||||
_ = ServiceTestSupport.GetValue(service.CreateCampaign(gmSession, "Owned 2", "d6"));
|
||||
_ = service.CreateCharacter(playerSession, "Joiner", gmCampaign.Id);
|
||||
|
||||
var playerCampaigns = service.GetCampaigns(playerSession);
|
||||
Assert.True(playerCampaigns.Succeeded);
|
||||
@@ -76,20 +76,20 @@ public sealed class ServiceCampaignTests
|
||||
using var harness = ServiceTestSupport.CreateHarness();
|
||||
var service = harness.Service;
|
||||
|
||||
service.Register(new RegisterCommand("gm", "Password123", "GM"));
|
||||
service.Register(new RegisterCommand("owner", "Password123", "Owner"));
|
||||
service.Register(new RegisterCommand("other", "Password123", "Other"));
|
||||
service.Register("gm", "Password123", "GM");
|
||||
service.Register("owner", "Password123", "Owner");
|
||||
service.Register("other", "Password123", "Other");
|
||||
|
||||
var gmSession = ServiceTestSupport.GetValue(service.Login(new LoginCommand("gm", "Password123"))).SessionToken;
|
||||
var ownerSession = ServiceTestSupport.GetValue(service.Login(new LoginCommand("owner", "Password123"))).SessionToken;
|
||||
var otherSession = ServiceTestSupport.GetValue(service.Login(new LoginCommand("other", "Password123"))).SessionToken;
|
||||
var gmSession = ServiceTestSupport.GetValue(service.Login("gm", "Password123")).SessionToken;
|
||||
var ownerSession = ServiceTestSupport.GetValue(service.Login("owner", "Password123")).SessionToken;
|
||||
var otherSession = ServiceTestSupport.GetValue(service.Login("other", "Password123")).SessionToken;
|
||||
|
||||
var campaign = ServiceTestSupport.GetValue(service.CreateCampaign(gmSession, new CreateCampaignCommand("Main", "d6")));
|
||||
var ownerCharacter = ServiceTestSupport.GetValue(service.CreateCharacter(ownerSession, new CreateCharacterCommand("Owner Character", campaign.Id)));
|
||||
var otherCharacter = ServiceTestSupport.GetValue(service.CreateCharacter(otherSession, new CreateCharacterCommand("Other Character", campaign.Id)));
|
||||
var campaign = ServiceTestSupport.GetValue(service.CreateCampaign(gmSession, "Main", "d6"));
|
||||
var ownerCharacter = ServiceTestSupport.GetValue(service.CreateCharacter(ownerSession, "Owner Character", campaign.Id));
|
||||
var otherCharacter = ServiceTestSupport.GetValue(service.CreateCharacter(otherSession, "Other Character", campaign.Id));
|
||||
|
||||
var ownerSkill = ServiceTestSupport.GetValue(service.CreateSkill(ownerSession, ownerCharacter.Id, new CreateSkillCommand("Stealth", "2D+1")));
|
||||
_ = ServiceTestSupport.GetValue(service.CreateSkill(otherSession, otherCharacter.Id, new CreateSkillCommand("Perception", "1D+2")));
|
||||
var ownerSkill = ServiceTestSupport.GetValue(service.CreateSkill(ownerSession, ownerCharacter.Id, "Stealth", "2D+1"));
|
||||
_ = ServiceTestSupport.GetValue(service.CreateSkill(otherSession, otherCharacter.Id, "Perception", "1D+2"));
|
||||
|
||||
var ownerView = ServiceTestSupport.GetValue(service.GetCampaign(ownerSession, campaign.Id));
|
||||
Assert.Single(ownerView.Characters);
|
||||
|
||||
@@ -8,35 +8,35 @@ public sealed class ServicePersistenceTests
|
||||
using var harness = ServiceTestSupport.CreateHarness(2, 3, 4);
|
||||
var service = harness.Service;
|
||||
|
||||
var invalidCredentials = service.Login(new LoginCommand("", ""));
|
||||
var invalidCredentials = service.Login("", "");
|
||||
Assert.False(invalidCredentials.Succeeded);
|
||||
|
||||
service.Logout("missing-session");
|
||||
|
||||
service.Register(new RegisterCommand("gm", "Password123", "GM"));
|
||||
service.Register(new RegisterCommand("owner", "Password123", "Owner"));
|
||||
service.Register(new RegisterCommand("other", "Password123", "Other"));
|
||||
service.Register("gm", "Password123", "GM");
|
||||
service.Register("owner", "Password123", "Owner");
|
||||
service.Register("other", "Password123", "Other");
|
||||
|
||||
var gmSession = ServiceTestSupport.GetValue(service.Login(new LoginCommand("gm", "Password123"))).SessionToken;
|
||||
var ownerSession = ServiceTestSupport.GetValue(service.Login(new LoginCommand("owner", "Password123"))).SessionToken;
|
||||
var otherSession = ServiceTestSupport.GetValue(service.Login(new LoginCommand("other", "Password123"))).SessionToken;
|
||||
var gmSession = ServiceTestSupport.GetValue(service.Login("gm", "Password123")).SessionToken;
|
||||
var ownerSession = ServiceTestSupport.GetValue(service.Login("owner", "Password123")).SessionToken;
|
||||
var otherSession = ServiceTestSupport.GetValue(service.Login("other", "Password123")).SessionToken;
|
||||
|
||||
var campaign = ServiceTestSupport.GetValue(service.CreateCampaign(gmSession, new CreateCampaignCommand("Main", "d6")));
|
||||
var ownerCharacter = ServiceTestSupport.GetValue(service.CreateCharacter(ownerSession, new CreateCharacterCommand("Owner Character", campaign.Id)));
|
||||
var campaign = ServiceTestSupport.GetValue(service.CreateCampaign(gmSession, "Main", "d6"));
|
||||
var ownerCharacter = ServiceTestSupport.GetValue(service.CreateCharacter(ownerSession, "Owner Character", campaign.Id));
|
||||
|
||||
Assert.False(service.GetMe(string.Empty).Succeeded);
|
||||
Assert.False(service.CreateCampaign(gmSession, new CreateCampaignCommand("", "d6")).Succeeded);
|
||||
Assert.False(service.CreateCampaign(gmSession, "", "d6").Succeeded);
|
||||
Assert.False(service.GetCampaigns(string.Empty).Succeeded);
|
||||
Assert.False(service.CreateCharacter(ownerSession, new CreateCharacterCommand("", campaign.Id)).Succeeded);
|
||||
Assert.False(service.CreateCharacter(string.Empty, new CreateCharacterCommand("Name", campaign.Id)).Succeeded);
|
||||
Assert.False(service.UpdateCharacter(string.Empty, ownerCharacter.Id, new UpdateCharacterCommand("Renamed", campaign.Id)).Succeeded);
|
||||
Assert.False(service.UpdateCharacter(ownerSession, Guid.NewGuid(), new UpdateCharacterCommand("Renamed", campaign.Id)).Succeeded);
|
||||
Assert.False(service.CreateCharacter(ownerSession, "", campaign.Id).Succeeded);
|
||||
Assert.False(service.CreateCharacter(string.Empty, "Name", campaign.Id).Succeeded);
|
||||
Assert.False(service.UpdateCharacter(string.Empty, ownerCharacter.Id, "Renamed", campaign.Id).Succeeded);
|
||||
Assert.False(service.UpdateCharacter(ownerSession, Guid.NewGuid(), "Renamed", campaign.Id).Succeeded);
|
||||
Assert.False(service.ActivateCharacter(string.Empty, ownerCharacter.Id).Succeeded);
|
||||
Assert.False(service.ActivateCharacter(gmSession, ownerCharacter.Id).Succeeded);
|
||||
Assert.False(service.GetCurrentCampaignCharacters(string.Empty).Succeeded);
|
||||
Assert.False(service.CreateSkill(string.Empty, ownerCharacter.Id, new CreateSkillCommand("Stealth", "2D+1")).Succeeded);
|
||||
Assert.False(service.CreateSkill(ownerSession, Guid.NewGuid(), new CreateSkillCommand("Stealth", "2D+1")).Succeeded);
|
||||
Assert.False(service.CreateSkill(otherSession, ownerCharacter.Id, new CreateSkillCommand("Stealth", "2D+1")).Succeeded);
|
||||
Assert.False(service.CreateSkill(string.Empty, ownerCharacter.Id, "Stealth", "2D+1").Succeeded);
|
||||
Assert.False(service.CreateSkill(ownerSession, Guid.NewGuid(), "Stealth", "2D+1").Succeeded);
|
||||
Assert.False(service.CreateSkill(otherSession, ownerCharacter.Id, "Stealth", "2D+1").Succeeded);
|
||||
|
||||
using (var db = harness.CreateDbContext())
|
||||
{
|
||||
@@ -74,11 +74,11 @@ public sealed class ServicePersistenceTests
|
||||
Assert.Null(db.Users.Single(u => u.UsernameNormalized == "OWNER").ActiveCharacterId);
|
||||
}
|
||||
|
||||
var skill = ServiceTestSupport.GetValue(service.CreateSkill(ownerSession, ownerCharacter.Id, new CreateSkillCommand("Stealth", "2D+1")));
|
||||
Assert.False(service.UpdateSkill(ownerSession, skill.Id, new UpdateSkillCommand("", "2D+1")).Succeeded);
|
||||
Assert.False(service.UpdateSkill(string.Empty, skill.Id, new UpdateSkillCommand("Stealth", "2D+1")).Succeeded);
|
||||
Assert.False(service.UpdateSkill(ownerSession, skill.Id, new UpdateSkillCommand("Stealth", "bad")).Succeeded);
|
||||
Assert.False(service.RollSkill(string.Empty, skill.Id, new RollSkillCommand("public")).Succeeded);
|
||||
var skill = ServiceTestSupport.GetValue(service.CreateSkill(ownerSession, ownerCharacter.Id, "Stealth", "2D+1"));
|
||||
Assert.False(service.UpdateSkill(ownerSession, skill.Id, "", "2D+1").Succeeded);
|
||||
Assert.False(service.UpdateSkill(string.Empty, skill.Id, "Stealth", "2D+1").Succeeded);
|
||||
Assert.False(service.UpdateSkill(ownerSession, skill.Id, "Stealth", "bad").Succeeded);
|
||||
Assert.False(service.RollSkill(string.Empty, skill.Id, "public").Succeeded);
|
||||
|
||||
using (var db = harness.CreateDbContext())
|
||||
{
|
||||
@@ -88,7 +88,7 @@ public sealed class ServicePersistenceTests
|
||||
}
|
||||
|
||||
using var invalidExpressionHarness = ServiceTestSupport.CreateHarnessFromPath(harness.DbPath, 2, 3, 4);
|
||||
Assert.False(invalidExpressionHarness.Service.RollSkill(ownerSession, skill.Id, new RollSkillCommand("public")).Succeeded);
|
||||
Assert.False(invalidExpressionHarness.Service.RollSkill(ownerSession, skill.Id, "public").Succeeded);
|
||||
Assert.False(service.GetCampaignLog(string.Empty, campaign.Id).Succeeded);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,54 +7,54 @@ public sealed class ServiceSkillRollTests
|
||||
{
|
||||
using var harness = ServiceTestSupport.CreateHarness(3, 4, 5, 6);
|
||||
var service = harness.Service;
|
||||
service.Register(new RegisterCommand("gm", "Password123", "GM"));
|
||||
service.Register(new RegisterCommand("owner", "Password123", "Owner"));
|
||||
service.Register(new RegisterCommand("other", "Password123", "Other"));
|
||||
service.Register("gm", "Password123", "GM");
|
||||
service.Register("owner", "Password123", "Owner");
|
||||
service.Register("other", "Password123", "Other");
|
||||
|
||||
var gmSession = ServiceTestSupport.GetValue(service.Login(new LoginCommand("gm", "Password123"))).SessionToken;
|
||||
var ownerSession = ServiceTestSupport.GetValue(service.Login(new LoginCommand("owner", "Password123"))).SessionToken;
|
||||
var otherSession = ServiceTestSupport.GetValue(service.Login(new LoginCommand("other", "Password123"))).SessionToken;
|
||||
var gmSession = ServiceTestSupport.GetValue(service.Login("gm", "Password123")).SessionToken;
|
||||
var ownerSession = ServiceTestSupport.GetValue(service.Login("owner", "Password123")).SessionToken;
|
||||
var otherSession = ServiceTestSupport.GetValue(service.Login("other", "Password123")).SessionToken;
|
||||
|
||||
var campaign = ServiceTestSupport.GetValue(service.CreateCampaign(gmSession, new CreateCampaignCommand("Main", "dnd5e")));
|
||||
var character = ServiceTestSupport.GetValue(service.CreateCharacter(ownerSession, new CreateCharacterCommand("Owner Char", campaign.Id)));
|
||||
var campaign = ServiceTestSupport.GetValue(service.CreateCampaign(gmSession, "Main", "dnd5e"));
|
||||
var character = ServiceTestSupport.GetValue(service.CreateCharacter(ownerSession, "Owner Char", campaign.Id));
|
||||
|
||||
var noPermissionUpdate = service.UpdateCharacter(otherSession, character.Id, new UpdateCharacterCommand("Renamed", campaign.Id));
|
||||
var noPermissionUpdate = service.UpdateCharacter(otherSession, character.Id, "Renamed", campaign.Id);
|
||||
Assert.False(noPermissionUpdate.Succeeded);
|
||||
|
||||
var invalidCharacterName = service.UpdateCharacter(ownerSession, character.Id, new UpdateCharacterCommand("", campaign.Id));
|
||||
var invalidCharacterName = service.UpdateCharacter(ownerSession, character.Id, "", campaign.Id);
|
||||
Assert.False(invalidCharacterName.Succeeded);
|
||||
|
||||
var missingTargetCampaign = service.UpdateCharacter(ownerSession, character.Id, new UpdateCharacterCommand("Renamed", Guid.NewGuid()));
|
||||
var missingTargetCampaign = service.UpdateCharacter(ownerSession, character.Id, "Renamed", Guid.NewGuid());
|
||||
Assert.False(missingTargetCampaign.Succeeded);
|
||||
|
||||
var noSkillName = service.CreateSkill(ownerSession, character.Id, new CreateSkillCommand("", "1d20"));
|
||||
var noSkillName = service.CreateSkill(ownerSession, character.Id, "", "1d20");
|
||||
Assert.False(noSkillName.Succeeded);
|
||||
|
||||
var invalidExpression = service.CreateSkill(ownerSession, character.Id, new CreateSkillCommand("Skill", "5D+4"));
|
||||
var invalidExpression = service.CreateSkill(ownerSession, character.Id, "Skill", "5D+4");
|
||||
Assert.False(invalidExpression.Succeeded);
|
||||
|
||||
var skill = ServiceTestSupport.GetValue(service.CreateSkill(ownerSession, character.Id, new CreateSkillCommand("Skill", "1d20+2")));
|
||||
var skill = ServiceTestSupport.GetValue(service.CreateSkill(ownerSession, character.Id, "Skill", "1d20+2"));
|
||||
|
||||
var missingSkillUpdate = service.UpdateSkill(ownerSession, Guid.NewGuid(), new UpdateSkillCommand("X", "1d20"));
|
||||
var missingSkillUpdate = service.UpdateSkill(ownerSession, Guid.NewGuid(), "X", "1d20");
|
||||
Assert.False(missingSkillUpdate.Succeeded);
|
||||
|
||||
var forbiddenSkillUpdate = service.UpdateSkill(otherSession, skill.Id, new UpdateSkillCommand("X", "1d20"));
|
||||
var forbiddenSkillUpdate = service.UpdateSkill(otherSession, skill.Id, "X", "1d20");
|
||||
Assert.False(forbiddenSkillUpdate.Succeeded);
|
||||
|
||||
var gmSkillUpdate = service.UpdateSkill(gmSession, skill.Id, new UpdateSkillCommand("GM Edit", "2d6+1"));
|
||||
var gmSkillUpdate = service.UpdateSkill(gmSession, skill.Id, "GM Edit", "2d6+1");
|
||||
Assert.True(gmSkillUpdate.Succeeded);
|
||||
|
||||
var missingRoll = service.RollSkill(ownerSession, Guid.NewGuid(), new RollSkillCommand("public"));
|
||||
var missingRoll = service.RollSkill(ownerSession, Guid.NewGuid(), "public");
|
||||
Assert.False(missingRoll.Succeeded);
|
||||
|
||||
var invalidVisibility = service.RollSkill(ownerSession, skill.Id, new RollSkillCommand("hidden"));
|
||||
var invalidVisibility = service.RollSkill(ownerSession, skill.Id, "hidden");
|
||||
Assert.False(invalidVisibility.Succeeded);
|
||||
|
||||
var forbiddenRoll = service.RollSkill(otherSession, skill.Id, new RollSkillCommand("public"));
|
||||
var forbiddenRoll = service.RollSkill(otherSession, skill.Id, "public");
|
||||
Assert.False(forbiddenRoll.Succeeded);
|
||||
|
||||
var privateRoll = service.RollSkill(ownerSession, skill.Id, new RollSkillCommand("private"));
|
||||
var publicRoll = service.RollSkill(ownerSession, skill.Id, new RollSkillCommand("public"));
|
||||
var privateRoll = service.RollSkill(ownerSession, skill.Id, "private");
|
||||
var publicRoll = service.RollSkill(ownerSession, skill.Id, "public");
|
||||
Assert.True(privateRoll.Succeeded);
|
||||
Assert.True(publicRoll.Succeeded);
|
||||
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
namespace RpgRoller.Tests;
|
||||
|
||||
// Integration API tests were split by concern under RpgRoller.Tests/Api.
|
||||
@@ -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.
|
||||
|
||||
2
TECH.md
2
TECH.md
@@ -14,7 +14,7 @@
|
||||
- Generated client output: `RpgRoller/wwwroot/generated/api-client.js`
|
||||
- Local CI parity entrypoint: `scripts/ci-local.ps1`
|
||||
- API endpoint modules: `RpgRoller/Api/*Endpoints.cs` + shared session/auth helpers
|
||||
- Service boundary model: API request DTOs are mapped to `RpgRoller.Services/*Command` records before workflow execution
|
||||
- Service boundary model: API request DTOs are mapped to explicit service method parameters before workflow execution
|
||||
- Current backend features: auth/session, campaign/character/skill management, ruleset-aware rolls, filtered campaign logs, and SSE state updates.
|
||||
- Current frontend features: authenticated campaign workspace with live log updates and full roll workflow controls.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user