Remove workspace session-token coupling

This commit is contained in:
2026-05-03 00:08:47 +02:00
parent 1f19bf7bfd
commit 231b0ac9a0
5 changed files with 58 additions and 76 deletions

View File

@@ -17,7 +17,7 @@
<link href="https://fonts.googleapis.com/css2?family=Noto+Color+Emoji&display=swap" rel="stylesheet">
@if (UseInteractiveApp)
{
<HeadOutlet @rendermode="InteractiveServer"/>
<HeadOutlet @rendermode="@(new InteractiveServerRenderMode(prerender: false))"/>
}
</head>
<body>
@@ -27,7 +27,7 @@
}
else
{
<Routes @rendermode="InteractiveServer"/>
<Routes @rendermode="@(new InteractiveServerRenderMode(prerender: false))"/>
}
<script src="js/rpgroller-api.js"></script>
@if (UseInteractiveApp)
@@ -92,4 +92,4 @@ else
return value.Count > 0 ? value[0] : null;
}
}
}

View File

@@ -1,81 +1,70 @@
using Microsoft.AspNetCore.WebUtilities;
using RpgRoller.Contracts;
using RpgRoller.Services;
namespace RpgRoller.Components;
public sealed class WorkspaceQueryService(IGameService gameService, WorkspaceSessionTokenAccessor sessionTokenAccessor)
public sealed class WorkspaceQueryService(RpgRollerApiClient apiClient)
{
public Task<MeResponse> GetMeAsync()
{
return Task.FromResult(GetValue(gameService.GetMe(GetRequiredSessionToken())));
return apiClient.RequestAsync<MeResponse>("GET", "/api/me");
}
public Task<IReadOnlyList<RulesetDefinition>> GetRulesetsAsync()
public async Task<IReadOnlyList<RulesetDefinition>> GetRulesetsAsync()
{
return Task.FromResult(gameService.GetRulesets());
return await apiClient.RequestAsync<RulesetDefinition[]>("GET", "/api/rulesets");
}
public Task<IReadOnlyList<CampaignSummary>> GetCampaignsAsync()
public async Task<IReadOnlyList<CampaignSummary>> GetCampaignsAsync()
{
return Task.FromResult(GetValue(gameService.GetCampaigns(GetRequiredSessionToken())));
return await apiClient.RequestAsync<CampaignSummary[]>("GET", "/api/campaigns");
}
public Task<IReadOnlyList<CampaignOption>> GetCharacterCampaignOptionsAsync()
public async Task<IReadOnlyList<CampaignOption>> GetCharacterCampaignOptionsAsync()
{
return Task.FromResult(GetValue(gameService.GetCharacterCampaignOptions(GetRequiredSessionToken())));
return await apiClient.RequestAsync<CampaignOption[]>("GET", "/api/campaigns/options");
}
public Task<CampaignRoster> GetCampaignAsync(Guid campaignId)
{
return Task.FromResult(GetValue(gameService.GetCampaign(GetRequiredSessionToken(), campaignId)));
return apiClient.RequestAsync<CampaignRoster>("GET", $"/api/campaigns/{campaignId:D}");
}
public Task<IReadOnlyList<string>> GetUsernamesAsync()
public async Task<IReadOnlyList<string>> GetUsernamesAsync()
{
return Task.FromResult(GetValue(gameService.GetUsernames(GetRequiredSessionToken())));
return await apiClient.RequestAsync<string[]>("GET", "/api/users/usernames");
}
public Task<CharacterSheet> GetCharacterSheetAsync(Guid characterId)
{
return Task.FromResult(GetValue(gameService.GetCharacterSheet(GetRequiredSessionToken(), characterId)));
return apiClient.RequestAsync<CharacterSheet>("GET", $"/api/characters/{characterId:D}/sheet");
}
public Task<IReadOnlyList<CampaignLogEntry>> GetCampaignLogAsync(Guid campaignId)
public async Task<IReadOnlyList<CampaignLogEntry>> GetCampaignLogAsync(Guid campaignId)
{
return Task.FromResult(GetValue(gameService.GetCampaignLog(GetRequiredSessionToken(), campaignId)));
return await apiClient.RequestAsync<CampaignLogEntry[]>("GET", $"/api/campaigns/{campaignId:D}/log");
}
public Task<CampaignLogPage> GetCampaignLogPageAsync(Guid campaignId, Guid? afterRollId = null, int? limit = null)
{
return Task.FromResult(GetValue(gameService.GetCampaignLogPage(GetRequiredSessionToken(), campaignId, afterRollId, limit)));
var query = new Dictionary<string, string?>();
if (afterRollId.HasValue)
query["afterRollId"] = afterRollId.Value.ToString("D");
if (limit.HasValue)
query["limit"] = limit.Value.ToString();
var path = QueryHelpers.AddQueryString($"/api/campaigns/{campaignId:D}/log/page", query);
return apiClient.RequestAsync<CampaignLogPage>("GET", path);
}
public Task<CampaignRollDetail> GetRollDetailAsync(Guid rollId)
{
return Task.FromResult(GetValue(gameService.GetRollDetail(GetRequiredSessionToken(), rollId)));
return apiClient.RequestAsync<CampaignRollDetail>("GET", $"/api/rolls/{rollId:D}");
}
public Task<IReadOnlyList<AdminUserSummary>> GetAdminUsersAsync()
public async Task<IReadOnlyList<AdminUserSummary>> GetAdminUsersAsync()
{
return Task.FromResult(GetValue(gameService.GetUsers(GetRequiredSessionToken())));
}
private string GetRequiredSessionToken()
{
return sessionTokenAccessor.GetRequiredSessionToken();
}
private static T GetValue<T>(ServiceResult<T> result)
{
if (result.Succeeded)
return result.Value!;
throw ToApiRequestException(result.Error!);
}
private static ApiRequestException ToApiRequestException(ServiceError error)
{
var statusCode = error.Code == "unauthorized" ? 401 : 400;
return new(statusCode, error.Message, error.Code);
return await apiClient.RequestAsync<AdminUserSummary[]>("GET", "/api/admin/users");
}
}

View File

@@ -1,33 +0,0 @@
using RpgRoller.Api;
namespace RpgRoller.Components;
public sealed class WorkspaceSessionTokenAccessor
{
public WorkspaceSessionTokenAccessor(IHttpContextAccessor httpContextAccessor)
{
var httpContext = httpContextAccessor.HttpContext;
if (httpContext is null)
return;
if (httpContext.Items.TryGetValue(SessionTokenItemKey, out var storedToken) && storedToken is string sessionToken && !string.IsNullOrWhiteSpace(sessionToken))
{
m_SessionToken = sessionToken;
return;
}
if (httpContext.TryReadSessionTokenFromCookie(out sessionToken))
m_SessionToken = sessionToken;
}
public string GetRequiredSessionToken()
{
if (!string.IsNullOrWhiteSpace(m_SessionToken))
return m_SessionToken;
throw new ApiRequestException(401, "You must be logged in.");
}
private const string SessionTokenItemKey = "__rpgroller.session-token";
private readonly string? m_SessionToken;
}

View File

@@ -13,9 +13,7 @@ builder.Services.AddResponseCompression(options =>
options.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(["application/json"]);
});
builder.Services.ConfigureHttpJsonOptions(options => RpgRollerJson.Configure(options.SerializerOptions));
builder.Services.AddHttpContextAccessor();
builder.Services.AddScoped<RpgRollerApiClient>();
builder.Services.AddScoped<WorkspaceSessionTokenAccessor>();
builder.Services.AddScoped<WorkspaceQueryService>();
var app = builder.Build();