Refactor campaign payload loading
This commit is contained in:
@@ -31,6 +31,12 @@ internal static class CharacterEndpoints
|
||||
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) =>
|
||||
{
|
||||
var result = game.GetUsernames(context.GetRequiredSessionToken());
|
||||
|
||||
@@ -18,8 +18,8 @@
|
||||
@foreach (var entry in CampaignLog)
|
||||
{
|
||||
<li class="log-entry @LogEntryCssClass(entry)">
|
||||
<p><strong>@RollerLabel(entry)</strong> rolled <strong>@SkillLabel(entry.SkillId)</strong> with
|
||||
<strong>@CharacterLabel(entry.CharacterId)</strong></p>
|
||||
<p><strong>@RollerLabel(entry)</strong> rolled <strong>@entry.SkillName</strong> with
|
||||
<strong>@entry.CharacterName</strong></p>
|
||||
<p class="roll-total inline">@entry.Result</p>
|
||||
<RollDiceStrip Dice="entry.Dice" AriaLabel="Log roll dice"/>
|
||||
<p>@entry.Breakdown</p>
|
||||
|
||||
@@ -48,12 +48,6 @@ public partial class CampaignLogPanel
|
||||
[Parameter]
|
||||
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]
|
||||
public Func<CampaignLogEntry, string> LogEntryCssClass { get; set; } = _ => string.Empty;
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<select id="campaign-select" @onchange="CampaignSelectionChanged">
|
||||
@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>
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ public partial class CampaignManagementPanel
|
||||
IsCreatingCampaign = true;
|
||||
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;
|
||||
ShowCreateCampaignModal = false;
|
||||
@@ -72,13 +72,13 @@ public partial class CampaignManagementPanel
|
||||
private bool ShowCreateCampaignModal { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public IReadOnlyList<CampaignDetails> Campaigns { get; set; } = [];
|
||||
public IReadOnlyList<CampaignSummary> Campaigns { get; set; } = [];
|
||||
|
||||
[Parameter]
|
||||
public Guid? SelectedCampaignId { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public CampaignDetails? SelectedCampaign { get; set; }
|
||||
public CampaignRoster? SelectedCampaign { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public IReadOnlyList<RulesetDefinition> Rulesets { get; set; } = [];
|
||||
|
||||
@@ -285,7 +285,7 @@ public partial class CharacterPanel
|
||||
public bool IsCampaignDataLoading { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public CampaignDetails? SelectedCampaign { get; set; }
|
||||
public CampaignRoster? SelectedCampaign { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Guid? SelectedCharacterId { get; set; }
|
||||
|
||||
@@ -61,8 +61,6 @@
|
||||
IsCampaignDataLoading="IsCampaignDataLoading"
|
||||
CampaignLog="PlayVisibleCampaignLog"
|
||||
RollerLabel="RollerLabel"
|
||||
SkillLabel="SkillLabel"
|
||||
CharacterLabel="CharacterLabel"
|
||||
LogEntryCssClass="LogEntryCssClass"
|
||||
VisibilityLabel="VisibilityLabel"
|
||||
VisibilityBadgeCssClass="VisibilityBadgeCssClass"/>
|
||||
|
||||
@@ -143,7 +143,7 @@ public partial class Workspace : IAsyncDisposable
|
||||
|
||||
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();
|
||||
|
||||
if (Campaigns.Count == 0)
|
||||
@@ -173,6 +173,8 @@ public partial class Workspace : IAsyncDisposable
|
||||
if (!SelectedCampaignId.HasValue)
|
||||
{
|
||||
SelectedCampaign = null;
|
||||
SelectedCharacterSkills = [];
|
||||
SelectedCharacterSkillGroups = [];
|
||||
CampaignLog = [];
|
||||
SelectedCharacterId = null;
|
||||
ConnectionState = "offline";
|
||||
@@ -183,9 +185,18 @@ public partial class Workspace : IAsyncDisposable
|
||||
try
|
||||
{
|
||||
var campaignId = SelectedCampaignId.Value;
|
||||
SelectedCampaign = await ApiClient.RequestAsync<CampaignDetails>("GET", $"/api/campaigns/{campaignId}");
|
||||
CampaignLog = (await ApiClient.RequestAsync<IReadOnlyList<CampaignLogEntry>>("GET", $"/api/campaigns/{campaignId}/log")).ToList();
|
||||
SelectedCampaign = await ApiClient.RequestAsync<CampaignRoster>("GET", $"/api/campaigns/{campaignId}");
|
||||
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();
|
||||
}
|
||||
catch (ApiRequestException ex) when (ex.StatusCode == 401)
|
||||
@@ -238,6 +249,12 @@ public partial class Workspace : IAsyncDisposable
|
||||
await PersistScreenPreferenceAsync(CurrentScreen);
|
||||
await InvokeAsync(StateHasChanged);
|
||||
|
||||
if (User is not null)
|
||||
{
|
||||
await RefreshCampaignScopeAsync();
|
||||
await SyncStateEventsAsync();
|
||||
}
|
||||
|
||||
if (IsAdminScreen)
|
||||
{
|
||||
await EnsureAdminUsersLoadedAsync();
|
||||
@@ -521,6 +538,7 @@ public partial class Workspace : IAsyncDisposable
|
||||
private async Task SelectCharacterAsync(Guid characterId)
|
||||
{
|
||||
SelectedCharacterId = characterId;
|
||||
await RefreshSelectedCharacterSheetAsync();
|
||||
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 _)
|
||||
{
|
||||
await RefreshCampaignScopeAsync();
|
||||
@@ -715,7 +751,7 @@ public partial class Workspace : IAsyncDisposable
|
||||
|
||||
private async Task SyncStateEventsAsync()
|
||||
{
|
||||
if (User is null || !SelectedCampaignId.HasValue)
|
||||
if (User is null || !SelectedCampaignId.HasValue || IsAdminScreen)
|
||||
{
|
||||
await StopStateEventsAsync();
|
||||
ConnectionState = "offline";
|
||||
@@ -795,16 +831,6 @@ public partial class Workspace : IAsyncDisposable
|
||||
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)
|
||||
{
|
||||
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)
|
||||
return "GM";
|
||||
|
||||
return "Participant";
|
||||
return entry.RollerDisplayName;
|
||||
}
|
||||
|
||||
private string VisibilityLabel(CampaignLogEntry entry)
|
||||
@@ -866,6 +892,8 @@ public partial class Workspace : IAsyncDisposable
|
||||
SelectedCampaign = null;
|
||||
Campaigns = [];
|
||||
CharacterCampaignOptions = [];
|
||||
SelectedCharacterSkills = [];
|
||||
SelectedCharacterSkillGroups = [];
|
||||
CampaignLog = [];
|
||||
SelectedCharacterId = null;
|
||||
LastRoll = null;
|
||||
@@ -934,9 +962,11 @@ public partial class Workspace : IAsyncDisposable
|
||||
private UserSummary? User { get; set; }
|
||||
private Guid? ActiveCharacterId { get; set; }
|
||||
private Guid? SelectedCampaignId { get; set; }
|
||||
private CampaignDetails? SelectedCampaign { get; set; }
|
||||
private List<CampaignDetails> Campaigns { get; set; } = [];
|
||||
private CampaignRoster? SelectedCampaign { get; set; }
|
||||
private List<CampaignSummary> Campaigns { 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<RulesetDefinition> Rulesets { get; set; } = [];
|
||||
private List<AdminUserSummary> AdminUsers { get; set; } = [];
|
||||
@@ -973,12 +1003,12 @@ public partial class Workspace : IAsyncDisposable
|
||||
[Parameter]
|
||||
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 =>
|
||||
SelectedCampaign?.Characters.FirstOrDefault(c => c.Id == SelectedCharacterId);
|
||||
|
||||
private CampaignDetails? PlaySelectedCampaign
|
||||
private CampaignRoster? PlaySelectedCampaign
|
||||
{
|
||||
get
|
||||
{
|
||||
@@ -986,27 +1016,18 @@ public partial class Workspace : IAsyncDisposable
|
||||
return 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
|
||||
.Where(character => character.OwnerUserId == User.Id)
|
||||
.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.Name,
|
||||
SelectedCampaign.RulesetId,
|
||||
SelectedCampaign.Gm,
|
||||
ownedCharacters,
|
||||
ownedSkillGroups,
|
||||
ownedSkills);
|
||||
ownedCharacters);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1038,20 +1059,10 @@ public partial class Workspace : IAsyncDisposable
|
||||
private Guid? PlaySelectedCharacterId => PlaySelectedCharacter?.Id;
|
||||
|
||||
private List<SkillSummary> PlaySelectedCharacterSkills =>
|
||||
PlaySelectedCampaign is null || !PlaySelectedCharacterId.HasValue
|
||||
? []
|
||||
: PlaySelectedCampaign.Skills
|
||||
.Where(skill => skill.CharacterId == PlaySelectedCharacterId.Value)
|
||||
.OrderBy(skill => skill.Name, StringComparer.OrdinalIgnoreCase)
|
||||
.ToList();
|
||||
PlaySelectedCampaign is null || !PlaySelectedCharacterId.HasValue ? [] : SelectedCharacterSkills;
|
||||
|
||||
private List<SkillGroupSummary> PlaySelectedCharacterSkillGroups =>
|
||||
PlaySelectedCampaign is null || !PlaySelectedCharacterId.HasValue
|
||||
? []
|
||||
: PlaySelectedCampaign.SkillGroups
|
||||
.Where(group => group.CharacterId == PlaySelectedCharacterId.Value)
|
||||
.OrderBy(group => group.Name, StringComparer.OrdinalIgnoreCase)
|
||||
.ToList();
|
||||
PlaySelectedCampaign is null || !PlaySelectedCharacterId.HasValue ? [] : SelectedCharacterSkillGroups;
|
||||
|
||||
private List<CampaignLogEntry> PlayVisibleCampaignLog =>
|
||||
User is null
|
||||
@@ -1079,12 +1090,6 @@ public partial class Workspace : IAsyncDisposable
|
||||
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 IsManagementScreen => string.Equals(CurrentScreen, ScreenManagement, 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 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);
|
||||
|
||||
@@ -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 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);
|
||||
builder.Services.AddRpgRollerCore(builder.Configuration, builder.Environment);
|
||||
builder.Services.AddRazorComponents()
|
||||
.AddInteractiveServerComponents()
|
||||
.AddHubOptions(options => options.MaximumReceiveMessageSize = 256 * 1024);
|
||||
builder.Services.AddRazorComponents().AddInteractiveServerComponents();
|
||||
builder.Services.AddScoped<RpgRollerApiClient>();
|
||||
|
||||
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))
|
||||
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);
|
||||
if (ruleset is null)
|
||||
return ServiceResult<CampaignDetails>.Failure("invalid_ruleset", "Unknown ruleset.");
|
||||
return ServiceResult<CampaignSummary>.Failure("invalid_ruleset", "Unknown ruleset.");
|
||||
|
||||
lock (m_Gate)
|
||||
{
|
||||
var user = ResolveUserLocked(sessionToken);
|
||||
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
|
||||
{
|
||||
@@ -154,17 +154,17 @@ public sealed class GameService : IGameService
|
||||
|
||||
m_CampaignsById[campaign.Id] = campaign;
|
||||
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)
|
||||
{
|
||||
var user = ResolveUserLocked(sessionToken);
|
||||
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;
|
||||
if (UserHasRoleLocked(user, UserRoles.Admin))
|
||||
@@ -180,9 +180,9 @@ public sealed class GameService : IGameService
|
||||
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)
|
||||
{
|
||||
var context = ResolveContextLocked(sessionToken, campaignId);
|
||||
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 gm = m_UsersById[campaign.GmUserId];
|
||||
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));
|
||||
return ServiceResult<CampaignRoster>.Success(ToCampaignRoster(campaign));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
lock (m_Gate)
|
||||
@@ -776,7 +789,16 @@ public sealed class GameService : IGameService
|
||||
return ServiceResult<IReadOnlyList<CampaignLogEntry>>.Failure(context.Error!.Code, context.Error.Message);
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -989,18 +1011,39 @@ public sealed class GameService : IGameService
|
||||
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 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();
|
||||
var gm = m_UsersById[campaign.GmUserId];
|
||||
var characterCount = m_CharactersById.Values.Count(character => character.CampaignId == campaign.Id);
|
||||
return new(campaign.Id, campaign.Name, DiceRules.ToRulesetId(campaign.Ruleset), ToUserSummary(gm), characterCount);
|
||||
}
|
||||
|
||||
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)
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
private static CampaignLogEntry ToLogEntry(RollLogEntry entry)
|
||||
private CampaignLogEntry ToLogEntry(RollLogEntry entry)
|
||||
{
|
||||
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)
|
||||
@@ -1385,6 +1444,7 @@ public sealed class GameService : IGameService
|
||||
};
|
||||
}
|
||||
|
||||
private const int CampaignLogPageSize = 100;
|
||||
private static readonly JsonSerializerOptions DiceJsonOptions = new(JsonSerializerDefaults.Web);
|
||||
private readonly Dictionary<Guid, Campaign> m_CampaignsById = [];
|
||||
private readonly Dictionary<Guid, Character> m_CharactersById = [];
|
||||
|
||||
@@ -12,10 +12,10 @@ public interface IGameService
|
||||
UserSummary? GetUserBySession(string sessionToken);
|
||||
ServiceResult<MeResponse> GetMe(string sessionToken);
|
||||
|
||||
ServiceResult<CampaignDetails> CreateCampaign(string sessionToken, string name, string rulesetId);
|
||||
ServiceResult<IReadOnlyList<CampaignDetails>> GetCampaigns(string sessionToken);
|
||||
ServiceResult<CampaignSummary> CreateCampaign(string sessionToken, string name, string rulesetId);
|
||||
ServiceResult<IReadOnlyList<CampaignSummary>> GetCampaigns(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<IReadOnlyList<string>> GetUsernames(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> UpdateSkill(string sessionToken, Guid skillId, string name, string diceRollDefinition, int wildDice, bool allowFumble, Guid? skillGroupId = null);
|
||||
ServiceResult<bool> DeleteSkill(string sessionToken, Guid skillId);
|
||||
ServiceResult<CharacterSheet> GetCharacterSheet(string sessionToken, Guid characterId);
|
||||
|
||||
ServiceResult<RollResult> RollSkill(string sessionToken, Guid skillId, string visibility);
|
||||
ServiceResult<IReadOnlyList<CampaignLogEntry>> GetCampaignLog(string sessionToken, Guid campaignId);
|
||||
|
||||
Reference in New Issue
Block a user