From 77c34077926173e95456a1b41845bb32bbfe6aec Mon Sep 17 00:00:00 2001 From: Aaron Po Date: Sat, 22 Aug 2026 02:06:06 -0400 Subject: [PATCH] Migrate Database.Seed to UserManager --- web/backend/API/API.Core/Dockerfile | 6 +- web/backend/API/API.Specs/Dockerfile | 6 +- web/backend/Database/Database.Seed/Program.cs | 56 +++++++++---------- web/backend/Dockerfile.tests | 4 +- 4 files changed, 36 insertions(+), 36 deletions(-) diff --git a/web/backend/API/API.Core/Dockerfile b/web/backend/API/API.Core/Dockerfile index 28ac9b4d..c2e028ea 100644 --- a/web/backend/API/API.Core/Dockerfile +++ b/web/backend/API/API.Core/Dockerfile @@ -12,9 +12,9 @@ WORKDIR /src # Copy the solution file and every project file first (preserving their real relative paths, since # ProjectReference paths are resolved relative to the referencing .csproj's location), so the # `dotnet restore` layer stays cached as long as no project file or package reference changes, -# independent of source-only edits. Restoring against Core.slnx avoids hand-maintaining a per-project +# independent of source-only edits. Restoring against Biergarten.API.slnx avoids hand-maintaining a per-project # dependency list here that silently drifts out of sync as ProjectReferences change. -COPY ["Core.slnx", "./"] +COPY ["Biergarten.API.slnx", "./"] COPY ["API/API.Core/API.Core.csproj", "API/API.Core/"] COPY ["API/API.Specs/API.Specs.csproj", "API/API.Specs/"] COPY ["Database/Database.Migrations/Database.Migrations.csproj", "Database/Database.Migrations/"] @@ -38,7 +38,7 @@ COPY ["Infrastructure/Infrastructure.PasswordHashing/Infrastructure.PasswordHash COPY ["Infrastructure/Infrastructure.Sql/Infrastructure.Sql.csproj", "Infrastructure/Infrastructure.Sql/"] COPY ["Shared/Shared.Application/Shared.Application.csproj", "Shared/Shared.Application/"] COPY ["Shared/Shared.Contracts/Shared.Contracts.csproj", "Shared/Shared.Contracts/"] -RUN dotnet restore "Core.slnx" +RUN dotnet restore "Biergarten.API.slnx" COPY . . WORKDIR "/src/API/API.Core" RUN dotnet build "./API.Core.csproj" -c $BUILD_CONFIGURATION -o /app/build diff --git a/web/backend/API/API.Specs/Dockerfile b/web/backend/API/API.Specs/Dockerfile index 2937ff6f..a81d2ce7 100644 --- a/web/backend/API/API.Specs/Dockerfile +++ b/web/backend/API/API.Specs/Dockerfile @@ -3,8 +3,8 @@ ARG BUILD_CONFIGURATION=Release WORKDIR /src # See API.Core/Dockerfile for why this copies every project file (with real relative paths) and -# restores against Core.slnx rather than hand-listing API.Specs' transitive dependencies. -COPY ["Core.slnx", "./"] +# restores against Biergarten.API.slnx rather than hand-listing API.Specs' transitive dependencies. +COPY ["Biergarten.API.slnx", "./"] COPY ["API/API.Core/API.Core.csproj", "API/API.Core/"] COPY ["API/API.Specs/API.Specs.csproj", "API/API.Specs/"] COPY ["Database/Database.Migrations/Database.Migrations.csproj", "Database/Database.Migrations/"] @@ -28,7 +28,7 @@ COPY ["Infrastructure/Infrastructure.PasswordHashing/Infrastructure.PasswordHash COPY ["Infrastructure/Infrastructure.Sql/Infrastructure.Sql.csproj", "Infrastructure/Infrastructure.Sql/"] COPY ["Shared/Shared.Application/Shared.Application.csproj", "Shared/Shared.Application/"] COPY ["Shared/Shared.Contracts/Shared.Contracts.csproj", "Shared/Shared.Contracts/"] -RUN dotnet restore "Core.slnx" +RUN dotnet restore "Biergarten.API.slnx" COPY . . WORKDIR "/src/API/API.Specs" RUN dotnet build "./API.Specs.csproj" -c $BUILD_CONFIGURATION -o /app/build diff --git a/web/backend/Database/Database.Seed/Program.cs b/web/backend/Database/Database.Seed/Program.cs index 176e6bdb..f713c07b 100644 --- a/web/backend/Database/Database.Seed/Program.cs +++ b/web/backend/Database/Database.Seed/Program.cs @@ -3,15 +3,15 @@ using Database.Seed.PipelineData; using Database.Seed.Sqlite; using Domain.Entities; using Features.Auth.DependencyInjection; -using Features.Auth.Repository; +using Features.Auth.Identity; using Features.Breweries.DependencyInjection; using Features.Breweries.Repository; using Features.Locations.DependencyInjection; using Features.Locations.Dtos; using Features.Locations.Repository; using idunno.Password; -using Infrastructure.PasswordHashing; using Infrastructure.Sql.DependencyInjection; +using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Spectre.Console; @@ -36,10 +36,8 @@ static async Task RunAsync() provider.GetRequiredService(); ILocationRepository locationRepository = provider.GetRequiredService(); - IAuthRepository authRepository = - provider.GetRequiredService(); - IPasswordInfrastructure passwordInfrastructure = - provider.GetRequiredService(); + UserManager userManager = + provider.GetRequiredService>(); AnsiConsole.Write(new Rule("[bold green]Database Seeder[/]") .LeftJustified()); @@ -94,11 +92,7 @@ static async Task RunAsync() "Loading user data into target database...", async ctx => { - postedByIds = await LoadUsersIntoDatabaseAsync( - authRepository, - passwordInfrastructure, - seedData.Users - ); + postedByIds = await LoadUsersIntoDatabaseAsync(userManager, seedData.Users); ctx.Status("User data loaded into target database."); } ); @@ -132,8 +126,7 @@ static async Task RunAsync() } static async Task> LoadUsersIntoDatabaseAsync( - IAuthRepository authRepository, - IPasswordInfrastructure passwordInfrastructure, + UserManager userManager, IReadOnlyList users ) { @@ -141,22 +134,29 @@ static async Task> LoadUsersIntoDatabaseAsync( foreach (UserRecord userRecord in users) { - UserAccount userAccount = await authRepository.RegisterUserAsync( - new UserAccount() - { - FirstName = userRecord.User.FirstName, - LastName = userRecord.User.LastName, - DateOfBirth = DateTime.Parse(userRecord.DateOfBirth), - Username = userRecord.User.Username, - Email = userRecord.Email, - UserCredential = new UserCredential - { - Hash = passwordInfrastructure.Hash( - password: PasswordGenerator.Generate(64, 12, 12)) - } - } + ApplicationUser user = new() + { + FirstName = userRecord.User.FirstName, + LastName = userRecord.User.LastName, + DateOfBirth = DateTime.Parse(userRecord.DateOfBirth), + UserName = userRecord.User.Username, + Email = userRecord.Email, + }; + + // allowRepeatedCharacters: true -- 12 unique digits was requested from only 10 possible + // digit characters (0-9), which idunno.Password rejects outright. + IdentityResult result = await userManager.CreateAsync( + user, + PasswordGenerator.Generate(64, 12, 12, allowRepeatedCharacters: true) ); - userAccountIds.Add(userAccount.UserAccountId); + + if (!result.Succeeded) + throw new InvalidOperationException( + $"Failed to seed user '{userRecord.User.Username}': " + + string.Join("; ", result.Errors.Select(e => e.Description)) + ); + + userAccountIds.Add(user.Id); } return userAccountIds; diff --git a/web/backend/Dockerfile.tests b/web/backend/Dockerfile.tests index 81b4f21f..bab9c101 100644 --- a/web/backend/Dockerfile.tests +++ b/web/backend/Dockerfile.tests @@ -2,8 +2,8 @@ FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build ARG BUILD_CONFIGURATION=Release WORKDIR /src COPY . . -RUN dotnet restore "Core.slnx" -RUN dotnet build "Core.slnx" -c $BUILD_CONFIGURATION +RUN dotnet restore "Biergarten.API.slnx" +RUN dotnet build "Biergarten.API.slnx" -c $BUILD_CONFIGURATION # Runs every Features.*.Tests project (unit + DbMocker-backed repository tests). API.Specs is # excluded here since it's a separate BDD/integration suite, run by its own compose service