@page "/cms/tracks/new" @using System.Net.Http.Headers @attribute [HierarchicalRoleAuthorize([SystemRoleConstants.Admin])] @inject IHttpClientFactory HttpClientFactory @inject NavigationManager Navigation @inject ISnackbar Snackbar @inject ILogger Logger @inject IAuthSession AuthSession Add Track — DeepDrft CMS Add Track WAV file @if (_selectedFile is not null) { Selected: @_selectedFile.Name (@FormatBytes(_selectedFile.Size)) } @if (!string.IsNullOrEmpty(_errorMessage)) { @_errorMessage } Cancel @if (_isUploading) { Uploading… } else { Upload } @code { // 1 GB ceiling matches the proxy controller's RequestSizeLimit; the actual streaming // path means the limit caps the request, not in-memory buffering. private const long MaxUploadBytes = 1_073_741_824L; private IBrowserFile? _selectedFile; private string _trackName = string.Empty; private string _artist = string.Empty; private string _album = string.Empty; private string _genre = string.Empty; private string _releaseDate = string.Empty; private string? _errorMessage; private bool _isUploading; private void OnFileSelected(InputFileChangeEventArgs e) { _selectedFile = e.File; _errorMessage = null; } private async Task SubmitAsync() { _errorMessage = null; if (_selectedFile is null) { _errorMessage = "Please select a WAV file."; return; } if (!_selectedFile.Name.EndsWith(".wav", StringComparison.OrdinalIgnoreCase)) { _errorMessage = "Selected file must be a .wav file."; return; } if (string.IsNullOrWhiteSpace(_trackName)) { _errorMessage = "Track Name is required."; return; } if (string.IsNullOrWhiteSpace(_artist)) { _errorMessage = "Artist is required."; return; } if (!string.IsNullOrWhiteSpace(_releaseDate) && !DateOnly.TryParseExact(_releaseDate, "yyyy-MM-dd", out _)) { _errorMessage = "Release Date must be in YYYY-MM-DD format."; 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. 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); if (response.IsSuccessStatusCode) { 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); } catch (Exception ex) { Logger.LogError(ex, "Upload failed in TrackNew"); _errorMessage = "Upload failed. Please try again."; } finally { _isUploading = false; } } private void Cancel() { 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; const long MB = KB * 1024; const long GB = MB * 1024; if (bytes >= GB) return $"{bytes / (double)GB:F2} GB"; if (bytes >= MB) return $"{bytes / (double)MB:F2} MB"; if (bytes >= KB) return $"{bytes / (double)KB:F2} KB"; return $"{bytes} bytes"; } }