Migrate Database.Seed to UserManager<ApplicationUser>

This commit is contained in:
Aaron Po
2026-08-22 02:06:06 -04:00
parent 6b9c14c14b
commit c055982066
4 changed files with 36 additions and 36 deletions

View File

@@ -12,9 +12,9 @@ WORKDIR /src
# Copy the solution file and every project file first (preserving their real relative paths, since # 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 # 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, # `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. # 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.Core/API.Core.csproj", "API/API.Core/"]
COPY ["API/API.Specs/API.Specs.csproj", "API/API.Specs/"] COPY ["API/API.Specs/API.Specs.csproj", "API/API.Specs/"]
COPY ["Database/Database.Migrations/Database.Migrations.csproj", "Database/Database.Migrations/"] 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 ["Infrastructure/Infrastructure.Sql/Infrastructure.Sql.csproj", "Infrastructure/Infrastructure.Sql/"]
COPY ["Shared/Shared.Application/Shared.Application.csproj", "Shared/Shared.Application/"] COPY ["Shared/Shared.Application/Shared.Application.csproj", "Shared/Shared.Application/"]
COPY ["Shared/Shared.Contracts/Shared.Contracts.csproj", "Shared/Shared.Contracts/"] COPY ["Shared/Shared.Contracts/Shared.Contracts.csproj", "Shared/Shared.Contracts/"]
RUN dotnet restore "Core.slnx" RUN dotnet restore "Biergarten.API.slnx"
COPY . . COPY . .
WORKDIR "/src/API/API.Core" WORKDIR "/src/API/API.Core"
RUN dotnet build "./API.Core.csproj" -c $BUILD_CONFIGURATION -o /app/build RUN dotnet build "./API.Core.csproj" -c $BUILD_CONFIGURATION -o /app/build

View File

@@ -3,8 +3,8 @@ ARG BUILD_CONFIGURATION=Release
WORKDIR /src WORKDIR /src
# See API.Core/Dockerfile for why this copies every project file (with real relative paths) and # 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. # restores against Biergarten.API.slnx rather than hand-listing API.Specs' transitive dependencies.
COPY ["Core.slnx", "./"] COPY ["Biergarten.API.slnx", "./"]
COPY ["API/API.Core/API.Core.csproj", "API/API.Core/"] COPY ["API/API.Core/API.Core.csproj", "API/API.Core/"]
COPY ["API/API.Specs/API.Specs.csproj", "API/API.Specs/"] COPY ["API/API.Specs/API.Specs.csproj", "API/API.Specs/"]
COPY ["Database/Database.Migrations/Database.Migrations.csproj", "Database/Database.Migrations/"] 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 ["Infrastructure/Infrastructure.Sql/Infrastructure.Sql.csproj", "Infrastructure/Infrastructure.Sql/"]
COPY ["Shared/Shared.Application/Shared.Application.csproj", "Shared/Shared.Application/"] COPY ["Shared/Shared.Application/Shared.Application.csproj", "Shared/Shared.Application/"]
COPY ["Shared/Shared.Contracts/Shared.Contracts.csproj", "Shared/Shared.Contracts/"] COPY ["Shared/Shared.Contracts/Shared.Contracts.csproj", "Shared/Shared.Contracts/"]
RUN dotnet restore "Core.slnx" RUN dotnet restore "Biergarten.API.slnx"
COPY . . COPY . .
WORKDIR "/src/API/API.Specs" WORKDIR "/src/API/API.Specs"
RUN dotnet build "./API.Specs.csproj" -c $BUILD_CONFIGURATION -o /app/build RUN dotnet build "./API.Specs.csproj" -c $BUILD_CONFIGURATION -o /app/build

View File

