using System.Net.Http.Headers; using System.Net.Http.Json; namespace JobTrackerApi.Services; public sealed record MaintenanceJobDefinition(string Id, string Title, string Description, bool Available, string? RunsUrl); public sealed class MaintenanceJobDispatcher(IConfiguration configuration, IHttpClientFactory httpClientFactory) { public const string CvBenchmarkJobId = "cv-benchmark"; public IReadOnlyList GetJobs() { var settings = ReadSettings(); return [ new( CvBenchmarkJobId, "CV extraction benchmark", "Runs the private CV corpus benchmark on the production host and publishes its report to Admin/System.", settings.IsConfigured, settings.RunsUrl) ]; } public async Task DispatchAsync(string jobId, CancellationToken cancellationToken) { if (!string.Equals(jobId, CvBenchmarkJobId, StringComparison.Ordinal)) { throw new KeyNotFoundException("Unknown maintenance job."); } var settings = ReadSettings(); if (!settings.IsConfigured) { throw new InvalidOperationException("Maintenance job dispatch is not configured."); } using var request = new HttpRequestMessage( HttpMethod.Post, $"{settings.BaseUrl}/api/v1/repos/{Uri.EscapeDataString(settings.Owner)}/{Uri.EscapeDataString(settings.Repository)}/actions/workflows/maintenance.yml/dispatches"); request.Headers.Authorization = new AuthenticationHeaderValue("token", settings.Token); request.Content = JsonContent.Create(new { @ref = "main", inputs = new Dictionary { ["job"] = jobId } }); using var response = await httpClientFactory.CreateClient("maintenance-jobs") .SendAsync(request, cancellationToken); if (!response.IsSuccessStatusCode) { throw new HttpRequestException( $"Gitea rejected the maintenance workflow dispatch with HTTP {(int)response.StatusCode}.", null, response.StatusCode); } } private Settings ReadSettings() { var baseUrl = (configuration["MaintenanceJobs:GiteaBaseUrl"] ?? "https://git.cesnimda.uk").Trim().TrimEnd('/'); var owner = (configuration["MaintenanceJobs:GiteaOwner"] ?? "cesnimda").Trim(); var repository = (configuration["MaintenanceJobs:GiteaRepository"] ?? "jobtrackingapp").Trim(); var token = (configuration["MaintenanceJobs:GiteaToken"] ?? string.Empty).Trim(); var configured = Uri.TryCreate(baseUrl, UriKind.Absolute, out var uri) && uri.Scheme == Uri.UriSchemeHttps && !string.IsNullOrWhiteSpace(owner) && !string.IsNullOrWhiteSpace(repository) && !string.IsNullOrWhiteSpace(token); var runsUrl = configured ? $"{baseUrl}/{owner}/{repository}/actions" : null; return new Settings(baseUrl, owner, repository, token, configured, runsUrl); } private sealed record Settings(string BaseUrl, string Owner, string Repository, string Token, bool IsConfigured, string? RunsUrl); }