refactor(manager): replace internal CMS HTTP layer with direct CmsTrackService calls

This commit is contained in:
Daniel Harvey
2026-05-24 20:46:22 -04:00
parent 058e4ca196
commit 428359b241
10 changed files with 301 additions and 509 deletions
@@ -1,10 +1,8 @@
@page "/cms/tracks/{Id:int}"
@using System.Net.Http.Headers
@using System.Net.Http.Json
@using DeepDrftManager.Services
@attribute [HierarchicalRoleAuthorize([SystemRoleConstants.Admin])]
@inject ITrackService TrackService
@inject IHttpClientFactory HttpClientFactory
@inject IAuthSession AuthSession
@inject ICmsTrackService CmsTrackService
@inject ISnackbar Snackbar
@inject IDialogService DialogService
@inject NavigationManager Nav
@@ -126,27 +124,32 @@
_busy = true;
try
{
var http = HttpClientFactory.CreateClient("DeepDrft.API");
await AttachBearerAsync(http);
var payload = new
// Re-fetch under the current scope so we mutate the DB-authoritative entity, not
// the copy loaded at OnInitialized. Metadata-only update — EntryKey is immutable.
var lookup = await TrackService.GetById(Id);
if (!lookup.Success || lookup.Value is null)
{
TrackName = _form.TrackName,
Artist = _form.Artist,
Album = string.IsNullOrWhiteSpace(_form.Album) ? null : _form.Album,
Genre = string.IsNullOrWhiteSpace(_form.Genre) ? null : _form.Genre,
ReleaseDate = _form.ReleaseDate is { } d ? DateOnly.FromDateTime(d) : (DateOnly?)null
};
Snackbar.Add("Save failed — track could not be loaded.", Severity.Error);
return;
}
var response = await http.PutAsJsonAsync($"api/cms/track/{Id}", payload);
if (response.IsSuccessStatusCode)
var track = lookup.Value;
track.TrackName = _form.TrackName;
track.Artist = _form.Artist;
track.Album = string.IsNullOrWhiteSpace(_form.Album) ? null : _form.Album;
track.Genre = string.IsNullOrWhiteSpace(_form.Genre) ? null : _form.Genre;
track.ReleaseDate = _form.ReleaseDate is { } d ? DateOnly.FromDateTime(d) : null;
var updated = await TrackService.Update(track);
if (updated.Success)
{
Snackbar.Add("Track updated.", Severity.Success);
await LoadAsync();
}
else
{
Snackbar.Add($"Save failed: {(int)response.StatusCode} {response.ReasonPhrase}", Severity.Error);
var error = updated.Messages.FirstOrDefault()?.Message ?? "Unknown error";
Snackbar.Add($"Save failed: {error}", Severity.Error);
}
}
catch (Exception ex)
@@ -160,7 +163,6 @@
}
}
// DELETE api/cms/track/{Id} is handled by CmsDeleteController (T3 branch).
private async Task ConfirmDelete()
{
if (_track is null) return;
@@ -176,18 +178,16 @@
_busy = true;
try
{
var http = HttpClientFactory.CreateClient("DeepDrft.API");
await AttachBearerAsync(http);
var response = await http.DeleteAsync($"api/cms/track/{Id}");
if (response.IsSuccessStatusCode)
var result = await CmsTrackService.DeleteTrackAsync(Id);
if (result.Success)
{
Snackbar.Add("Track deleted.", Severity.Success);
Nav.NavigateTo("/cms/tracks");
}
else
{
Snackbar.Add($"Delete failed: {(int)response.StatusCode} {response.ReasonPhrase}", Severity.Error);
var error = result.Messages.FirstOrDefault()?.Message ?? "Unknown error";
Snackbar.Add($"Delete failed: {error}", Severity.Error);
_busy = false;
}
}
@@ -199,15 +199,6 @@
}
}
private async Task AttachBearerAsync(HttpClient http)
{
var token = await AuthSession.GetValidTokenAsync();
if (!string.IsNullOrEmpty(token))
{
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
}
}
private sealed class TrackEditForm
{
public string TrackName { get; set; } = string.Empty;
@@ -1,10 +1,9 @@
@page "/cms/tracks"
@using System.Net
@using System.Net.Http.Headers
@using DeepDrftManager.Services
@attribute [HierarchicalRoleAuthorize([SystemRoleConstants.Admin])]
@inject ITrackService TrackService
@inject IHttpClientFactory HttpClientFactory
@inject IAuthSession AuthSession
@inject ICmsTrackService CmsTrackService
@inject IDialogService DialogService
@inject ISnackbar Snackbar
@inject ILogger<TrackList> Logger
@@ -113,20 +112,16 @@
try
{
var client = HttpClientFactory.CreateClient("DeepDrft.API");
var token = await AuthSession.GetValidTokenAsync();
if (!string.IsNullOrEmpty(token))
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
var response = await client.DeleteAsync($"api/cms/track/{track.Id}");
if (response.IsSuccessStatusCode)
var result = await CmsTrackService.DeleteTrackAsync(track.Id);
if (result.Success)
{
Snackbar.Add($"Deleted '{track.TrackName}'.", Severity.Success);
if (_table is not null) await _table.ReloadServerData();
}
else
{
Snackbar.Add($"Delete failed ({(int)response.StatusCode} {response.ReasonPhrase}).", Severity.Error);
var error = result.Messages.FirstOrDefault()?.Message ?? "Unknown error";
Snackbar.Add($"Delete failed: {error}", Severity.Error);
}
}
catch (Exception ex)
@@ -1,12 +1,13 @@
@page "/cms/tracks/new"
@using System.Net.Http.Headers
@using System.Security.Claims
@using DeepDrftManager.Services
@attribute [HierarchicalRoleAuthorize([SystemRoleConstants.Admin])]
@inject IHttpClientFactory HttpClientFactory
@inject ICmsTrackService CmsTrackService
@inject AuthenticationStateProvider AuthStateProvider
@inject NavigationManager Navigation
@inject ISnackbar Snackbar
@inject ILogger<TrackNew> Logger
@inject IAuthSession AuthSession
<PageTitle>Add Track — DeepDrft CMS</PageTitle>
@@ -61,8 +62,8 @@
</MudContainer>
@code {
// 1 GB ceiling matches the proxy controller's RequestSizeLimit; the actual streaming
// path means the limit caps the request, not in-memory buffering.
// 1 GB ceiling matches DeepDrftContent's per-request limit on api/track/upload; the
// streaming path means the limit caps the request, not in-memory buffering.
private const long MaxUploadBytes = 1_073_741_824L;
private IBrowserFile? _selectedFile;
@@ -115,41 +116,46 @@
return;
}
var authState = await AuthStateProvider.GetAuthenticationStateAsync();
var userIdValue = authState.User.FindFirstValue(ClaimTypes.NameIdentifier);
if (!long.TryParse(userIdValue, out var createdByUserId))
{
// The page is gated by [HierarchicalRoleAuthorize(Admin)], so a missing or
// unparseable id here is a configuration bug, not normal client state.
Logger.LogError("Authenticated user has no parseable NameIdentifier claim: {Value}", userIdValue);
_errorMessage = "Your session is missing a valid identifier. Please sign in again.";
return;
}
_isUploading = true;
try
{
using var multipart = new MultipartFormDataContent();
// OpenReadStream streams chunks from the browser via the SignalR circuit;
// wrapping in StreamContent avoids materialising the whole file in memory
// before the proxy controller receives it.
// OpenReadStream streams chunks from the browser via the SignalR circuit; the
// service wraps it in StreamContent so the whole file is never materialised in
// memory before DeepDrftContent receives it.
await using var fileStream = _selectedFile.OpenReadStream(MaxUploadBytes);
var fileContent = new StreamContent(fileStream);
fileContent.Headers.ContentType = new MediaTypeHeaderValue(
string.IsNullOrWhiteSpace(_selectedFile.ContentType) ? "audio/wav" : _selectedFile.ContentType);
multipart.Add(fileContent, "wav", _selectedFile.Name);
multipart.Add(new StringContent(_trackName), "trackName");
multipart.Add(new StringContent(_artist), "artist");
if (!string.IsNullOrWhiteSpace(_album)) multipart.Add(new StringContent(_album), "album");
if (!string.IsNullOrWhiteSpace(_genre)) multipart.Add(new StringContent(_genre), "genre");
if (!string.IsNullOrWhiteSpace(_releaseDate)) multipart.Add(new StringContent(_releaseDate), "releaseDate");
var client = HttpClientFactory.CreateClient("DeepDrft.API");
await AttachBearerAsync(client);
using var response = await client.PostAsync("api/cms/track", multipart);
var result = await CmsTrackService.UploadTrackAsync(
fileStream,
_selectedFile.Name,
_selectedFile.ContentType,
_trackName,
_artist,
string.IsNullOrWhiteSpace(_album) ? null : _album,
string.IsNullOrWhiteSpace(_genre) ? null : _genre,
string.IsNullOrWhiteSpace(_releaseDate) ? null : _releaseDate,
createdByUserId);
if (response.IsSuccessStatusCode)
if (result.Success)
{
Snackbar.Add($"Uploaded '{_trackName}'.", Severity.Success);
Navigation.NavigateTo("/cms/tracks");
return;
}
var body = await response.Content.ReadAsStringAsync();
_errorMessage = string.IsNullOrWhiteSpace(body)
? $"Upload failed ({(int)response.StatusCode})."
: $"Upload failed ({(int)response.StatusCode}): {body}";
Logger.LogWarning("CMS upload rejected: {Status} {Body}", (int)response.StatusCode, body);
var error = result.Messages.FirstOrDefault()?.Message ?? "Unknown error";
_errorMessage = $"Upload failed: {error}";
Logger.LogWarning("CMS upload rejected: {Error}", error);
}
catch (Exception ex)
{
@@ -167,15 +173,6 @@
Navigation.NavigateTo("/cms/tracks");
}
private async Task AttachBearerAsync(HttpClient http)
{
var token = await AuthSession.GetValidTokenAsync();
if (!string.IsNullOrEmpty(token))
{
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
}
}
private static string FormatBytes(long bytes)
{
const long KB = 1024;