Refactor campaign payload loading
This commit is contained in:
@@ -99,6 +99,7 @@ dotnet dotnet-ef migrations add <MigrationName> --project RpgRoller/RpgRoller.cs
|
|||||||
|
|
||||||
- Runtime frontend is Blazor Server with interactive components.
|
- Runtime frontend is Blazor Server with interactive components.
|
||||||
- Browser interop is in `RpgRoller/wwwroot/js/rpgroller-api.js`.
|
- Browser interop is in `RpgRoller/wwwroot/js/rpgroller-api.js`.
|
||||||
|
- Workspace campaign data is loaded in bounded slices: visible campaign summaries, a selected campaign roster, a selected character sheet, and the 100 most recent visible log entries.
|
||||||
- OpenAPI contract source remains at `openapi/RpgRoller.json`.
|
- OpenAPI contract source remains at `openapi/RpgRoller.json`.
|
||||||
|
|
||||||
## Test and Coverage
|
## Test and Coverage
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ public sealed class CampaignApiTests : ApiTestBase
|
|||||||
await RegisterAsync(gmClient, "gm", "Password123", "Game Master");
|
await RegisterAsync(gmClient, "gm", "Password123", "Game Master");
|
||||||
await LoginAsync(gmClient, "gm", "Password123");
|
await LoginAsync(gmClient, "gm", "Password123");
|
||||||
|
|
||||||
var campaign = await PostAsync<CreateCampaignRequest, CampaignDetails>(gmClient, "/api/campaigns", new("Alpha Campaign", "dnd5e"));
|
var campaign = await PostAsync<CreateCampaignRequest, CampaignSummary>(gmClient, "/api/campaigns", new("Alpha Campaign", "dnd5e"));
|
||||||
|
|
||||||
var gmCharacter = await PostAsync<CreateCharacterRequest, CharacterSummary>(gmClient, "/api/characters", new("Arin", campaign.Id));
|
var gmCharacter = await PostAsync<CreateCharacterRequest, CharacterSummary>(gmClient, "/api/characters", new("Arin", campaign.Id));
|
||||||
Assert.Equal("Game Master", gmCharacter.OwnerDisplayName);
|
Assert.Equal("Game Master", gmCharacter.OwnerDisplayName);
|
||||||
@@ -39,17 +39,25 @@ public sealed class CampaignApiTests : ApiTestBase
|
|||||||
var invalidSkill = await gmClient.PostAsJsonAsync($"/api/characters/{gmCharacter.Id}/skills", new CreateSkillRequest("Broken", "5D+4", 0, false));
|
var invalidSkill = await gmClient.PostAsJsonAsync($"/api/characters/{gmCharacter.Id}/skills", new CreateSkillRequest("Broken", "5D+4", 0, false));
|
||||||
Assert.Equal(HttpStatusCode.BadRequest, invalidSkill.StatusCode);
|
Assert.Equal(HttpStatusCode.BadRequest, invalidSkill.StatusCode);
|
||||||
|
|
||||||
var details = await GetAsync<CampaignDetails>(gmClient, $"/api/campaigns/{campaign.Id}");
|
var details = await GetAsync<CampaignRoster>(gmClient, $"/api/campaigns/{campaign.Id}");
|
||||||
Assert.Equal(campaign.Id, details.Id);
|
Assert.Equal(campaign.Id, details.Id);
|
||||||
Assert.Single(details.Characters);
|
Assert.Single(details.Characters);
|
||||||
Assert.Equal("Game Master", details.Characters[0].OwnerDisplayName);
|
Assert.Equal("Game Master", details.Characters[0].OwnerDisplayName);
|
||||||
|
|
||||||
|
var summaries = await GetAsync<IReadOnlyList<CampaignSummary>>(gmClient, "/api/campaigns");
|
||||||
|
Assert.Single(summaries);
|
||||||
|
Assert.Equal(1, summaries[0].CharacterCount);
|
||||||
|
|
||||||
|
var sheet = await GetAsync<CharacterSheet>(gmClient, $"/api/characters/{gmCharacter.Id}/sheet");
|
||||||
|
Assert.Single(sheet.Skills);
|
||||||
|
Assert.Equal(updatedSkill.Id, sheet.Skills[0].Id);
|
||||||
|
|
||||||
var currentCampaignCharacters = await GetAsync<IReadOnlyList<CharacterSummary>>(gmClient, "/api/characters");
|
var currentCampaignCharacters = await GetAsync<IReadOnlyList<CharacterSummary>>(gmClient, "/api/characters");
|
||||||
Assert.Single(currentCampaignCharacters);
|
Assert.Single(currentCampaignCharacters);
|
||||||
Assert.Equal(gmCharacter.Id, currentCampaignCharacters[0].Id);
|
Assert.Equal(gmCharacter.Id, currentCampaignCharacters[0].Id);
|
||||||
Assert.Equal("Game Master", currentCampaignCharacters[0].OwnerDisplayName);
|
Assert.Equal("Game Master", currentCampaignCharacters[0].OwnerDisplayName);
|
||||||
|
|
||||||
var otherCampaign = await PostAsync<CreateCampaignRequest, CampaignDetails>(gmClient, "/api/campaigns", new("Beta Campaign", "d6"));
|
var otherCampaign = await PostAsync<CreateCampaignRequest, CampaignSummary>(gmClient, "/api/campaigns", new("Beta Campaign", "d6"));
|
||||||
|
|
||||||
var updatedCharacter = await PutAsync<UpdateCharacterRequest, CharacterSummary>(gmClient, $"/api/characters/{gmCharacter.Id}", new("Arin Updated", otherCampaign.Id));
|
var updatedCharacter = await PutAsync<UpdateCharacterRequest, CharacterSummary>(gmClient, $"/api/characters/{gmCharacter.Id}", new("Arin Updated", otherCampaign.Id));
|
||||||
|
|
||||||
@@ -74,7 +82,7 @@ public sealed class CampaignApiTests : ApiTestBase
|
|||||||
await RegisterAsync(receiverClient, "receiver2", "Password123", "Receiver");
|
await RegisterAsync(receiverClient, "receiver2", "Password123", "Receiver");
|
||||||
await LoginAsync(receiverClient, "receiver2", "Password123");
|
await LoginAsync(receiverClient, "receiver2", "Password123");
|
||||||
|
|
||||||
var campaign = await PostAsync<CreateCampaignRequest, CampaignDetails>(gmClient, "/api/campaigns", new("Grouped Campaign", "d6"));
|
var campaign = await PostAsync<CreateCampaignRequest, CampaignSummary>(gmClient, "/api/campaigns", new("Grouped Campaign", "d6"));
|
||||||
var character = await PostAsync<CreateCharacterRequest, CharacterSummary>(ownerClient, "/api/characters", new("Grouped Hero", campaign.Id));
|
var character = await PostAsync<CreateCharacterRequest, CharacterSummary>(ownerClient, "/api/characters", new("Grouped Hero", campaign.Id));
|
||||||
|
|
||||||
var createdGroup = await PostAsync<CreateSkillGroupRequest, SkillGroupSummary>(ownerClient, $"/api/characters/{character.Id}/skill-groups", new("Combat", "2D+1", 1, true));
|
var createdGroup = await PostAsync<CreateSkillGroupRequest, SkillGroupSummary>(ownerClient, $"/api/characters/{character.Id}/skill-groups", new("Combat", "2D+1", 1, true));
|
||||||
@@ -103,7 +111,7 @@ public sealed class CampaignApiTests : ApiTestBase
|
|||||||
Assert.Equal("Grouped Hero", transferResult.Name);
|
Assert.Equal("Grouped Hero", transferResult.Name);
|
||||||
Assert.Equal("Receiver", transferResult.OwnerDisplayName);
|
Assert.Equal("Receiver", transferResult.OwnerDisplayName);
|
||||||
|
|
||||||
var gmCampaignView = await GetAsync<CampaignDetails>(gmClient, $"/api/campaigns/{campaign.Id}");
|
var gmCampaignView = await GetAsync<CampaignRoster>(gmClient, $"/api/campaigns/{campaign.Id}");
|
||||||
var gmViewedCharacter = Assert.Single(gmCampaignView.Characters, c => c.Id == character.Id);
|
var gmViewedCharacter = Assert.Single(gmCampaignView.Characters, c => c.Id == character.Id);
|
||||||
Assert.Equal("Receiver", gmViewedCharacter.OwnerDisplayName);
|
Assert.Equal("Receiver", gmViewedCharacter.OwnerDisplayName);
|
||||||
|
|
||||||
@@ -139,7 +147,7 @@ public sealed class CampaignApiTests : ApiTestBase
|
|||||||
var promotedPlayer = await PutAsync<UpdateUserRolesRequest, AdminUserSummary>(adminClient, $"/api/admin/users/{player.Id}/roles", new([ "admin" ]));
|
var promotedPlayer = await PutAsync<UpdateUserRolesRequest, AdminUserSummary>(adminClient, $"/api/admin/users/{player.Id}/roles", new([ "admin" ]));
|
||||||
Assert.Contains(promotedPlayer.Roles, role => string.Equals(role, "admin", StringComparison.OrdinalIgnoreCase));
|
Assert.Contains(promotedPlayer.Roles, role => string.Equals(role, "admin", StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
var campaign = await PostAsync<CreateCampaignRequest, CampaignDetails>(gmClient, "/api/campaigns", new("Disposable Campaign", "d6"));
|
var campaign = await PostAsync<CreateCampaignRequest, CampaignSummary>(gmClient, "/api/campaigns", new("Disposable Campaign", "d6"));
|
||||||
var character = await PostAsync<CreateCharacterRequest, CharacterSummary>(playerClient, "/api/characters", new("Disposable Hero", campaign.Id));
|
var character = await PostAsync<CreateCharacterRequest, CharacterSummary>(playerClient, "/api/characters", new("Disposable Hero", campaign.Id));
|
||||||
var skill = await PostAsync<CreateSkillRequest, SkillSummary>(playerClient, $"/api/characters/{character.Id}/skills", new("Stealth", "2D+1", 1, true));
|
var skill = await PostAsync<CreateSkillRequest, SkillSummary>(playerClient, $"/api/characters/{character.Id}/skills", new("Stealth", "2D+1", 1, true));
|
||||||
_ = await PostAsync<RollSkillRequest, RollResult>(playerClient, $"/api/skills/{skill.Id}/roll", new("public"));
|
_ = await PostAsync<RollSkillRequest, RollResult>(playerClient, $"/api/skills/{skill.Id}/roll", new("public"));
|
||||||
@@ -213,10 +221,10 @@ public sealed class CampaignApiTests : ApiTestBase
|
|||||||
await RegisterAsync(playerClient, "player-options", "Password123", "Player");
|
await RegisterAsync(playerClient, "player-options", "Password123", "Player");
|
||||||
await LoginAsync(playerClient, "player-options", "Password123");
|
await LoginAsync(playerClient, "player-options", "Password123");
|
||||||
|
|
||||||
var firstCampaign = await PostAsync<CreateCampaignRequest, CampaignDetails>(gmClient, "/api/campaigns", new("Alpha Visible", "d6"));
|
var firstCampaign = await PostAsync<CreateCampaignRequest, CampaignSummary>(gmClient, "/api/campaigns", new("Alpha Visible", "d6"));
|
||||||
var secondCampaign = await PostAsync<CreateCampaignRequest, CampaignDetails>(otherGmClient, "/api/campaigns", new("Beta Available", "d6"));
|
var secondCampaign = await PostAsync<CreateCampaignRequest, CampaignSummary>(otherGmClient, "/api/campaigns", new("Beta Available", "d6"));
|
||||||
|
|
||||||
var playerVisibleCampaigns = await GetAsync<IReadOnlyList<CampaignDetails>>(playerClient, "/api/campaigns");
|
var playerVisibleCampaigns = await GetAsync<IReadOnlyList<CampaignSummary>>(playerClient, "/api/campaigns");
|
||||||
Assert.Empty(playerVisibleCampaigns);
|
Assert.Empty(playerVisibleCampaigns);
|
||||||
|
|
||||||
var playerCampaignOptions = await GetAsync<IReadOnlyList<CampaignOption>>(playerClient, "/api/campaigns/options");
|
var playerCampaignOptions = await GetAsync<IReadOnlyList<CampaignOption>>(playerClient, "/api/campaigns/options");
|
||||||
@@ -246,7 +254,7 @@ public sealed class CampaignApiTests : ApiTestBase
|
|||||||
await RegisterAsync(otherClient, "other-delete", "Password123", "Other");
|
await RegisterAsync(otherClient, "other-delete", "Password123", "Other");
|
||||||
await LoginAsync(otherClient, "other-delete", "Password123");
|
await LoginAsync(otherClient, "other-delete", "Password123");
|
||||||
|
|
||||||
var campaign = await PostAsync<CreateCampaignRequest, CampaignDetails>(gmClient, "/api/campaigns", new("Deletion Campaign", "d6"));
|
var campaign = await PostAsync<CreateCampaignRequest, CampaignSummary>(gmClient, "/api/campaigns", new("Deletion Campaign", "d6"));
|
||||||
var ownerCharacter = await PostAsync<CreateCharacterRequest, CharacterSummary>(ownerClient, "/api/characters", new("Owner Character", campaign.Id));
|
var ownerCharacter = await PostAsync<CreateCharacterRequest, CharacterSummary>(ownerClient, "/api/characters", new("Owner Character", campaign.Id));
|
||||||
var otherCharacter = await PostAsync<CreateCharacterRequest, CharacterSummary>(otherClient, "/api/characters", new("Other Character", campaign.Id));
|
var otherCharacter = await PostAsync<CreateCharacterRequest, CharacterSummary>(otherClient, "/api/characters", new("Other Character", campaign.Id));
|
||||||
|
|
||||||
@@ -262,7 +270,43 @@ public sealed class CampaignApiTests : ApiTestBase
|
|||||||
var adminDelete = await adminClient.DeleteAsync($"/api/characters/{otherCharacter.Id}");
|
var adminDelete = await adminClient.DeleteAsync($"/api/characters/{otherCharacter.Id}");
|
||||||
Assert.Equal(HttpStatusCode.OK, adminDelete.StatusCode);
|
Assert.Equal(HttpStatusCode.OK, adminDelete.StatusCode);
|
||||||
|
|
||||||
var campaignAfterDeletes = await GetAsync<CampaignDetails>(gmClient, $"/api/campaigns/{campaign.Id}");
|
var campaignAfterDeletes = await GetAsync<CampaignRoster>(gmClient, $"/api/campaigns/{campaign.Id}");
|
||||||
Assert.Empty(campaignAfterDeletes.Characters);
|
Assert.Empty(campaignAfterDeletes.Characters);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CampaignLog_ReturnsMostRecentHundredEntries()
|
||||||
|
{
|
||||||
|
using var factory = CreateFactory();
|
||||||
|
using var gmClient = factory.CreateClient(new() { AllowAutoRedirect = false });
|
||||||
|
using var playerClient = factory.CreateClient(new() { AllowAutoRedirect = false });
|
||||||
|
|
||||||
|
await RegisterAsync(gmClient, "gm-log-cap", "Password123", "GM");
|
||||||
|
await LoginAsync(gmClient, "gm-log-cap", "Password123");
|
||||||
|
|
||||||
|
await RegisterAsync(playerClient, "player-log-cap", "Password123", "Player");
|
||||||
|
await LoginAsync(playerClient, "player-log-cap", "Password123");
|
||||||
|
|
||||||
|
var campaign = await PostAsync<CreateCampaignRequest, CampaignSummary>(gmClient, "/api/campaigns", new("Log Cap", "d6"));
|
||||||
|
var character = await PostAsync<CreateCharacterRequest, CharacterSummary>(playerClient, "/api/characters", new("Roller", campaign.Id));
|
||||||
|
var skill = await PostAsync<CreateSkillRequest, SkillSummary>(playerClient, $"/api/characters/{character.Id}/skills", new("Stealth", "2D+1", 1, true));
|
||||||
|
|
||||||
|
var rollIds = new List<Guid>();
|
||||||
|
for (var i = 0; i < 105; i++)
|
||||||
|
{
|
||||||
|
var roll = await PostAsync<RollSkillRequest, RollResult>(playerClient, $"/api/skills/{skill.Id}/roll", new("public"));
|
||||||
|
rollIds.Add(roll.RollId);
|
||||||
|
}
|
||||||
|
|
||||||
|
var log = await GetAsync<IReadOnlyList<CampaignLogEntry>>(gmClient, $"/api/campaigns/{campaign.Id}/log");
|
||||||
|
Assert.Equal(100, log.Count);
|
||||||
|
Assert.Equal(rollIds[5], log[0].RollId);
|
||||||
|
Assert.Equal(rollIds[^1], log[^1].RollId);
|
||||||
|
Assert.All(log, entry =>
|
||||||
|
{
|
||||||
|
Assert.False(string.IsNullOrWhiteSpace(entry.CharacterName));
|
||||||
|
Assert.False(string.IsNullOrWhiteSpace(entry.SkillName));
|
||||||
|
Assert.False(string.IsNullOrWhiteSpace(entry.RollerDisplayName));
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
using Microsoft.AspNetCore.SignalR;
|
|
||||||
using Microsoft.Extensions.Options;
|
|
||||||
|
|
||||||
namespace RpgRoller.Tests;
|
namespace RpgRoller.Tests;
|
||||||
|
|
||||||
public sealed class FrontendHostTests : ApiTestBase
|
public sealed class FrontendHostTests : ApiTestBase
|
||||||
@@ -21,24 +18,4 @@ public sealed class FrontendHostTests : ApiTestBase
|
|||||||
Assert.Contains("_framework/blazor.web.js", html);
|
Assert.Contains("_framework/blazor.web.js", html);
|
||||||
Assert.Contains("Connecting...", html);
|
Assert.Contains("Connecting...", html);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void BlazorHub_AllowsLargerInteropPayloads()
|
|
||||||
{
|
|
||||||
using var factory = CreateFactory();
|
|
||||||
var componentHubType = Type.GetType("Microsoft.AspNetCore.Components.Server.ComponentHub, Microsoft.AspNetCore.Components.Server");
|
|
||||||
Assert.NotNull(componentHubType);
|
|
||||||
|
|
||||||
var hubOptionsType = typeof(HubOptions<>).MakeGenericType(componentHubType);
|
|
||||||
var optionsType = typeof(IOptions<>).MakeGenericType(hubOptionsType);
|
|
||||||
var options = factory.Services.GetService(optionsType);
|
|
||||||
Assert.NotNull(options);
|
|
||||||
|
|
||||||
var value = optionsType.GetProperty("Value")!.GetValue(options);
|
|
||||||
Assert.NotNull(value);
|
|
||||||
|
|
||||||
var maximumReceiveMessageSize = (long?)hubOptionsType.GetProperty("MaximumReceiveMessageSize")!.GetValue(value);
|
|
||||||
|
|
||||||
Assert.Equal(256 * 1024, maximumReceiveMessageSize);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ public sealed class RollVisibilityApiTests : ApiTestBase
|
|||||||
|
|
||||||
await RegisterAsync(gmClient, "gm", "Password123", "GM");
|
await RegisterAsync(gmClient, "gm", "Password123", "GM");
|
||||||
await LoginAsync(gmClient, "gm", "Password123");
|
await LoginAsync(gmClient, "gm", "Password123");
|
||||||
var campaign = await PostAsync<CreateCampaignRequest, CampaignDetails>(gmClient, "/api/campaigns", new("Main", "d6"));
|
var campaign = await PostAsync<CreateCampaignRequest, CampaignSummary>(gmClient, "/api/campaigns", new("Main", "d6"));
|
||||||
|
|
||||||
await RegisterAsync(playerClient, "player", "Password123", "Player");
|
await RegisterAsync(playerClient, "player", "Password123", "Player");
|
||||||
await LoginAsync(playerClient, "player", "Password123");
|
await LoginAsync(playerClient, "player", "Password123");
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ public sealed class SystemApiTests : ApiTestBase
|
|||||||
await RegisterAsync(client, "sse", "Password123", "Sse User");
|
await RegisterAsync(client, "sse", "Password123", "Sse User");
|
||||||
await LoginAsync(client, "sse", "Password123");
|
await LoginAsync(client, "sse", "Password123");
|
||||||
|
|
||||||
var campaign = await PostAsync<CreateCampaignRequest, CampaignDetails>(client, "/api/campaigns", new("SSE", "d6"));
|
var campaign = await PostAsync<CreateCampaignRequest, CampaignSummary>(client, "/api/campaigns", new("SSE", "d6"));
|
||||||
|
|
||||||
var sseResponse = await client.GetAsync($"/api/events/state?campaignId={campaign.Id}", HttpCompletionOption.ResponseHeadersRead);
|
var sseResponse = await client.GetAsync($"/api/events/state?campaignId={campaign.Id}", HttpCompletionOption.ResponseHeadersRead);
|
||||||
Assert.Equal(HttpStatusCode.OK, sseResponse.StatusCode);
|
Assert.Equal(HttpStatusCode.OK, sseResponse.StatusCode);
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ public sealed class ServiceCampaignTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void GetCampaign_ForNonGmParticipant_ReturnsCampaignCharactersAndSkills()
|
public void GetCampaignAndCharacterSheet_ForNonGmParticipant_ReturnCampaignRosterAndSheet()
|
||||||
{
|
{
|
||||||
using var harness = ServiceTestSupport.CreateHarness();
|
using var harness = ServiceTestSupport.CreateHarness();
|
||||||
var service = harness.Service;
|
var service = harness.Service;
|
||||||
@@ -120,7 +120,11 @@ public sealed class ServiceCampaignTests
|
|||||||
Assert.Equal(2, ownerView.Characters.Count);
|
Assert.Equal(2, ownerView.Characters.Count);
|
||||||
Assert.Contains(ownerView.Characters, character => character.Id == ownerCharacter.Id);
|
Assert.Contains(ownerView.Characters, character => character.Id == ownerCharacter.Id);
|
||||||
Assert.Contains(ownerView.Characters, character => character.Id == otherCharacter.Id);
|
Assert.Contains(ownerView.Characters, character => character.Id == otherCharacter.Id);
|
||||||
Assert.Equal(2, ownerView.Skills.Count);
|
|
||||||
Assert.Contains(ownerView.Skills, skill => skill.Id == ownerSkill.Id);
|
var ownerSheet = ServiceTestSupport.GetValue(service.GetCharacterSheet(ownerSession, ownerCharacter.Id));
|
||||||
|
var otherSheet = ServiceTestSupport.GetValue(service.GetCharacterSheet(ownerSession, otherCharacter.Id));
|
||||||
|
Assert.Single(ownerSheet.Skills);
|
||||||
|
Assert.Contains(ownerSheet.Skills, skill => skill.Id == ownerSkill.Id);
|
||||||
|
Assert.Single(otherSheet.Skills);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,17 +45,19 @@ public sealed class ServiceSkillGroupAndOwnershipTests
|
|||||||
var deletedGroup = ServiceTestSupport.GetValue(service.DeleteSkillGroup(ownerSession, renamedGroup.Id));
|
var deletedGroup = ServiceTestSupport.GetValue(service.DeleteSkillGroup(ownerSession, renamedGroup.Id));
|
||||||
Assert.True(deletedGroup);
|
Assert.True(deletedGroup);
|
||||||
|
|
||||||
var afterGroupDelete = ServiceTestSupport.GetValue(service.GetCampaign(ownerSession, campaign.Id));
|
var ownerSheetAfterGroupDelete = ServiceTestSupport.GetValue(service.GetCharacterSheet(ownerSession, ownerCharacter.Id));
|
||||||
Assert.DoesNotContain(afterGroupDelete.SkillGroups, group => group.Id == renamedGroup.Id);
|
var otherSheetAfterGroupDelete = ServiceTestSupport.GetValue(service.GetCharacterSheet(ownerSession, otherCharacter.Id));
|
||||||
Assert.Contains(afterGroupDelete.SkillGroups, group => group.Id == otherGroup.Id);
|
Assert.DoesNotContain(ownerSheetAfterGroupDelete.SkillGroups, group => group.Id == renamedGroup.Id);
|
||||||
Assert.Null(afterGroupDelete.Skills.Single(s => s.Id == regroupedSkill.Id).SkillGroupId);
|
Assert.Contains(otherSheetAfterGroupDelete.SkillGroups, group => group.Id == otherGroup.Id);
|
||||||
|
Assert.Null(ownerSheetAfterGroupDelete.Skills.Single(s => s.Id == regroupedSkill.Id).SkillGroupId);
|
||||||
|
|
||||||
var deletedSkill = ServiceTestSupport.GetValue(service.DeleteSkill(ownerSession, regroupedSkill.Id));
|
var deletedSkill = ServiceTestSupport.GetValue(service.DeleteSkill(ownerSession, regroupedSkill.Id));
|
||||||
Assert.True(deletedSkill);
|
Assert.True(deletedSkill);
|
||||||
|
|
||||||
var ownerView = ServiceTestSupport.GetValue(service.GetCampaign(ownerSession, campaign.Id));
|
var ownerView = ServiceTestSupport.GetValue(service.GetCharacterSheet(ownerSession, ownerCharacter.Id));
|
||||||
|
var otherView = ServiceTestSupport.GetValue(service.GetCharacterSheet(ownerSession, otherCharacter.Id));
|
||||||
Assert.DoesNotContain(ownerView.SkillGroups, group => group.Id == renamedGroup.Id);
|
Assert.DoesNotContain(ownerView.SkillGroups, group => group.Id == renamedGroup.Id);
|
||||||
Assert.Contains(ownerView.SkillGroups, group => group.Id == otherGroup.Id);
|
Assert.Contains(otherView.SkillGroups, group => group.Id == otherGroup.Id);
|
||||||
Assert.DoesNotContain(ownerView.Skills, skillSummary => skillSummary.Id == regroupedSkill.Id);
|
Assert.DoesNotContain(ownerView.Skills, skillSummary => skillSummary.Id == regroupedSkill.Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,12 @@ internal static class CharacterEndpoints
|
|||||||
return ApiResultMapper.ToApiResult(result);
|
return ApiResultMapper.ToApiResult(result);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
group.MapGet("/characters/{characterId:guid}/sheet", (Guid characterId, HttpContext context, IGameService game) =>
|
||||||
|
{
|
||||||
|
var result = game.GetCharacterSheet(context.GetRequiredSessionToken(), characterId);
|
||||||
|
return ApiResultMapper.ToApiResult(result);
|
||||||
|
});
|
||||||
|
|
||||||
group.MapGet("/users/usernames", (HttpContext context, IGameService game) =>
|
group.MapGet("/users/usernames", (HttpContext context, IGameService game) =>
|
||||||
{
|
{
|
||||||
var result = game.GetUsernames(context.GetRequiredSessionToken());
|
var result = game.GetUsernames(context.GetRequiredSessionToken());
|
||||||
|
|||||||
@@ -18,8 +18,8 @@
|
|||||||
@foreach (var entry in CampaignLog)
|
@foreach (var entry in CampaignLog)
|
||||||
{
|
{
|
||||||
<li class="log-entry @LogEntryCssClass(entry)">
|
<li class="log-entry @LogEntryCssClass(entry)">
|
||||||
<p><strong>@RollerLabel(entry)</strong> rolled <strong>@SkillLabel(entry.SkillId)</strong> with
|
<p><strong>@RollerLabel(entry)</strong> rolled <strong>@entry.SkillName</strong> with
|
||||||
<strong>@CharacterLabel(entry.CharacterId)</strong></p>
|
<strong>@entry.CharacterName</strong></p>
|
||||||
<p class="roll-total inline">@entry.Result</p>
|
<p class="roll-total inline">@entry.Result</p>
|
||||||
<RollDiceStrip Dice="entry.Dice" AriaLabel="Log roll dice"/>
|
<RollDiceStrip Dice="entry.Dice" AriaLabel="Log roll dice"/>
|
||||||
<p>@entry.Breakdown</p>
|
<p>@entry.Breakdown</p>
|
||||||
|
|||||||
@@ -48,12 +48,6 @@ public partial class CampaignLogPanel
|
|||||||
[Parameter]
|
[Parameter]
|
||||||
public Func<CampaignLogEntry, string> RollerLabel { get; set; } = _ => string.Empty;
|
public Func<CampaignLogEntry, string> RollerLabel { get; set; } = _ => string.Empty;
|
||||||
|
|
||||||
[Parameter]
|
|
||||||
public Func<Guid, string> SkillLabel { get; set; } = _ => string.Empty;
|
|
||||||
|
|
||||||
[Parameter]
|
|
||||||
public Func<Guid, string> CharacterLabel { get; set; } = _ => string.Empty;
|
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public Func<CampaignLogEntry, string> LogEntryCssClass { get; set; } = _ => string.Empty;
|
public Func<CampaignLogEntry, string> LogEntryCssClass { get; set; } = _ => string.Empty;
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
<select id="campaign-select" @onchange="CampaignSelectionChanged">
|
<select id="campaign-select" @onchange="CampaignSelectionChanged">
|
||||||
@foreach (var campaign in Campaigns)
|
@foreach (var campaign in Campaigns)
|
||||||
{
|
{
|
||||||
<option value="@campaign.Id" selected="@(campaign.Id == SelectedCampaignId)">@campaign.Name (@campaign.RulesetId), GM: @campaign.Gm.DisplayName, @campaign.Characters.Count characters</option>
|
<option value="@campaign.Id" selected="@(campaign.Id == SelectedCampaignId)">@campaign.Name (@campaign.RulesetId), GM: @campaign.Gm.DisplayName, @campaign.CharacterCount characters</option>
|
||||||
}
|
}
|
||||||
</select>
|
</select>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ public partial class CampaignManagementPanel
|
|||||||
IsCreatingCampaign = true;
|
IsCreatingCampaign = true;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var campaign = await ApiClient.RequestAsync<CampaignDetails>("POST", "/api/campaigns", new CreateCampaignRequest(CampaignState.Model.Name.Trim(), CampaignState.Model.RulesetId));
|
var campaign = await ApiClient.RequestAsync<CampaignSummary>("POST", "/api/campaigns", new CreateCampaignRequest(CampaignState.Model.Name.Trim(), CampaignState.Model.RulesetId));
|
||||||
|
|
||||||
CampaignState.Model.Name = string.Empty;
|
CampaignState.Model.Name = string.Empty;
|
||||||
ShowCreateCampaignModal = false;
|
ShowCreateCampaignModal = false;
|
||||||
@@ -72,13 +72,13 @@ public partial class CampaignManagementPanel
|
|||||||
private bool ShowCreateCampaignModal { get; set; }
|
private bool ShowCreateCampaignModal { get; set; }
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public IReadOnlyList<CampaignDetails> Campaigns { get; set; } = [];
|
public IReadOnlyList<CampaignSummary> Campaigns { get; set; } = [];
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public Guid? SelectedCampaignId { get; set; }
|
public Guid? SelectedCampaignId { get; set; }
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public CampaignDetails? SelectedCampaign { get; set; }
|
public CampaignRoster? SelectedCampaign { get; set; }
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public IReadOnlyList<RulesetDefinition> Rulesets { get; set; } = [];
|
public IReadOnlyList<RulesetDefinition> Rulesets { get; set; } = [];
|
||||||
|
|||||||
@@ -285,7 +285,7 @@ public partial class CharacterPanel
|
|||||||
public bool IsCampaignDataLoading { get; set; }
|
public bool IsCampaignDataLoading { get; set; }
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public CampaignDetails? SelectedCampaign { get; set; }
|
public CampaignRoster? SelectedCampaign { get; set; }
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public Guid? SelectedCharacterId { get; set; }
|
public Guid? SelectedCharacterId { get; set; }
|
||||||
|
|||||||
@@ -61,8 +61,6 @@
|
|||||||
IsCampaignDataLoading="IsCampaignDataLoading"
|
IsCampaignDataLoading="IsCampaignDataLoading"
|
||||||
CampaignLog="PlayVisibleCampaignLog"
|
CampaignLog="PlayVisibleCampaignLog"
|
||||||
RollerLabel="RollerLabel"
|
RollerLabel="RollerLabel"
|
||||||
SkillLabel="SkillLabel"
|
|
||||||
CharacterLabel="CharacterLabel"
|
|
||||||
LogEntryCssClass="LogEntryCssClass"
|
LogEntryCssClass="LogEntryCssClass"
|
||||||
VisibilityLabel="VisibilityLabel"
|
VisibilityLabel="VisibilityLabel"
|
||||||
VisibilityBadgeCssClass="VisibilityBadgeCssClass"/>
|
VisibilityBadgeCssClass="VisibilityBadgeCssClass"/>
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ public partial class Workspace : IAsyncDisposable
|
|||||||
|
|
||||||
private async Task ReloadCampaignsAsync(Guid? preferredCampaignId)
|
private async Task ReloadCampaignsAsync(Guid? preferredCampaignId)
|
||||||
{
|
{
|
||||||
var campaigns = await ApiClient.RequestAsync<IReadOnlyList<CampaignDetails>>("GET", "/api/campaigns");
|
var campaigns = await ApiClient.RequestAsync<IReadOnlyList<CampaignSummary>>("GET", "/api/campaigns");
|
||||||
Campaigns = campaigns.OrderBy(c => c.Name, StringComparer.OrdinalIgnoreCase).ToList();
|
Campaigns = campaigns.OrderBy(c => c.Name, StringComparer.OrdinalIgnoreCase).ToList();
|
||||||
|
|
||||||
if (Campaigns.Count == 0)
|
if (Campaigns.Count == 0)
|
||||||
@@ -173,6 +173,8 @@ public partial class Workspace : IAsyncDisposable
|
|||||||
if (!SelectedCampaignId.HasValue)
|
if (!SelectedCampaignId.HasValue)
|
||||||
{
|
{
|
||||||
SelectedCampaign = null;
|
SelectedCampaign = null;
|
||||||
|
SelectedCharacterSkills = [];
|
||||||
|
SelectedCharacterSkillGroups = [];
|
||||||
CampaignLog = [];
|
CampaignLog = [];
|
||||||
SelectedCharacterId = null;
|
SelectedCharacterId = null;
|
||||||
ConnectionState = "offline";
|
ConnectionState = "offline";
|
||||||
@@ -183,9 +185,18 @@ public partial class Workspace : IAsyncDisposable
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var campaignId = SelectedCampaignId.Value;
|
var campaignId = SelectedCampaignId.Value;
|
||||||
SelectedCampaign = await ApiClient.RequestAsync<CampaignDetails>("GET", $"/api/campaigns/{campaignId}");
|
SelectedCampaign = await ApiClient.RequestAsync<CampaignRoster>("GET", $"/api/campaigns/{campaignId}");
|
||||||
CampaignLog = (await ApiClient.RequestAsync<IReadOnlyList<CampaignLogEntry>>("GET", $"/api/campaigns/{campaignId}/log")).ToList();
|
|
||||||
SyncSelectedCharacter();
|
SyncSelectedCharacter();
|
||||||
|
|
||||||
|
if (IsPlayScreen && PlaySelectedCharacterId.HasValue && SelectedCharacterId != PlaySelectedCharacterId)
|
||||||
|
SelectedCharacterId = PlaySelectedCharacterId;
|
||||||
|
|
||||||
|
await RefreshSelectedCharacterSheetAsync();
|
||||||
|
|
||||||
|
CampaignLog = IsPlayScreen
|
||||||
|
? (await ApiClient.RequestAsync<IReadOnlyList<CampaignLogEntry>>("GET", $"/api/campaigns/{campaignId}/log")).ToList()
|
||||||
|
: [];
|
||||||
|
|
||||||
await EnsureSelectedCharacterActiveAsync();
|
await EnsureSelectedCharacterActiveAsync();
|
||||||
}
|
}
|
||||||
catch (ApiRequestException ex) when (ex.StatusCode == 401)
|
catch (ApiRequestException ex) when (ex.StatusCode == 401)
|
||||||
@@ -238,6 +249,12 @@ public partial class Workspace : IAsyncDisposable
|
|||||||
await PersistScreenPreferenceAsync(CurrentScreen);
|
await PersistScreenPreferenceAsync(CurrentScreen);
|
||||||
await InvokeAsync(StateHasChanged);
|
await InvokeAsync(StateHasChanged);
|
||||||
|
|
||||||
|
if (User is not null)
|
||||||
|
{
|
||||||
|
await RefreshCampaignScopeAsync();
|
||||||
|
await SyncStateEventsAsync();
|
||||||
|
}
|
||||||
|
|
||||||
if (IsAdminScreen)
|
if (IsAdminScreen)
|
||||||
{
|
{
|
||||||
await EnsureAdminUsersLoadedAsync();
|
await EnsureAdminUsersLoadedAsync();
|
||||||
@@ -521,6 +538,7 @@ public partial class Workspace : IAsyncDisposable
|
|||||||
private async Task SelectCharacterAsync(Guid characterId)
|
private async Task SelectCharacterAsync(Guid characterId)
|
||||||
{
|
{
|
||||||
SelectedCharacterId = characterId;
|
SelectedCharacterId = characterId;
|
||||||
|
await RefreshSelectedCharacterSheetAsync();
|
||||||
await EnsureSelectedCharacterActiveAsync();
|
await EnsureSelectedCharacterActiveAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -559,6 +577,24 @@ public partial class Workspace : IAsyncDisposable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task RefreshSelectedCharacterSheetAsync()
|
||||||
|
{
|
||||||
|
if (!SelectedCharacterId.HasValue || SelectedCampaign is null || !IsPlayScreen)
|
||||||
|
{
|
||||||
|
SelectedCharacterSkills = [];
|
||||||
|
SelectedCharacterSkillGroups = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var sheet = await ApiClient.RequestAsync<CharacterSheet>("GET", $"/api/characters/{SelectedCharacterId.Value}/sheet");
|
||||||
|
SelectedCharacterSkillGroups = sheet.SkillGroups
|
||||||
|
.OrderBy(group => group.Name, StringComparer.OrdinalIgnoreCase)
|
||||||
|
.ToList();
|
||||||
|
SelectedCharacterSkills = sheet.Skills
|
||||||
|
.OrderBy(skill => skill.Name, StringComparer.OrdinalIgnoreCase)
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
private async Task OnSkillCreatedAsync(Guid _)
|
private async Task OnSkillCreatedAsync(Guid _)
|
||||||
{
|
{
|
||||||
await RefreshCampaignScopeAsync();
|
await RefreshCampaignScopeAsync();
|
||||||
@@ -715,7 +751,7 @@ public partial class Workspace : IAsyncDisposable
|
|||||||
|
|
||||||
private async Task SyncStateEventsAsync()
|
private async Task SyncStateEventsAsync()
|
||||||
{
|
{
|
||||||
if (User is null || !SelectedCampaignId.HasValue)
|
if (User is null || !SelectedCampaignId.HasValue || IsAdminScreen)
|
||||||
{
|
{
|
||||||
await StopStateEventsAsync();
|
await StopStateEventsAsync();
|
||||||
ConnectionState = "offline";
|
ConnectionState = "offline";
|
||||||
@@ -795,16 +831,6 @@ public partial class Workspace : IAsyncDisposable
|
|||||||
return string.IsNullOrWhiteSpace(ownerDisplayName) ? "Unknown owner" : ownerDisplayName;
|
return string.IsNullOrWhiteSpace(ownerDisplayName) ? "Unknown owner" : ownerDisplayName;
|
||||||
}
|
}
|
||||||
|
|
||||||
private string CharacterLabel(Guid characterId)
|
|
||||||
{
|
|
||||||
return SelectedCampaign?.Characters.FirstOrDefault(c => c.Id == characterId)?.Name ?? "Hidden character";
|
|
||||||
}
|
|
||||||
|
|
||||||
private string SkillLabel(Guid skillId)
|
|
||||||
{
|
|
||||||
return SelectedCampaign?.Skills.FirstOrDefault(s => s.Id == skillId)?.Name ?? "Hidden skill";
|
|
||||||
}
|
|
||||||
|
|
||||||
private string SkillDefinitionLabel(SkillSummary skill)
|
private string SkillDefinitionLabel(SkillSummary skill)
|
||||||
{
|
{
|
||||||
if (!string.Equals(SelectedCampaign?.RulesetId, "d6", StringComparison.OrdinalIgnoreCase))
|
if (!string.Equals(SelectedCampaign?.RulesetId, "d6", StringComparison.OrdinalIgnoreCase))
|
||||||
@@ -822,7 +848,7 @@ public partial class Workspace : IAsyncDisposable
|
|||||||
if (SelectedCampaign is not null && entry.RollerUserId == SelectedCampaign.Gm.Id)
|
if (SelectedCampaign is not null && entry.RollerUserId == SelectedCampaign.Gm.Id)
|
||||||
return "GM";
|
return "GM";
|
||||||
|
|
||||||
return "Participant";
|
return entry.RollerDisplayName;
|
||||||
}
|
}
|
||||||
|
|
||||||
private string VisibilityLabel(CampaignLogEntry entry)
|
private string VisibilityLabel(CampaignLogEntry entry)
|
||||||
@@ -866,6 +892,8 @@ public partial class Workspace : IAsyncDisposable
|
|||||||
SelectedCampaign = null;
|
SelectedCampaign = null;
|
||||||
Campaigns = [];
|
Campaigns = [];
|
||||||
CharacterCampaignOptions = [];
|
CharacterCampaignOptions = [];
|
||||||
|
SelectedCharacterSkills = [];
|
||||||
|
SelectedCharacterSkillGroups = [];
|
||||||
CampaignLog = [];
|
CampaignLog = [];
|
||||||
SelectedCharacterId = null;
|
SelectedCharacterId = null;
|
||||||
LastRoll = null;
|
LastRoll = null;
|
||||||
@@ -934,9 +962,11 @@ public partial class Workspace : IAsyncDisposable
|
|||||||
private UserSummary? User { get; set; }
|
private UserSummary? User { get; set; }
|
||||||
private Guid? ActiveCharacterId { get; set; }
|
private Guid? ActiveCharacterId { get; set; }
|
||||||
private Guid? SelectedCampaignId { get; set; }
|
private Guid? SelectedCampaignId { get; set; }
|
||||||
private CampaignDetails? SelectedCampaign { get; set; }
|
private CampaignRoster? SelectedCampaign { get; set; }
|
||||||
private List<CampaignDetails> Campaigns { get; set; } = [];
|
private List<CampaignSummary> Campaigns { get; set; } = [];
|
||||||
private List<CampaignOption> CharacterCampaignOptions { get; set; } = [];
|
private List<CampaignOption> CharacterCampaignOptions { get; set; } = [];
|
||||||
|
private List<SkillSummary> SelectedCharacterSkills { get; set; } = [];
|
||||||
|
private List<SkillGroupSummary> SelectedCharacterSkillGroups { get; set; } = [];
|
||||||
private List<CampaignLogEntry> CampaignLog { get; set; } = [];
|
private List<CampaignLogEntry> CampaignLog { get; set; } = [];
|
||||||
private List<RulesetDefinition> Rulesets { get; set; } = [];
|
private List<RulesetDefinition> Rulesets { get; set; } = [];
|
||||||
private List<AdminUserSummary> AdminUsers { get; set; } = [];
|
private List<AdminUserSummary> AdminUsers { get; set; } = [];
|
||||||
@@ -973,12 +1003,12 @@ public partial class Workspace : IAsyncDisposable
|
|||||||
[Parameter]
|
[Parameter]
|
||||||
public EventCallback<string?> LoggedOut { get; set; }
|
public EventCallback<string?> LoggedOut { get; set; }
|
||||||
|
|
||||||
private string? SelectedCampaignName => SelectedCampaign?.Name;
|
private string? SelectedCampaignName => SelectedCampaign?.Name ?? Campaigns.FirstOrDefault(campaign => campaign.Id == SelectedCampaignId)?.Name;
|
||||||
|
|
||||||
private CharacterSummary? SelectedCharacter =>
|
private CharacterSummary? SelectedCharacter =>
|
||||||
SelectedCampaign?.Characters.FirstOrDefault(c => c.Id == SelectedCharacterId);
|
SelectedCampaign?.Characters.FirstOrDefault(c => c.Id == SelectedCharacterId);
|
||||||
|
|
||||||
private CampaignDetails? PlaySelectedCampaign
|
private CampaignRoster? PlaySelectedCampaign
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
@@ -986,27 +1016,18 @@ public partial class Workspace : IAsyncDisposable
|
|||||||
return null;
|
return null;
|
||||||
|
|
||||||
if (User is null)
|
if (User is null)
|
||||||
return new CampaignDetails(SelectedCampaign.Id, SelectedCampaign.Name, SelectedCampaign.RulesetId, SelectedCampaign.Gm, [], [], []);
|
return new CampaignRoster(SelectedCampaign.Id, SelectedCampaign.Name, SelectedCampaign.RulesetId, SelectedCampaign.Gm, []);
|
||||||
|
|
||||||
var ownedCharacters = SelectedCampaign.Characters
|
var ownedCharacters = SelectedCampaign.Characters
|
||||||
.Where(character => character.OwnerUserId == User.Id)
|
.Where(character => character.OwnerUserId == User.Id)
|
||||||
.ToList();
|
.ToList();
|
||||||
var ownedCharacterIds = ownedCharacters.Select(character => character.Id).ToHashSet();
|
|
||||||
var ownedSkillGroups = SelectedCampaign.SkillGroups
|
|
||||||
.Where(group => ownedCharacterIds.Contains(group.CharacterId))
|
|
||||||
.ToList();
|
|
||||||
var ownedSkills = SelectedCampaign.Skills
|
|
||||||
.Where(skill => ownedCharacterIds.Contains(skill.CharacterId))
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
return new CampaignDetails(
|
return new CampaignRoster(
|
||||||
SelectedCampaign.Id,
|
SelectedCampaign.Id,
|
||||||
SelectedCampaign.Name,
|
SelectedCampaign.Name,
|
||||||
SelectedCampaign.RulesetId,
|
SelectedCampaign.RulesetId,
|
||||||
SelectedCampaign.Gm,
|
SelectedCampaign.Gm,
|
||||||
ownedCharacters,
|
ownedCharacters);
|
||||||
ownedSkillGroups,
|
|
||||||
ownedSkills);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1038,20 +1059,10 @@ public partial class Workspace : IAsyncDisposable
|
|||||||
private Guid? PlaySelectedCharacterId => PlaySelectedCharacter?.Id;
|
private Guid? PlaySelectedCharacterId => PlaySelectedCharacter?.Id;
|
||||||
|
|
||||||
private List<SkillSummary> PlaySelectedCharacterSkills =>
|
private List<SkillSummary> PlaySelectedCharacterSkills =>
|
||||||
PlaySelectedCampaign is null || !PlaySelectedCharacterId.HasValue
|
PlaySelectedCampaign is null || !PlaySelectedCharacterId.HasValue ? [] : SelectedCharacterSkills;
|
||||||
? []
|
|
||||||
: PlaySelectedCampaign.Skills
|
|
||||||
.Where(skill => skill.CharacterId == PlaySelectedCharacterId.Value)
|
|
||||||
.OrderBy(skill => skill.Name, StringComparer.OrdinalIgnoreCase)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
private List<SkillGroupSummary> PlaySelectedCharacterSkillGroups =>
|
private List<SkillGroupSummary> PlaySelectedCharacterSkillGroups =>
|
||||||
PlaySelectedCampaign is null || !PlaySelectedCharacterId.HasValue
|
PlaySelectedCampaign is null || !PlaySelectedCharacterId.HasValue ? [] : SelectedCharacterSkillGroups;
|
||||||
? []
|
|
||||||
: PlaySelectedCampaign.SkillGroups
|
|
||||||
.Where(group => group.CharacterId == PlaySelectedCharacterId.Value)
|
|
||||||
.OrderBy(group => group.Name, StringComparer.OrdinalIgnoreCase)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
private List<CampaignLogEntry> PlayVisibleCampaignLog =>
|
private List<CampaignLogEntry> PlayVisibleCampaignLog =>
|
||||||
User is null
|
User is null
|
||||||
@@ -1079,12 +1090,6 @@ public partial class Workspace : IAsyncDisposable
|
|||||||
return user.Roles.Contains(UserRoles.Admin, StringComparer.OrdinalIgnoreCase);
|
return user.Roles.Contains(UserRoles.Admin, StringComparer.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<SkillSummary> SelectedCharacterSkills =>
|
|
||||||
SelectedCampaign is null || !SelectedCharacterId.HasValue ? [] : SelectedCampaign.Skills.Where(skill => skill.CharacterId == SelectedCharacterId.Value).OrderBy(skill => skill.Name, StringComparer.OrdinalIgnoreCase).ToList();
|
|
||||||
|
|
||||||
private List<SkillGroupSummary> SelectedCharacterSkillGroups =>
|
|
||||||
SelectedCampaign is null || !SelectedCharacterId.HasValue ? [] : SelectedCampaign.SkillGroups.Where(group => group.CharacterId == SelectedCharacterId.Value).OrderBy(group => group.Name, StringComparer.OrdinalIgnoreCase).ToList();
|
|
||||||
|
|
||||||
private bool IsPlayScreen => string.Equals(CurrentScreen, ScreenPlay, StringComparison.OrdinalIgnoreCase);
|
private bool IsPlayScreen => string.Equals(CurrentScreen, ScreenPlay, StringComparison.OrdinalIgnoreCase);
|
||||||
private bool IsManagementScreen => string.Equals(CurrentScreen, ScreenManagement, StringComparison.OrdinalIgnoreCase);
|
private bool IsManagementScreen => string.Equals(CurrentScreen, ScreenManagement, StringComparison.OrdinalIgnoreCase);
|
||||||
private bool IsAdminScreen => string.Equals(CurrentScreen, ScreenAdmin, StringComparison.OrdinalIgnoreCase);
|
private bool IsAdminScreen => string.Equals(CurrentScreen, ScreenAdmin, StringComparison.OrdinalIgnoreCase);
|
||||||
|
|||||||
@@ -20,7 +20,9 @@ public sealed record RulesetDefinition(string Id, string Name, string DiceSyntax
|
|||||||
|
|
||||||
public sealed record CreateCampaignRequest(string Name, string RulesetId);
|
public sealed record CreateCampaignRequest(string Name, string RulesetId);
|
||||||
|
|
||||||
public sealed record CampaignDetails(Guid Id, string Name, string RulesetId, UserSummary Gm, IReadOnlyList<CharacterSummary> Characters, IReadOnlyList<SkillGroupSummary> SkillGroups, IReadOnlyList<SkillSummary> Skills);
|
public sealed record CampaignSummary(Guid Id, string Name, string RulesetId, UserSummary Gm, int CharacterCount);
|
||||||
|
|
||||||
|
public sealed record CampaignRoster(Guid Id, string Name, string RulesetId, UserSummary Gm, IReadOnlyList<CharacterSummary> Characters);
|
||||||
|
|
||||||
public sealed record CampaignOption(Guid Id, string Name);
|
public sealed record CampaignOption(Guid Id, string Name);
|
||||||
|
|
||||||
@@ -48,4 +50,6 @@ public sealed record RollDieResult(int Roll, bool Crit, bool Fumble, bool Wild,
|
|||||||
|
|
||||||
public sealed record RollResult(Guid RollId, Guid CampaignId, Guid CharacterId, Guid SkillId, Guid RollerUserId, string Visibility, int Result, string Breakdown, IReadOnlyList<RollDieResult> Dice, DateTimeOffset TimestampUtc);
|
public sealed record RollResult(Guid RollId, Guid CampaignId, Guid CharacterId, Guid SkillId, Guid RollerUserId, string Visibility, int Result, string Breakdown, IReadOnlyList<RollDieResult> Dice, DateTimeOffset TimestampUtc);
|
||||||
|
|
||||||
public sealed record CampaignLogEntry(Guid RollId, Guid CampaignId, Guid CharacterId, Guid SkillId, Guid RollerUserId, string Visibility, int Result, string Breakdown, IReadOnlyList<RollDieResult> Dice, DateTimeOffset TimestampUtc);
|
public sealed record CharacterSheet(Guid CharacterId, IReadOnlyList<SkillGroupSummary> SkillGroups, IReadOnlyList<SkillSummary> Skills);
|
||||||
|
|
||||||
|
public sealed record CampaignLogEntry(Guid RollId, Guid CampaignId, Guid CharacterId, string CharacterName, Guid SkillId, string SkillName, Guid RollerUserId, string RollerDisplayName, string Visibility, int Result, string Breakdown, IReadOnlyList<RollDieResult> Dice, DateTimeOffset TimestampUtc);
|
||||||
|
|||||||
@@ -4,9 +4,7 @@ using RpgRoller.Hosting;
|
|||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
builder.Services.AddRpgRollerCore(builder.Configuration, builder.Environment);
|
builder.Services.AddRpgRollerCore(builder.Configuration, builder.Environment);
|
||||||
builder.Services.AddRazorComponents()
|
builder.Services.AddRazorComponents().AddInteractiveServerComponents();
|
||||||
.AddInteractiveServerComponents()
|
|
||||||
.AddHubOptions(options => options.MaximumReceiveMessageSize = 256 * 1024);
|
|
||||||
builder.Services.AddScoped<RpgRollerApiClient>();
|
builder.Services.AddScoped<RpgRollerApiClient>();
|
||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|||||||
@@ -128,20 +128,20 @@ public sealed class GameService : IGameService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ServiceResult<CampaignDetails> CreateCampaign(string sessionToken, string name, string rulesetId)
|
public ServiceResult<CampaignSummary> CreateCampaign(string sessionToken, string name, string rulesetId)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(name))
|
if (string.IsNullOrWhiteSpace(name))
|
||||||
return ServiceResult<CampaignDetails>.Failure("invalid_campaign_name", "Campaign name is required.");
|
return ServiceResult<CampaignSummary>.Failure("invalid_campaign_name", "Campaign name is required.");
|
||||||
|
|
||||||
var ruleset = DiceRules.TryParseRulesetId(rulesetId);
|
var ruleset = DiceRules.TryParseRulesetId(rulesetId);
|
||||||
if (ruleset is null)
|
if (ruleset is null)
|
||||||
return ServiceResult<CampaignDetails>.Failure("invalid_ruleset", "Unknown ruleset.");
|
return ServiceResult<CampaignSummary>.Failure("invalid_ruleset", "Unknown ruleset.");
|
||||||
|
|
||||||
lock (m_Gate)
|
lock (m_Gate)
|
||||||
{
|
{
|
||||||
var user = ResolveUserLocked(sessionToken);
|
var user = ResolveUserLocked(sessionToken);
|
||||||
if (user is null)
|
if (user is null)
|
||||||
return ServiceResult<CampaignDetails>.Failure("unauthorized", "You must be logged in.");
|
return ServiceResult<CampaignSummary>.Failure("unauthorized", "You must be logged in.");
|
||||||
|
|
||||||
var campaign = new Campaign
|
var campaign = new Campaign
|
||||||
{
|
{
|
||||||
@@ -154,17 +154,17 @@ public sealed class GameService : IGameService
|
|||||||
|
|
||||||
m_CampaignsById[campaign.Id] = campaign;
|
m_CampaignsById[campaign.Id] = campaign;
|
||||||
PersistStateLocked();
|
PersistStateLocked();
|
||||||
return ServiceResult<CampaignDetails>.Success(ToCampaignDetails(campaign));
|
return ServiceResult<CampaignSummary>.Success(ToCampaignSummary(campaign));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ServiceResult<IReadOnlyList<CampaignDetails>> GetCampaigns(string sessionToken)
|
public ServiceResult<IReadOnlyList<CampaignSummary>> GetCampaigns(string sessionToken)
|
||||||
{
|
{
|
||||||
lock (m_Gate)
|
lock (m_Gate)
|
||||||
{
|
{
|
||||||
var user = ResolveUserLocked(sessionToken);
|
var user = ResolveUserLocked(sessionToken);
|
||||||
if (user is null)
|
if (user is null)
|
||||||
return ServiceResult<IReadOnlyList<CampaignDetails>>.Failure("unauthorized", "You must be logged in.");
|
return ServiceResult<IReadOnlyList<CampaignSummary>>.Failure("unauthorized", "You must be logged in.");
|
||||||
|
|
||||||
IEnumerable<Campaign> visibleCampaigns;
|
IEnumerable<Campaign> visibleCampaigns;
|
||||||
if (UserHasRoleLocked(user, UserRoles.Admin))
|
if (UserHasRoleLocked(user, UserRoles.Admin))
|
||||||
@@ -180,9 +180,9 @@ public sealed class GameService : IGameService
|
|||||||
visibleCampaigns = campaignIds.Select(campaignId => m_CampaignsById[campaignId]);
|
visibleCampaigns = campaignIds.Select(campaignId => m_CampaignsById[campaignId]);
|
||||||
}
|
}
|
||||||
|
|
||||||
var results = visibleCampaigns.OrderBy(campaign => campaign.Name, StringComparer.OrdinalIgnoreCase).Select(ToCampaignDetails).ToArray();
|
var results = visibleCampaigns.OrderBy(campaign => campaign.Name, StringComparer.OrdinalIgnoreCase).Select(ToCampaignSummary).ToArray();
|
||||||
|
|
||||||
return ServiceResult<IReadOnlyList<CampaignDetails>>.Success(results);
|
return ServiceResult<IReadOnlyList<CampaignSummary>>.Success(results);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -203,24 +203,16 @@ public sealed class GameService : IGameService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ServiceResult<CampaignDetails> GetCampaign(string sessionToken, Guid campaignId)
|
public ServiceResult<CampaignRoster> GetCampaign(string sessionToken, Guid campaignId)
|
||||||
{
|
{
|
||||||
lock (m_Gate)
|
lock (m_Gate)
|
||||||
{
|
{
|
||||||
var context = ResolveContextLocked(sessionToken, campaignId);
|
var context = ResolveContextLocked(sessionToken, campaignId);
|
||||||
if (!context.Succeeded)
|
if (!context.Succeeded)
|
||||||
return ServiceResult<CampaignDetails>.Failure(context.Error!.Code, context.Error.Message);
|
return ServiceResult<CampaignRoster>.Failure(context.Error!.Code, context.Error.Message);
|
||||||
|
|
||||||
var (_, campaign) = context.Value;
|
var (_, campaign) = context.Value;
|
||||||
var gm = m_UsersById[campaign.GmUserId];
|
return ServiceResult<CampaignRoster>.Success(ToCampaignRoster(campaign));
|
||||||
var characters = m_CharactersById.Values.Where(c => c.CampaignId == campaign.Id).Select(ToCharacterSummary).ToList();
|
|
||||||
|
|
||||||
var visibleCharacterIds = characters.Select(c => c.Id).ToHashSet();
|
|
||||||
|
|
||||||
var skillGroups = m_SkillGroupsById.Values.Where(g => visibleCharacterIds.Contains(g.CharacterId)).OrderBy(g => g.Name, StringComparer.OrdinalIgnoreCase).Select(ToSkillGroupSummary).ToArray();
|
|
||||||
var skills = m_SkillsById.Values.Where(s => visibleCharacterIds.Contains(s.CharacterId)).OrderBy(s => s.Name, StringComparer.OrdinalIgnoreCase).Select(ToSkillSummary).ToArray();
|
|
||||||
|
|
||||||
return ServiceResult<CampaignDetails>.Success(new(campaign.Id, campaign.Name, DiceRules.ToRulesetId(campaign.Ruleset), ToUserSummary(gm), characters, skillGroups, skills));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -718,6 +710,27 @@ public sealed class GameService : IGameService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ServiceResult<CharacterSheet> GetCharacterSheet(string sessionToken, Guid characterId)
|
||||||
|
{
|
||||||
|
lock (m_Gate)
|
||||||
|
{
|
||||||
|
var user = ResolveUserLocked(sessionToken);
|
||||||
|
if (user is null)
|
||||||
|
return ServiceResult<CharacterSheet>.Failure("unauthorized", "You must be logged in.");
|
||||||
|
|
||||||
|
if (!m_CharactersById.TryGetValue(characterId, out var character))
|
||||||
|
return ServiceResult<CharacterSheet>.Failure("character_not_found", "Character was not found.");
|
||||||
|
|
||||||
|
if (!TryResolveCharacterCampaignLocked(character, out var campaign, out var campaignError))
|
||||||
|
return ServiceResult<CharacterSheet>.Failure(campaignError!.Code, campaignError.Message);
|
||||||
|
|
||||||
|
if (!CanViewCampaignLocked(user.Id, campaign.Id))
|
||||||
|
return ServiceResult<CharacterSheet>.Failure("forbidden", "You are not a participant in this campaign.");
|
||||||
|
|
||||||
|
return ServiceResult<CharacterSheet>.Success(ToCharacterSheet(character.Id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public ServiceResult<RollResult> RollSkill(string sessionToken, Guid skillId, string visibility)
|
public ServiceResult<RollResult> RollSkill(string sessionToken, Guid skillId, string visibility)
|
||||||
{
|
{
|
||||||
lock (m_Gate)
|
lock (m_Gate)
|
||||||
@@ -776,7 +789,16 @@ public sealed class GameService : IGameService
|
|||||||
return ServiceResult<IReadOnlyList<CampaignLogEntry>>.Failure(context.Error!.Code, context.Error.Message);
|
return ServiceResult<IReadOnlyList<CampaignLogEntry>>.Failure(context.Error!.Code, context.Error.Message);
|
||||||
|
|
||||||
var (user, campaign) = context.Value!;
|
var (user, campaign) = context.Value!;
|
||||||
var entries = m_RollLog.Where(r => r.CampaignId == campaign.Id).Where(r => r.Visibility == RollVisibility.Public || r.RollerUserId == user.Id || campaign.GmUserId == user.Id).OrderBy(r => r.TimestampUtc).ThenBy(r => r.Id).Select(ToLogEntry).ToArray();
|
var entries = m_RollLog
|
||||||
|
.Where(r => r.CampaignId == campaign.Id)
|
||||||
|
.Where(r => r.Visibility == RollVisibility.Public || r.RollerUserId == user.Id || campaign.GmUserId == user.Id)
|
||||||
|
.OrderByDescending(r => r.TimestampUtc)
|
||||||
|
.ThenByDescending(r => r.Id)
|
||||||
|
.Take(CampaignLogPageSize)
|
||||||
|
.OrderBy(r => r.TimestampUtc)
|
||||||
|
.ThenBy(r => r.Id)
|
||||||
|
.Select(ToLogEntry)
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
return ServiceResult<IReadOnlyList<CampaignLogEntry>>.Success(entries);
|
return ServiceResult<IReadOnlyList<CampaignLogEntry>>.Success(entries);
|
||||||
}
|
}
|
||||||
@@ -989,18 +1011,39 @@ public sealed class GameService : IGameService
|
|||||||
return new(campaign.Id, campaign.Name);
|
return new(campaign.Id, campaign.Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
private CampaignDetails ToCampaignDetails(Campaign campaign)
|
private CampaignSummary ToCampaignSummary(Campaign campaign)
|
||||||
{
|
{
|
||||||
lock (m_Gate)
|
var gm = m_UsersById[campaign.GmUserId];
|
||||||
{
|
var characterCount = m_CharactersById.Values.Count(character => character.CampaignId == campaign.Id);
|
||||||
var gm = m_UsersById[campaign.GmUserId];
|
return new(campaign.Id, campaign.Name, DiceRules.ToRulesetId(campaign.Ruleset), ToUserSummary(gm), characterCount);
|
||||||
var characters = m_CharactersById.Values.Where(c => c.CampaignId == campaign.Id).Select(ToCharacterSummary).ToList();
|
}
|
||||||
var visibleCharacterIds = characters.Select(c => c.Id).ToHashSet();
|
|
||||||
var skillGroups = m_SkillGroupsById.Values.Where(g => visibleCharacterIds.Contains(g.CharacterId)).OrderBy(g => g.Name, StringComparer.OrdinalIgnoreCase).Select(ToSkillGroupSummary).ToArray();
|
|
||||||
var skills = m_SkillsById.Values.Where(s => visibleCharacterIds.Contains(s.CharacterId)).OrderBy(s => s.Name, StringComparer.OrdinalIgnoreCase).Select(ToSkillSummary).ToArray();
|
|
||||||
|
|
||||||
return new(campaign.Id, campaign.Name, DiceRules.ToRulesetId(campaign.Ruleset), ToUserSummary(gm), characters, skillGroups, skills);
|
private CampaignRoster ToCampaignRoster(Campaign campaign)
|
||||||
}
|
{
|
||||||
|
var gm = m_UsersById[campaign.GmUserId];
|
||||||
|
var characters = m_CharactersById.Values
|
||||||
|
.Where(character => character.CampaignId == campaign.Id)
|
||||||
|
.OrderBy(character => character.Name, StringComparer.OrdinalIgnoreCase)
|
||||||
|
.Select(ToCharacterSummary)
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
return new(campaign.Id, campaign.Name, DiceRules.ToRulesetId(campaign.Ruleset), ToUserSummary(gm), characters);
|
||||||
|
}
|
||||||
|
|
||||||
|
private CharacterSheet ToCharacterSheet(Guid characterId)
|
||||||
|
{
|
||||||
|
var skillGroups = m_SkillGroupsById.Values
|
||||||
|
.Where(group => group.CharacterId == characterId)
|
||||||
|
.OrderBy(group => group.Name, StringComparer.OrdinalIgnoreCase)
|
||||||
|
.Select(ToSkillGroupSummary)
|
||||||
|
.ToArray();
|
||||||
|
var skills = m_SkillsById.Values
|
||||||
|
.Where(skill => skill.CharacterId == characterId)
|
||||||
|
.OrderBy(skill => skill.Name, StringComparer.OrdinalIgnoreCase)
|
||||||
|
.Select(ToSkillSummary)
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
return new(characterId, skillGroups, skills);
|
||||||
}
|
}
|
||||||
|
|
||||||
private CharacterSummary ToCharacterSummary(Character character)
|
private CharacterSummary ToCharacterSummary(Character character)
|
||||||
@@ -1024,11 +1067,27 @@ public sealed class GameService : IGameService
|
|||||||
return new(entry.Id, entry.CampaignId, entry.CharacterId, entry.SkillId, entry.RollerUserId, entry.Visibility == RollVisibility.Public ? "public" : "private", entry.Result, entry.Breakdown, dice, entry.TimestampUtc);
|
return new(entry.Id, entry.CampaignId, entry.CharacterId, entry.SkillId, entry.RollerUserId, entry.Visibility == RollVisibility.Public ? "public" : "private", entry.Result, entry.Breakdown, dice, entry.TimestampUtc);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static CampaignLogEntry ToLogEntry(RollLogEntry entry)
|
private CampaignLogEntry ToLogEntry(RollLogEntry entry)
|
||||||
{
|
{
|
||||||
var dice = DeserializeDice(entry.Dice);
|
var dice = DeserializeDice(entry.Dice);
|
||||||
|
var characterName = m_CharactersById.TryGetValue(entry.CharacterId, out var character) ? character.Name : "Unknown character";
|
||||||
|
var skillName = m_SkillsById.TryGetValue(entry.SkillId, out var skill) ? skill.Name : "Unknown skill";
|
||||||
|
var rollerDisplayName = ResolveOwnerDisplayName(entry.RollerUserId);
|
||||||
|
|
||||||
return new(entry.Id, entry.CampaignId, entry.CharacterId, entry.SkillId, entry.RollerUserId, entry.Visibility == RollVisibility.Public ? "public" : "private", entry.Result, entry.Breakdown, dice, entry.TimestampUtc);
|
return new(
|
||||||
|
entry.Id,
|
||||||
|
entry.CampaignId,
|
||||||
|
entry.CharacterId,
|
||||||
|
characterName,
|
||||||
|
entry.SkillId,
|
||||||
|
skillName,
|
||||||
|
entry.RollerUserId,
|
||||||
|
rollerDisplayName,
|
||||||
|
entry.Visibility == RollVisibility.Public ? "public" : "private",
|
||||||
|
entry.Result,
|
||||||
|
entry.Breakdown,
|
||||||
|
dice,
|
||||||
|
entry.TimestampUtc);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string SerializeDice(IReadOnlyList<RollDieResult> dice)
|
private static string SerializeDice(IReadOnlyList<RollDieResult> dice)
|
||||||
@@ -1385,6 +1444,7 @@ public sealed class GameService : IGameService
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private const int CampaignLogPageSize = 100;
|
||||||
private static readonly JsonSerializerOptions DiceJsonOptions = new(JsonSerializerDefaults.Web);
|
private static readonly JsonSerializerOptions DiceJsonOptions = new(JsonSerializerDefaults.Web);
|
||||||
private readonly Dictionary<Guid, Campaign> m_CampaignsById = [];
|
private readonly Dictionary<Guid, Campaign> m_CampaignsById = [];
|
||||||
private readonly Dictionary<Guid, Character> m_CharactersById = [];
|
private readonly Dictionary<Guid, Character> m_CharactersById = [];
|
||||||
|
|||||||
@@ -12,10 +12,10 @@ public interface IGameService
|
|||||||
UserSummary? GetUserBySession(string sessionToken);
|
UserSummary? GetUserBySession(string sessionToken);
|
||||||
ServiceResult<MeResponse> GetMe(string sessionToken);
|
ServiceResult<MeResponse> GetMe(string sessionToken);
|
||||||
|
|
||||||
ServiceResult<CampaignDetails> CreateCampaign(string sessionToken, string name, string rulesetId);
|
ServiceResult<CampaignSummary> CreateCampaign(string sessionToken, string name, string rulesetId);
|
||||||
ServiceResult<IReadOnlyList<CampaignDetails>> GetCampaigns(string sessionToken);
|
ServiceResult<IReadOnlyList<CampaignSummary>> GetCampaigns(string sessionToken);
|
||||||
ServiceResult<IReadOnlyList<CampaignOption>> GetCharacterCampaignOptions(string sessionToken);
|
ServiceResult<IReadOnlyList<CampaignOption>> GetCharacterCampaignOptions(string sessionToken);
|
||||||
ServiceResult<CampaignDetails> GetCampaign(string sessionToken, Guid campaignId);
|
ServiceResult<CampaignRoster> GetCampaign(string sessionToken, Guid campaignId);
|
||||||
ServiceResult<bool> DeleteCampaign(string sessionToken, Guid campaignId);
|
ServiceResult<bool> DeleteCampaign(string sessionToken, Guid campaignId);
|
||||||
ServiceResult<IReadOnlyList<string>> GetUsernames(string sessionToken);
|
ServiceResult<IReadOnlyList<string>> GetUsernames(string sessionToken);
|
||||||
ServiceResult<IReadOnlyList<AdminUserSummary>> GetUsers(string sessionToken);
|
ServiceResult<IReadOnlyList<AdminUserSummary>> GetUsers(string sessionToken);
|
||||||
@@ -34,6 +34,7 @@ public interface IGameService
|
|||||||
ServiceResult<SkillSummary> CreateSkill(string sessionToken, Guid characterId, string name, string diceRollDefinition, int wildDice, bool allowFumble, Guid? skillGroupId = null);
|
ServiceResult<SkillSummary> CreateSkill(string sessionToken, Guid characterId, string name, string diceRollDefinition, int wildDice, bool allowFumble, Guid? skillGroupId = null);
|
||||||
ServiceResult<SkillSummary> UpdateSkill(string sessionToken, Guid skillId, string name, string diceRollDefinition, int wildDice, bool allowFumble, Guid? skillGroupId = null);
|
ServiceResult<SkillSummary> UpdateSkill(string sessionToken, Guid skillId, string name, string diceRollDefinition, int wildDice, bool allowFumble, Guid? skillGroupId = null);
|
||||||
ServiceResult<bool> DeleteSkill(string sessionToken, Guid skillId);
|
ServiceResult<bool> DeleteSkill(string sessionToken, Guid skillId);
|
||||||
|
ServiceResult<CharacterSheet> GetCharacterSheet(string sessionToken, Guid characterId);
|
||||||
|
|
||||||
ServiceResult<RollResult> RollSkill(string sessionToken, Guid skillId, string visibility);
|
ServiceResult<RollResult> RollSkill(string sessionToken, Guid skillId, string visibility);
|
||||||
ServiceResult<IReadOnlyList<CampaignLogEntry>> GetCampaignLog(string sessionToken, Guid campaignId);
|
ServiceResult<IReadOnlyList<CampaignLogEntry>> GetCampaignLog(string sessionToken, Guid campaignId);
|
||||||
|
|||||||
Reference in New Issue
Block a user