using System.Net.Http.Headers; using System.Text; using System.Text.Json; using ForgejoReleaseSync.DTOs.Forgejo; using Microsoft.Extensions.Logging; namespace ForgejoReleaseSync.Services; public class ForgejoService(IHttpClientFactory httpClientFactory, ILogger logger) { public async Task CreateReleaseAsync( string owner, string repo, string body, string name, string tagName, CancellationToken token = default) { try { var dto = new ForgejoCreateReleaseDto(body, false, false, name, false, tagName, ""); var json = JsonSerializer.Serialize(dto, AppJsonSerializerContext.Default.ForgejoCreateReleaseDto); var httpClient = httpClientFactory.CreateClient("forgejo"); using var request = new HttpRequestMessage( HttpMethod.Post, $"api/v1/repos/{owner}/{repo}/releases") { Content = new StringContent(json, Encoding.UTF8, "application/json") }; using var response = await httpClient.SendAsync(request, token); response.EnsureSuccessStatusCode(); await using var stream = await response.Content.ReadAsStreamAsync(token); var created = await JsonSerializer.DeserializeAsync( stream, AppJsonSerializerContext.Default.ForgejoReleaseResponseDto, token); return created ?? throw new InvalidOperationException("Forgejo returned an empty release response"); } catch (Exception ex) { logger.LogError(ex, "Failed to create Forgejo release {Tag} for {Owner}/{Repo}", tagName, owner, repo); throw; } } public async Task> FetchRepoReleasesAsync( string owner, string repo, CancellationToken token = default) { try { var httpClient = httpClientFactory.CreateClient("forgejo"); using var response = await httpClient.GetAsync( $"api/v1/repos/{owner}/{repo}/releases?per_page=3&page=1", token); response.EnsureSuccessStatusCode(); await using var stream = await response.Content.ReadAsStreamAsync(token); var releases = await JsonSerializer.DeserializeAsync( stream, AppJsonSerializerContext.Default.ListForgejoReleaseResponseDto, token); return releases ?? []; } catch (Exception ex) { logger.LogError(ex, "Failed to fetch Forgejo releases for {Owner}/{Repo}", owner, repo); throw; } } public async Task UploadReleaseAssetAsync( string owner, string repo, long releaseId, string filePath, CancellationToken token = default) { try { var httpClient = httpClientFactory.CreateClient("forgejo"); await using var fileStream = new FileStream( filePath, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, useAsync: true); using var fileContent = new StreamContent(fileStream); fileContent.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream"); using var formData = new MultipartFormDataContent(); formData.Add(fileContent, "attachment", Path.GetFileName(filePath)); using var request = new HttpRequestMessage( HttpMethod.Post, $"api/v1/repos/{owner}/{repo}/releases/{releaseId}/assets") { Content = formData }; using var response = await httpClient.SendAsync( request, HttpCompletionOption.ResponseHeadersRead, token); response.EnsureSuccessStatusCode(); } catch (Exception ex) { logger.LogError(ex, "Failed to upload asset {Path} to {Owner}/{Repo}", filePath, owner, repo); throw; } } }