@@ -3,15 +3,15 @@ using Database.Seed.PipelineData;
using Database.Seed.Sqlite; using Database.Seed.Sqlite;
using Domain.Entities; using Domain.Entities;
using Features.Auth.DependencyInjection; using Features.Auth.DependencyInjection;
using Features.Auth.Repository; using Features.Auth.Identity;
using Features.Breweries.DependencyInjection; using Features.Breweries.DependencyInjection;
using Features.Breweries.Repository; using Features.Breweries.Repository;
using Features.Locations.DependencyInjection; using Features.Locations.DependencyInjection;
using Features.Locations.Dtos; using Features.Locations.Dtos;
using Features.Locations.Repository; using Features.Locations.Repository;
using idunno.Password; using idunno.Password;
using Infrastructure.PasswordHashing;
using Infrastructure.Sql.DependencyInjection; using Infrastructure.Sql.DependencyInjection;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Spectre.Console; using Spectre.Console;
@@ -36,10 +36,8 @@ static async Task<int> RunAsync()
provider.GetRequiredService<IBreweryRepository>(); provider.GetRequiredService<IBreweryRepository>();
ILocationRepository locationRepository = ILocationRepository locationRepository =
provider.GetRequiredService<ILocationRepository>(); provider.GetRequiredService<ILocationRepository>();
IAuthRepository authRepository = UserManager<ApplicationUser> userManager =
provider.GetRequiredService<IAuthRepository>(); provider.GetRequiredService<UserManager<ApplicationUser>>();
IPasswordInfrastructure passwordInfrastructure =
provider.GetRequiredService<IPasswordInfrastructure>();
AnsiConsole.Write(new Rule("[bold green]Database Seeder[/]") AnsiConsole.Write(new Rule("[bold green]Database Seeder[/]")
.LeftJustified()); .LeftJustified());
@@ -94,11 +92,7 @@ static async Task<int> RunAsync()
"Loading user data into target database...", "Loading user data into target database...",
async ctx => async ctx =>
{ {
postedByIds = await LoadUsersIntoDatabaseAsync( postedByIds = await LoadUsersIntoDatabaseAsync(userManager, seedData.Users);
authRepository,
passwordInfrastructure,
seedData.Users
);
ctx.Status("User data loaded into target database."); ctx.Status("User data loaded into target database.");
} }
); );
@@ -132,8 +126,7 @@ static async Task<int> RunAsync()
} }
static async Task<IReadOnlyList<Guid>> LoadUsersIntoDatabaseAsync( static async Task<IReadOnlyList<Guid>> LoadUsersIntoDatabaseAsync(
IAuthRepository authRepository, UserManager<ApplicationUser> userManager,
IPasswordInfrastructure passwordInfrastructure,
IReadOnlyList<UserRecord> users IReadOnlyList<UserRecord> users
) )
{ {
@@ -141,22 +134,29 @@ static async Task<IReadOnlyList<Guid>> LoadUsersIntoDatabaseAsync(
foreach (UserRecord userRecord in users) foreach (UserRecord userRecord in users)
{ {
UserAccount userAccount = await authRepository.RegisterUserAsync( ApplicationUser user = new()
new UserAccount() {
{ FirstName = userRecord.User.FirstName,
FirstName = userRecord.User.FirstName, LastName = userRecord.User.LastName,
LastName = userRecord.User.LastName, DateOfBirth = DateTime.Parse(userRecord.DateOfBirth),
DateOfBirth = DateTime.Parse(userRecord.DateOfBirth), UserName = userRecord.User.Username,
Username = userRecord.User.Username, Email = userRecord.Email,
Email = userRecord.Email, };
UserCredential = new UserCredential
{ // allowRepeatedCharacters: true -- 12 unique digits was requested from only 10 possible
Hash = passwordInfrastructure.Hash( // digit characters (0-9), which idunno.Password rejects outright.
password: PasswordGenerator.Generate(64, 12, 12)) 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; return userAccountIds;

View File

@@ -2,8 +2,8 @@ FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
ARG BUILD_CONFIGURATION=Release ARG BUILD_CONFIGURATION=Release
WORKDIR /src WORKDIR /src
COPY . . COPY . .
RUN dotnet restore "Core.slnx" RUN dotnet restore "Biergarten.API.slnx"
RUN dotnet build "Core.slnx" -c $BUILD_CONFIGURATION RUN dotnet build "Biergarten.API.slnx" -c $BUILD_CONFIGURATION
# Runs every Features.*.Tests project (unit + DbMocker-backed repository tests). API.Specs is # 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 # excluded here since it's a separate BDD/integration suite, run by its own compose service