work
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
using System;
|
||||
|
||||
namespace StellaOps.SbomService.Models;
|
||||
|
||||
public sealed record SbomVersionCreatedEvent(
|
||||
string SnapshotId,
|
||||
string TenantId,
|
||||
string ProjectionHash,
|
||||
string SchemaVersion,
|
||||
DateTimeOffset CreatedAtUtc);
|
||||
@@ -18,23 +18,10 @@ builder.Services.AddOptions();
|
||||
builder.Services.AddLogging();
|
||||
|
||||
// Register SBOM query services (InMemory seed; replace with Mongo-backed repository later).
|
||||
builder.Services.AddSingleton<IComponentLookupRepository>(sp =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var config = sp.GetRequiredService<IConfiguration>();
|
||||
var mongoConn = config.GetConnectionString("SbomServiceMongo") ?? "mongodb://localhost:27017";
|
||||
var mongoClient = new MongoDB.Driver.MongoClient(mongoConn);
|
||||
var databaseName = config.GetSection("SbomService")?["Database"] ?? "sbomservice";
|
||||
var database = mongoClient.GetDatabase(databaseName);
|
||||
return new MongoComponentLookupRepository(database);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Fallback for test/offline environments when Mongo driver is unavailable.
|
||||
return new InMemoryComponentLookupRepository();
|
||||
}
|
||||
});
|
||||
builder.Services.AddSingleton<IComponentLookupRepository>(_ => new InMemoryComponentLookupRepository());
|
||||
builder.Services.AddSingleton<IClock, SystemClock>();
|
||||
builder.Services.AddSingleton<ISbomEventStore, InMemorySbomEventStore>();
|
||||
builder.Services.AddSingleton<ISbomEventPublisher>(sp => sp.GetRequiredService<ISbomEventStore>());
|
||||
builder.Services.AddSingleton<ISbomQueryService, InMemorySbomQueryService>();
|
||||
|
||||
builder.Services.AddSingleton<IProjectionRepository>(sp =>
|
||||
@@ -279,6 +266,39 @@ app.MapGet("/sboms/{snapshotId}/projection", async Task<IResult> (
|
||||
});
|
||||
});
|
||||
|
||||
app.MapGet("/internal/sbom/events", async Task<IResult> (
|
||||
[FromServices] ISbomEventStore store,
|
||||
CancellationToken cancellationToken) =>
|
||||
{
|
||||
var events = await store.ListAsync(cancellationToken);
|
||||
return Results.Ok(events);
|
||||
});
|
||||
|
||||
app.MapPost("/internal/sbom/events/backfill", async Task<IResult> (
|
||||
[FromServices] IProjectionRepository repository,
|
||||
[FromServices] ISbomEventPublisher publisher,
|
||||
[FromServices] IClock clock,
|
||||
CancellationToken cancellationToken) =>
|
||||
{
|
||||
var projections = await repository.ListAsync(cancellationToken);
|
||||
var published = 0;
|
||||
foreach (var projection in projections)
|
||||
{
|
||||
var evt = new SbomVersionCreatedEvent(
|
||||
projection.SnapshotId,
|
||||
projection.TenantId,
|
||||
projection.ProjectionHash,
|
||||
projection.SchemaVersion,
|
||||
clock.UtcNow);
|
||||
if (await publisher.PublishVersionCreatedAsync(evt, cancellationToken))
|
||||
{
|
||||
published++;
|
||||
}
|
||||
}
|
||||
|
||||
return Results.Ok(new { published });
|
||||
});
|
||||
|
||||
app.Run();
|
||||
|
||||
public partial class Program;
|
||||
|
||||
@@ -57,6 +57,12 @@ internal sealed class FileProjectionRepository : IProjectionRepository
|
||||
return Task.FromResult(result);
|
||||
}
|
||||
|
||||
public Task<IReadOnlyList<SbomProjectionResult>> ListAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var list = _projections.Values.ToList();
|
||||
return Task.FromResult<IReadOnlyList<SbomProjectionResult>>(list);
|
||||
}
|
||||
|
||||
private static string ComputeHash(JsonElement element)
|
||||
{
|
||||
var json = JsonSerializer.Serialize(element, new JsonSerializerOptions { WriteIndented = false });
|
||||
|
||||
@@ -5,4 +5,5 @@ namespace StellaOps.SbomService.Repositories;
|
||||
public interface IProjectionRepository
|
||||
{
|
||||
Task<SbomProjectionResult?> GetAsync(string snapshotId, string tenantId, CancellationToken cancellationToken);
|
||||
Task<IReadOnlyList<SbomProjectionResult>> ListAsync(CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
13
src/SbomService/StellaOps.SbomService/Services/Clock.cs
Normal file
13
src/SbomService/StellaOps.SbomService/Services/Clock.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
|
||||
namespace StellaOps.SbomService.Services;
|
||||
|
||||
public interface IClock
|
||||
{
|
||||
DateTimeOffset UtcNow { get; }
|
||||
}
|
||||
|
||||
public sealed class SystemClock : IClock
|
||||
{
|
||||
public DateTimeOffset UtcNow => DateTimeOffset.UtcNow;
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Globalization;
|
||||
using StellaOps.SbomService.Models;
|
||||
using StellaOps.SbomService.Repositories;
|
||||
using StellaOps.SbomService.Models;
|
||||
using StellaOps.SbomService.Repositories;
|
||||
using StellaOps.SbomService.Services;
|
||||
|
||||
namespace StellaOps.SbomService.Services;
|
||||
|
||||
@@ -12,12 +13,20 @@ internal sealed class InMemorySbomQueryService : ISbomQueryService
|
||||
private readonly IReadOnlyList<CatalogRecord> _catalog;
|
||||
private readonly IComponentLookupRepository _componentLookupRepository;
|
||||
private readonly IProjectionRepository _projectionRepository;
|
||||
private readonly ISbomEventPublisher _eventPublisher;
|
||||
private readonly IClock _clock;
|
||||
private readonly ConcurrentDictionary<string, object> _cache = new();
|
||||
|
||||
public InMemorySbomQueryService(IComponentLookupRepository componentLookupRepository, IProjectionRepository projectionRepository)
|
||||
public InMemorySbomQueryService(
|
||||
IComponentLookupRepository componentLookupRepository,
|
||||
IProjectionRepository projectionRepository,
|
||||
ISbomEventPublisher eventPublisher,
|
||||
IClock clock)
|
||||
{
|
||||
_componentLookupRepository = componentLookupRepository;
|
||||
_projectionRepository = projectionRepository;
|
||||
_eventPublisher = eventPublisher;
|
||||
_clock = clock;
|
||||
// Deterministic seed data for early contract testing; replace with Mongo-backed implementation later.
|
||||
_paths = SeedPaths();
|
||||
_timelines = SeedTimelines();
|
||||
@@ -170,6 +179,13 @@ internal sealed class InMemorySbomQueryService : ISbomQueryService
|
||||
if (projection is not null)
|
||||
{
|
||||
_cache[cacheKey] = projection;
|
||||
var evt = new SbomVersionCreatedEvent(
|
||||
projection.SnapshotId,
|
||||
projection.TenantId,
|
||||
projection.ProjectionHash,
|
||||
projection.SchemaVersion,
|
||||
_clock.UtcNow);
|
||||
await _eventPublisher.PublishVersionCreatedAsync(evt, cancellationToken);
|
||||
}
|
||||
|
||||
return projection;
|
||||
|
||||
37
src/SbomService/StellaOps.SbomService/Services/SbomEvents.cs
Normal file
37
src/SbomService/StellaOps.SbomService/Services/SbomEvents.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using System.Collections.Concurrent;
|
||||
using StellaOps.SbomService.Models;
|
||||
|
||||
namespace StellaOps.SbomService.Services;
|
||||
|
||||
public interface ISbomEventPublisher
|
||||
{
|
||||
/// <summary>
|
||||
/// Publishes a version-created event. Returns true when the event was newly recorded; false when it was already present.
|
||||
/// </summary>
|
||||
Task<bool> PublishVersionCreatedAsync(SbomVersionCreatedEvent evt, CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
public interface ISbomEventStore : ISbomEventPublisher
|
||||
{
|
||||
Task<IReadOnlyList<SbomVersionCreatedEvent>> ListAsync(CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
public sealed class InMemorySbomEventStore : ISbomEventStore
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, SbomVersionCreatedEvent> _events = new();
|
||||
|
||||
public Task<IReadOnlyList<SbomVersionCreatedEvent>> ListAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var list = _events.Values.OrderBy(e => e.SnapshotId, StringComparer.Ordinal)
|
||||
.ThenBy(e => e.TenantId, StringComparer.Ordinal)
|
||||
.ToList();
|
||||
return Task.FromResult<IReadOnlyList<SbomVersionCreatedEvent>>(list);
|
||||
}
|
||||
|
||||
public Task<bool> PublishVersionCreatedAsync(SbomVersionCreatedEvent evt, CancellationToken cancellationToken)
|
||||
{
|
||||
var key = $"{evt.SnapshotId}|{evt.TenantId}|{evt.ProjectionHash}";
|
||||
var added = _events.TryAdd(key, evt);
|
||||
return Task.FromResult(added);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user