61991bf6cd
Adds the access foundation everything else enforces against. SharedKernel (shared access contracts, no Identity dependency for consumers): - ScopeRef/ScopeType, RoleType, Capability, AccessPolicy (role x capability matrix), ICurrentUser, IPermissionService (scope-chain evaluation). Identity module: - Member, Membership, Invitation entities; internal IdentityDbContext (schema "identity") + InitialIdentity migration; design-time factory. - JWT auth (HS256) issuing membership claims; PasswordHasher<Member>; CurrentUser (claims -> ICurrentUser) and PermissionService implementations. - Public IMemberDirectory contract for other modules to resolve member display info. - Endpoints: POST /bootstrap (first owner), /auth/login, GET /me, POST /invitations, POST /invitations/accept. Owner-only actions enforced via IPermissionService. - Web host wires UseAuthentication/UseAuthorization and string-enum JSON. Verified: build green; ArchitectureTests 8/8 (Identity references only SharedKernel); IntegrationTests 11/11 incl. a new end-to-end flow — bootstrap -> login -> /me -> invite -> accept -> login as invitee, and a Member is 403'd from inviting. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
65 lines
2.0 KiB
C#
65 lines
2.0 KiB
C#
using System.Text.Json.Serialization;
|
|
using OpenTelemetry.Trace;
|
|
using Serilog;
|
|
using TeamUp.Bootstrap;
|
|
using TeamUp.Infrastructure.Observability;
|
|
using TeamUp.Infrastructure.Persistence;
|
|
|
|
var builder = WebApplication.CreateBuilder(args);
|
|
|
|
builder.Host.UseSerilog((context, services, configuration) => configuration
|
|
.ReadFrom.Configuration(context.Configuration)
|
|
.ReadFrom.Services(services));
|
|
|
|
builder.Services.AddOpenApi();
|
|
|
|
// Bind/serialize enums as strings across the API (e.g. ScopeType "Organization", RoleType "Member").
|
|
builder.Services.ConfigureHttpJsonOptions(options =>
|
|
options.SerializerOptions.Converters.Add(new JsonStringEnumConverter()));
|
|
|
|
builder.Services.AddTeamUpObservability(
|
|
builder.Configuration,
|
|
serviceName: "teamup-web",
|
|
configureTracing: tracing => tracing.AddAspNetCoreInstrumentation());
|
|
|
|
builder.Services.AddTeamUpPersistence(builder.Configuration);
|
|
builder.Services.AddTeamUpModules(builder.Configuration);
|
|
|
|
var app = builder.Build();
|
|
|
|
// Apply migrations on startup when configured (default: in Development). EF Core takes a
|
|
// DB-wide lock, so the web and worker applying concurrently is safe.
|
|
if (app.Configuration.GetValue("Database:ApplyMigrationsOnStartup", app.Environment.IsDevelopment()))
|
|
{
|
|
await MigrationRunner.MigrateAllAsync(app.Services);
|
|
}
|
|
|
|
if (app.Environment.IsDevelopment())
|
|
{
|
|
app.MapOpenApi();
|
|
}
|
|
|
|
app.UseSerilogRequestLogging();
|
|
|
|
// Serve the built SPA from wwwroot (single deployable). UseStaticFiles (not MapStaticAssets)
|
|
// because the SPA is copied into wwwroot at publish/Docker time, after the build-time asset
|
|
// manifest is computed.
|
|
app.UseDefaultFiles();
|
|
app.UseStaticFiles();
|
|
|
|
app.UseAuthentication();
|
|
app.UseAuthorization();
|
|
|
|
app.MapHealthChecks("/health");
|
|
app.MapTeamUpModules();
|
|
|
|
// SPA deep links (client-side routing) fall back to index.html.
|
|
app.MapFallbackToFile("index.html");
|
|
|
|
app.Run();
|
|
|
|
/// <summary>Exposed so the integration tests can drive the host via WebApplicationFactory.</summary>
|
|
public partial class Program
|
|
{
|
|
}
|