Fix campaign owner labels to always use display names

This commit is contained in:
2026-02-26 17:42:44 +01:00
parent 0b30b04283
commit 6f94b1ba95
5 changed files with 30 additions and 5 deletions

View File

@@ -53,6 +53,7 @@ Gameplay capabilities now include:
- Character owner selection in edit modal backed by existing-username dropdown data - Character owner selection in edit modal backed by existing-username dropdown data
- Role-aware authorization with admin role support (including admin user/role management) - Role-aware authorization with admin role support (including admin user/role management)
- Campaign deletion by campaign owner or admin (unlinks characters from the campaign and clears campaign log entries) - Campaign deletion by campaign owner or admin (unlinks characters from the campaign and clears campaign log entries)
- Campaign management owner labels use account display names (no GUID fallback rendering)
## Prerequisites ## Prerequisites

View File

@@ -18,6 +18,7 @@ public sealed class CampaignApiTests : ApiTestBase
var campaign = await PostAsync<CreateCampaignRequest, CampaignDetails>(gmClient, "/api/campaigns", new("Alpha Campaign", "dnd5e")); var campaign = await PostAsync<CreateCampaignRequest, CampaignDetails>(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);
var activateResponse = await gmClient.PostAsync($"/api/characters/{gmCharacter.Id}/activate", null); var activateResponse = await gmClient.PostAsync($"/api/characters/{gmCharacter.Id}/activate", null);
Assert.Equal(HttpStatusCode.OK, activateResponse.StatusCode); Assert.Equal(HttpStatusCode.OK, activateResponse.StatusCode);
@@ -39,10 +40,12 @@ public sealed class CampaignApiTests : ApiTestBase
var details = await GetAsync<CampaignDetails>(gmClient, $"/api/campaigns/{campaign.Id}"); var details = await GetAsync<CampaignDetails>(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);
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);
var otherCampaign = await PostAsync<CreateCampaignRequest, CampaignDetails>(gmClient, "/api/campaigns", new("Beta Campaign", "d6")); var otherCampaign = await PostAsync<CreateCampaignRequest, CampaignDetails>(gmClient, "/api/campaigns", new("Beta Campaign", "d6"));
@@ -96,6 +99,11 @@ public sealed class CampaignApiTests : ApiTestBase
var transferResult = await PutAsync<UpdateCharacterRequest, CharacterSummary>(gmClient, $"/api/characters/{character.Id}", new("Grouped Hero", campaign.Id, "receiver2")); var transferResult = await PutAsync<UpdateCharacterRequest, CharacterSummary>(gmClient, $"/api/characters/{character.Id}", new("Grouped Hero", campaign.Id, "receiver2"));
Assert.Equal("Grouped Hero", transferResult.Name); Assert.Equal("Grouped Hero", transferResult.Name);
Assert.Equal("Receiver", transferResult.OwnerDisplayName);
var gmCampaignView = await GetAsync<CampaignDetails>(gmClient, $"/api/campaigns/{campaign.Id}");
var gmViewedCharacter = Assert.Single(gmCampaignView.Characters, c => c.Id == character.Id);
Assert.Equal("Receiver", gmViewedCharacter.OwnerDisplayName);
var ownerActivate = await ownerClient.PostAsync($"/api/characters/{character.Id}/activate", null); var ownerActivate = await ownerClient.PostAsync($"/api/characters/{character.Id}/activate", null);
Assert.Equal(HttpStatusCode.BadRequest, ownerActivate.StatusCode); Assert.Equal(HttpStatusCode.BadRequest, ownerActivate.StatusCode);

View File

@@ -590,10 +590,18 @@ public partial class Workspace : IAsyncDisposable
if (User is not null && ownerUserId == User.Id) if (User is not null && ownerUserId == User.Id)
return "You"; return "You";
if (SelectedCampaign is not null && ownerUserId == SelectedCampaign.Gm.Id) if (SelectedCampaign is null)
return "Unknown owner";
if (ownerUserId == SelectedCampaign.Gm.Id)
return $"{SelectedCampaign.Gm.DisplayName} (GM)"; return $"{SelectedCampaign.Gm.DisplayName} (GM)";
return ownerUserId.ToString("N")[..8]; var ownerDisplayName = SelectedCampaign.Characters
.Where(character => character.OwnerUserId == ownerUserId)
.Select(character => character.OwnerDisplayName)
.FirstOrDefault(displayName => !string.IsNullOrWhiteSpace(displayName));
return string.IsNullOrWhiteSpace(ownerDisplayName) ? "Unknown owner" : ownerDisplayName;
} }
private string CharacterLabel(Guid characterId) private string CharacterLabel(Guid characterId)

View File

@@ -26,7 +26,7 @@ public sealed record CreateCharacterRequest(string Name, Guid CampaignId);
public sealed record UpdateCharacterRequest(string Name, Guid CampaignId, string? OwnerUsername = null); public sealed record UpdateCharacterRequest(string Name, Guid CampaignId, string? OwnerUsername = null);
public sealed record CharacterSummary(Guid Id, string Name, Guid OwnerUserId, Guid? CampaignId); public sealed record CharacterSummary(Guid Id, string Name, Guid OwnerUserId, Guid? CampaignId, string OwnerDisplayName);
public sealed record CreateSkillRequest(string Name, string DiceRollDefinition, int WildDice, bool AllowFumble, Guid? SkillGroupId = null); public sealed record CreateSkillRequest(string Name, string DiceRollDefinition, int WildDice, bool AllowFumble, Guid? SkillGroupId = null);

View File

@@ -948,9 +948,10 @@ public sealed class GameService : IGameService
} }
} }
private static CharacterSummary ToCharacterSummary(Character character) private CharacterSummary ToCharacterSummary(Character character)
{ {
return new(character.Id, character.Name, character.OwnerUserId, character.CampaignId); var ownerDisplayName = ResolveOwnerDisplayName(character.OwnerUserId);
return new(character.Id, character.Name, character.OwnerUserId, character.CampaignId, ownerDisplayName);
} }
private static SkillGroupSummary ToSkillGroupSummary(SkillGroup skillGroup) private static SkillGroupSummary ToSkillGroupSummary(SkillGroup skillGroup)
@@ -980,6 +981,13 @@ public sealed class GameService : IGameService
return JsonSerializer.Serialize(dice, DiceJsonOptions); return JsonSerializer.Serialize(dice, DiceJsonOptions);
} }
private string ResolveOwnerDisplayName(Guid ownerUserId)
{
return m_UsersById.TryGetValue(ownerUserId, out var owner) && !string.IsNullOrWhiteSpace(owner.DisplayName)
? owner.DisplayName
: "Unknown owner";
}
private static IReadOnlyList<RollDieResult> DeserializeDice(string serializedDice) private static IReadOnlyList<RollDieResult> DeserializeDice(string serializedDice)
{ {
if (string.IsNullOrWhiteSpace(serializedDice)) if (string.IsNullOrWhiteSpace(serializedDice))