using Microsoft.AspNetCore.DataProtection; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Tiku.Application; using Tiku.Application.Security; using Tiku.Infrastructure; using Tiku.Infrastructure.Bootstrap; using Tiku.Infrastructure.Persistence; var builder = Host.CreateApplicationBuilder(args); builder.Logging.AddFilter("Microsoft.EntityFrameworkCore", LogLevel.Warning); var isDevelopment = builder.Environment.IsDevelopment() || string.Equals( Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT"), Environments.Development, StringComparison.OrdinalIgnoreCase); var bootstrapPlatformAdmin = args.Contains("--bootstrap-platform-admin", StringComparer.Ordinal); var skipDevelopmentSeed = args.Contains("--skip-development-seed", StringComparer.Ordinal); PlatformAdminBootstrapOptions? bootstrapOptions = null; if (bootstrapPlatformAdmin) bootstrapOptions = new PlatformAdminBootstrapOptions( RequiredBootstrapSetting(builder.Configuration, "TIKU_BOOTSTRAP_PLATFORM_ADMIN_EMAIL"), RequiredBootstrapSetting(builder.Configuration, "TIKU_BOOTSTRAP_PLATFORM_ADMIN_PASSWORD"), builder.Configuration["TIKU_BOOTSTRAP_PLATFORM_ADMIN_NAME"]); var connectionString = builder.Configuration.GetConnectionString("Database") ?? Environment.GetEnvironmentVariable("DATABASE_URL") ?? (isDevelopment ? $"Host=localhost;Database=tiku;Username={Environment.UserName}" : throw new InvalidOperationException( "Database connection is required outside Development. Configure ConnectionStrings:Database or DATABASE_URL.")); builder.Services.AddApplication(); builder.Services.AddAuthentication(); builder.Services.AddInfrastructure( connectionString, isDevelopment && !bootstrapPlatformAdmin && !skipDevelopmentSeed ? DevelopmentPlatformAdminSeeder.Configure : null); // Resolving UserManager also activates Identity's default token providers. // Bootstrap never issues a reset token, so the migrator uses a process-local provider; // the API remains the sole owner of the persisted, certificate-protected key ring. builder.Services.AddDataProtection().UseEphemeralDataProtectionProvider(); using var host = builder.Build(); await using var scope = host.Services.CreateAsyncScope(); scope.ServiceProvider.GetRequiredService() .InitializeSystem(null, "Database migration and bootstrap"); var dbContext = scope.ServiceProvider.GetRequiredService(); await dbContext.Database.MigrateAsync(); var catalogSeeder = ActivatorUtilities.CreateInstance(scope.ServiceProvider); await catalogSeeder.SeedAsync(); var starterOfferingSeeder = ActivatorUtilities.CreateInstance(scope.ServiceProvider); await starterOfferingSeeder.SeedAsync(); if (bootstrapOptions is not null) { var bootstrapper = ActivatorUtilities.CreateInstance(scope.ServiceProvider); var result = await bootstrapper.BootstrapAsync(bootstrapOptions); Console.WriteLine( $"Platform administrator '{result.Email}' was created and must change the temporary password at first sign-in."); } static string RequiredBootstrapSetting(IConfiguration configuration, string key) { return configuration[key] is { } value && !string.IsNullOrWhiteSpace(value) ? value : throw new InvalidOperationException($"{key} is required with --bootstrap-platform-admin."); }