Files
git.stella-ops.org/src/Findings/StellaOps.Findings.Ledger.WebService/Services/FindingSummaryService.cs
2026-02-16 07:32:38 +02:00

106 lines
3.5 KiB
C#

using StellaOps.Findings.Ledger.WebService.Contracts;
namespace StellaOps.Findings.Ledger.WebService.Services;
public interface IFindingSummaryService
{
Task<FindingSummary?> GetSummaryAsync(Guid findingId, CancellationToken ct);
Task<FindingSummaryPage> GetSummariesAsync(FindingSummaryFilter filter, CancellationToken ct);
}
public sealed class FindingSummaryService : IFindingSummaryService
{
private readonly IFindingSummaryBuilder _builder;
private readonly IFindingRepository _repository;
public FindingSummaryService(
IFindingSummaryBuilder builder,
IFindingRepository repository)
{
_builder = builder;
_repository = repository;
}
public async Task<FindingSummary?> GetSummaryAsync(Guid findingId, CancellationToken ct)
{
var finding = await _repository.GetByIdAsync(findingId, ct);
if (finding is null)
return null;
return _builder.Build(finding);
}
public async Task<FindingSummaryPage> GetSummariesAsync(FindingSummaryFilter filter, CancellationToken ct)
{
var (findings, totalCount) = await _repository.GetPagedAsync(
page: filter.Page,
pageSize: filter.PageSize,
status: filter.Status,
severity: filter.Severity,
minConfidence: filter.MinConfidence,
ct);
var summaries = findings.Select(f => _builder.Build(f)).ToList();
var sorted = ApplySort(summaries, filter.SortBy, filter.SortDirection);
return new FindingSummaryPage
{
Items = sorted,
TotalCount = totalCount,
Page = filter.Page,
PageSize = filter.PageSize
};
}
private static IReadOnlyList<FindingSummary> ApplySort(
List<FindingSummary> summaries,
string? sortBy,
string sortDirection)
{
if (string.IsNullOrEmpty(sortBy))
return summaries;
var descending = string.Equals(sortDirection, "desc", StringComparison.OrdinalIgnoreCase);
IEnumerable<FindingSummary> ordered = sortBy.ToLowerInvariant() switch
{
"cvss" => descending
? summaries.OrderByDescending(s => s.CvssScore ?? 0m)
: summaries.OrderBy(s => s.CvssScore ?? 0m),
"severity" => descending
? summaries.OrderByDescending(s => s.Severity)
: summaries.OrderBy(s => s.Severity),
"status" => descending
? summaries.OrderByDescending(s => s.Status)
: summaries.OrderBy(s => s.Status),
"component" => descending
? summaries.OrderByDescending(s => s.Component)
: summaries.OrderBy(s => s.Component),
"firstseen" => descending
? summaries.OrderByDescending(s => s.FirstSeen)
: summaries.OrderBy(s => s.FirstSeen),
"lastupdated" => descending
? summaries.OrderByDescending(s => s.LastUpdated)
: summaries.OrderBy(s => s.LastUpdated),
_ => summaries
};
return ordered.ToList();
}
}
/// <summary>
/// Repository for finding data access.
/// </summary>
public interface IFindingRepository
{
Task<FindingData?> GetByIdAsync(Guid id, CancellationToken ct);
Task<(IReadOnlyList<FindingData> findings, int totalCount)> GetPagedAsync(
int page,
int pageSize,
string? status,
string? severity,
decimal? minConfidence,
CancellationToken ct);
}