using System.Net; using System.Net.Http.Headers; using System.Net.Http.Json; using DeepDrftModels.DTOs; using DeepDrftModels.Enums; using Models.Common; using NetBlocks.Models; namespace DeepDrftManager.Services; /// /// HTTP client over DeepDrftAPI's api/release family for CMS release operations. Mirrors /// : the Manager is InteractiveServer-only with no in-process data /// layer, so every read and write is a network call. The ApiKey is baked into the /// DeepDrft.Content.Cms named client's default headers; the unauthenticated reads still go /// through it (the extra header is harmless on public endpoints). /// public class CmsReleaseService : ICmsReleaseService { private const string ContentCmsClientName = "DeepDrft.Content.Cms"; private readonly IHttpClientFactory _httpClientFactory; private readonly ILogger _logger; public CmsReleaseService( IHttpClientFactory httpClientFactory, ILogger logger) { _httpClientFactory = httpClientFactory; _logger = logger; } public async Task>> GetPagedAsync( ReleaseMedium? medium, int page, int pageSize, string? sortColumn, bool sortDescending, CancellationToken ct = default) { var client = _httpClientFactory.CreateClient(ContentCmsClientName); var query = $"api/release?page={page}&pageSize={pageSize}&sortDescending={sortDescending}"; if (medium is { } m) { query += $"&medium={Uri.EscapeDataString(m.ToString())}"; } if (!string.IsNullOrWhiteSpace(sortColumn)) { query += $"&sortColumn={Uri.EscapeDataString(sortColumn)}"; } HttpResponseMessage response; try { response = await client.GetAsync(query, ct); } catch (Exception ex) { _logger.LogError(ex, "Content API call failed for release page (medium {Medium})", medium); return ResultContainer>.CreateFailResult("Content API is unreachable."); } using (response) { if (!response.IsSuccessStatusCode) { _logger.LogError("Content API release page failed: {Status}", (int)response.StatusCode); return ResultContainer>.CreateFailResult("Failed to load releases."); } PagedResult? paged; try { paged = await response.Content.ReadFromJsonAsync>(ct); } catch (Exception ex) { _logger.LogError(ex, "Failed to deserialize release page from Content API response"); return ResultContainer>.CreateFailResult("Content API returned an unexpected response."); } if (paged is null) { _logger.LogError("Content API returned a null release page"); return ResultContainer>.CreateFailResult("Content API returned an empty response."); } return ResultContainer>.CreatePassResult(paged); } } public async Task> GetByIdAsync(long id, CancellationToken ct = default) { var client = _httpClientFactory.CreateClient(ContentCmsClientName); HttpResponseMessage response; try { response = await client.GetAsync($"api/release/{id}", ct); } catch (Exception ex) { _logger.LogError(ex, "Content API call failed for release {ReleaseId}", id); return ResultContainer.CreateFailResult("Content API is unreachable."); } using (response) { if (response.StatusCode == HttpStatusCode.NotFound) { return ResultContainer.CreatePassResult(null); } if (!response.IsSuccessStatusCode) { _logger.LogError("Content API release lookup failed for {ReleaseId}: {Status}", id, (int)response.StatusCode); return ResultContainer.CreateFailResult("Failed to load release."); } ReleaseDto? release; try { release = await response.Content.ReadFromJsonAsync(ct); } catch (Exception ex) { _logger.LogError(ex, "Failed to deserialize ReleaseDto from Content API response"); return ResultContainer.CreateFailResult("Content API returned an unexpected response."); } return ResultContainer.CreatePassResult(release); } } public async Task UploadSessionHeroImageAsync( long releaseId, Stream imageStream, string fileName, string contentType, CancellationToken ct = default) { using var multipart = new MultipartFormDataContent(); var imageContent = new StreamContent(imageStream); imageContent.Headers.ContentType = new MediaTypeHeaderValue( string.IsNullOrWhiteSpace(contentType) ? "application/octet-stream" : contentType); // Field name "image" matches the controller's [FromForm] IFormFile image parameter. multipart.Add(imageContent, "image", fileName); var client = _httpClientFactory.CreateClient(ContentCmsClientName); using var request = new HttpRequestMessage(HttpMethod.Post, $"api/release/{releaseId}/session/hero-image") { Content = multipart }; HttpResponseMessage response; try { response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, ct); } catch (Exception ex) { _logger.LogError(ex, "Content API call failed for hero-image upload of release {ReleaseId}", releaseId); return Result.CreateFailResult("Content API is unreachable."); } using (response) { if (response.IsSuccessStatusCode) { return Result.CreatePassResult(); } if (response.StatusCode == HttpStatusCode.NotFound) { return Result.CreateFailResult("Release not found."); } var body = await response.Content.ReadAsStringAsync(ct); var statusCode = (int)response.StatusCode; if (statusCode >= 500) { _logger.LogError("Content API returned {Status} for hero-image upload of release {ReleaseId}: {Body}", statusCode, releaseId, body); return Result.CreateFailResult("Hero image upload failed on the content server."); } // 4xx: body is user-friendly validation text from DeepDrftAPI — relay as-is. _logger.LogWarning("Content API rejected hero-image upload for release {ReleaseId}: {Status} {Body}", releaseId, statusCode, body); return Result.CreateFailResult( string.IsNullOrWhiteSpace(body) ? $"Hero image upload rejected ({statusCode})." : body); } } public async Task GenerateMixWaveformAsync(long releaseId, CancellationToken ct = default) { var client = _httpClientFactory.CreateClient(ContentCmsClientName); HttpResponseMessage response; try { response = await client.PostAsync($"api/release/{releaseId}/mix/waveform", null, ct); } catch (Exception ex) { _logger.LogError(ex, "Content API call failed for mix waveform generation of release {ReleaseId}", releaseId); return Result.CreateFailResult("Content API is unreachable."); } using (response) { if (response.IsSuccessStatusCode) { return Result.CreatePassResult(); } if (response.StatusCode == HttpStatusCode.NotFound) { return Result.CreateFailResult("Mix audio not found."); } var body = await response.Content.ReadAsStringAsync(ct); _logger.LogError("Content API mix waveform generation failed for release {ReleaseId}: {Status} {Body}", releaseId, (int)response.StatusCode, body); return Result.CreateFailResult("Failed to generate mix waveform."); } } }