using Microsoft.EntityFrameworkCore; using Projection.ReadModel; var builder = WebApplication.CreateBuilder(args); var connectionString = builder.Configuration.GetConnectionString("Projection") ?? throw new InvalidOperationException("Missing connection string 'ConnectionStrings:Projection'"); builder.Services.AddProjectionReadModel(connectionString); var app = builder.Build(); // Ensure the schema exists before serving reads. The Event Subscriber migrates this shared DB too; // MigrateProjectionAsync serialises concurrent migrators with a session advisory lock so the whole // migration sequence runs exactly once (see ServiceCollectionExtensions). await app.Services.MigrateProjectionAsync(); app.MapGet("/health", () => "Healthy"); // The read side of the projection. Public-safe field filtering is tightened in S-09; for now // the minimal projection only carries id + status (bsn/naam deferred — ADR-0008). app.MapGet("/register", async (ProjectionDbContext db, CancellationToken ct) => { var rows = await db.RegisterEntries .OrderBy(r => r.Id) .Select(r => new RegisterEntryDto(r.Id, r.Status, r.Bsn, r.NaamPlaceholder)) .ToListAsync(ct); return Results.Ok(rows); }); app.MapGet("/register/{id}", async (string id, ProjectionDbContext db, CancellationToken ct) => { var row = await db.RegisterEntries .Where(r => r.Id == id) .Select(r => new RegisterEntryDto(r.Id, r.Status, r.Bsn, r.NaamPlaceholder)) .SingleOrDefaultAsync(ct); return row is null ? Results.NotFound() : Results.Ok(row); }); await app.RunAsync(); public sealed record RegisterEntryDto(string Id, string Status, string? Bsn, string? NaamPlaceholder); public partial class Program;