Merge pull request #122 from aaronpo97/121-database-seed-enhancement

Initiate db drop/recreation in seed application, update broken procs
This commit is contained in:
Aaron Po
2026-01-29 23:06:47 -05:00
committed by GitHub
5 changed files with 102 additions and 75 deletions

View File

@@ -12,6 +12,8 @@ AS
BEGIN BEGIN
SET NOCOUNT ON; SET NOCOUNT ON;
DECLARE @Inserted TABLE (UserAccountID UNIQUEIDENTIFIER);
INSERT INTO UserAccount INSERT INTO UserAccount
( (
Username, Username,
@@ -20,6 +22,7 @@ BEGIN
DateOfBirth, DateOfBirth,
Email Email
) )
OUTPUT INSERTED.UserAccountID INTO @Inserted
VALUES VALUES
( (
@Username, @Username,
@@ -29,5 +32,5 @@ BEGIN
@Email @Email
); );
SELECT @UserAccountId AS UserAccountId; SELECT @UserAccountId = UserAccountID FROM @Inserted;
END; END;

View File

@@ -27,10 +27,9 @@ BEGIN
THROW 50000, 'Failed to create user account.', 1; THROW 50000, 'Failed to create user account.', 1;
END END
INSERT INTO dbo.UserCredential
EXEC dbo.usp_RotateUserCredential (UserAccountId, Hash)
@UserAccountId = @UserAccountId_, VALUES (@UserAccountId_, @Hash);
@Hash = @Hash;
IF @@ROWCOUNT = 0 IF @@ROWCOUNT = 0
BEGIN BEGIN

View File

@@ -14,9 +14,11 @@
Version="1.3.1" Version="1.3.1"
/> />
<PackageReference Include="Microsoft.Data.SqlClient" Version="6.1.3" /> <PackageReference Include="Microsoft.Data.SqlClient" Version="6.1.3" />
<PackageReference Include="dbup" Version="5.0.41" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Database.Core\Database.Core.csproj" />
<ProjectReference Include="..\..\Repository\Repository.Core\Repository.Core.csproj" /> <ProjectReference Include="..\..\Repository\Repository.Core\Repository.Core.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -1,5 +1,7 @@
using DBSeed; using DBSeed;
using Microsoft.Data.SqlClient; using Microsoft.Data.SqlClient;
using DbUp;
using System.Reflection;
try try
{ {
@@ -14,8 +16,47 @@ try
await using var connection = new SqlConnection(connectionString); await using var connection = new SqlConnection(connectionString);
await connection.OpenAsync(); await connection.OpenAsync();
// drop and recreate the database
var useMaster = connection.CreateCommand();
useMaster.CommandText = "USE master;";
await useMaster.ExecuteNonQueryAsync();
var dbName = "Biergarten";
var dropDb = connection.CreateCommand();
dropDb.CommandText = $@"
IF DB_ID(N'{dbName}') IS NOT NULL
BEGIN
ALTER DATABASE [{dbName}] SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
DROP DATABASE [{dbName}];
END";
await dropDb.ExecuteNonQueryAsync();
var createDb = connection.CreateCommand();
createDb.CommandText = $@"CREATE DATABASE [{dbName}];";
await createDb.ExecuteNonQueryAsync();
await connection.CloseAsync();
await connection.OpenAsync();
Console.WriteLine("Connected to database."); Console.WriteLine("Connected to database.");
Console.WriteLine("Starting migrations...");
// Run Database.Core migrations (embedded resources) via DbUp
var migrationAssembly = Assembly.Load("Database.Core");
var upgrader = DeployChanges
.To.SqlDatabase(connectionString)
.WithScriptsEmbeddedInAssembly(migrationAssembly)
.LogToConsole()
.Build();
var upgradeResult = upgrader.PerformUpgrade();
if (!upgradeResult.Successful)
throw upgradeResult.Error;
Console.WriteLine("Migrations completed.");
ISeeder[] seeders = ISeeder[] seeders =
[ [
new LocationSeeder(), new LocationSeeder(),
@@ -30,6 +71,7 @@ try
} }
Console.WriteLine("Seed completed successfully."); Console.WriteLine("Seed completed successfully.");
await connection.CloseAsync();
return 0; return 0;
} }
catch (Exception ex) catch (Exception ex)

View File

@@ -9,11 +9,8 @@ using Microsoft.Data.SqlClient;
namespace DBSeed namespace DBSeed
{ {
internal class UserSeeder : ISeeder internal class UserSeeder : ISeeder
{ {
private static readonly IReadOnlyList<( private static readonly IReadOnlyList<(
string FirstName, string FirstName,
string LastName string LastName
@@ -129,36 +126,38 @@ namespace DBSeed
int createdCredentials = 0; int createdCredentials = 0;
int createdVerifications = 0; int createdVerifications = 0;
foreach (var (firstName, lastName) in SeedNames) foreach (var (firstName, lastName) in SeedNames)
{ {
// create the user in the database // prepare user fields
var userAccountId = Guid.NewGuid(); var username = $"{firstName[0]}.{lastName}";
await AddUserAccountAsync(connection, new UserAccount var email = $"{firstName}.{lastName}@thebiergarten.app";
{ var dob = GenerateDateOfBirth(rng);
UserAccountId = userAccountId,
FirstName = firstName,
LastName = lastName,
Email = $"{firstName}.{lastName}@thebiergarten.app",
Username = $"{firstName[0]}.{lastName}",
DateOfBirth = GenerateDateOfBirth(rng)
});
createdUsers++;
// add user credentials // generate a password and hash it
if (!await HasUserCredentialAsync(connection, userAccountId)) string pwd = generator.Generate(
{ length: 64,
string pwd = generator.Generate( numberOfDigits: 10,
length: 64, numberOfSymbols: 10
numberOfDigits: 10, );
numberOfSymbols: 10 string hash = GeneratePasswordHash(pwd);
);
string hash = GeneratePasswordHash(pwd); // register the user (creates account + credential)
await AddUserCredentialAsync(connection, userAccountId, hash); var userAccountId = await RegisterUserAsync(
createdCredentials++; connection,
} username,
firstName,
lastName,
dob,
email,
hash
);
createdUsers++;
createdCredentials++;
// add user verification // add user verification
if (await HasUserVerificationAsync(connection, userAccountId)) continue; if (await HasUserVerificationAsync(connection, userAccountId)) continue;
await AddUserVerificationAsync(connection, userAccountId); await AddUserVerificationAsync(connection, userAccountId);
createdVerifications++; createdVerifications++;
} }
@@ -168,19 +167,34 @@ namespace DBSeed
Console.WriteLine($"Added {createdVerifications} user verifications."); Console.WriteLine($"Added {createdVerifications} user verifications.");
} }
private static async Task AddUserAccountAsync(SqlConnection connection, UserAccount ua) private static async Task<Guid> RegisterUserAsync(
SqlConnection connection,
string username,
string firstName,
string lastName,
DateTime dateOfBirth,
string email,
string hash
)
{ {
await using var command = new SqlCommand("usp_CreateUserAccount", connection); await using var command = new SqlCommand("dbo.USP_RegisterUser", connection);
command.CommandType = CommandType.StoredProcedure; command.CommandType = CommandType.StoredProcedure;
command.Parameters.Add("@UserAccountId", SqlDbType.UniqueIdentifier).Value = ua.UserAccountId; var idParam = new SqlParameter("@UserAccountId_", SqlDbType.UniqueIdentifier)
command.Parameters.Add("@Username", SqlDbType.NVarChar, 100).Value = ua.Username; {
command.Parameters.Add("@FirstName", SqlDbType.NVarChar, 100).Value = ua.FirstName; Direction = ParameterDirection.Output
command.Parameters.Add("@LastName", SqlDbType.NVarChar, 100).Value = ua.LastName; };
command.Parameters.Add("@Email", SqlDbType.NVarChar, 256).Value = ua.Email; command.Parameters.Add(idParam);
command.Parameters.Add("@DateOfBirth", SqlDbType.Date).Value = ua.DateOfBirth;
command.Parameters.Add("@Username", SqlDbType.VarChar, 64).Value = username;
command.Parameters.Add("@FirstName", SqlDbType.NVarChar, 128).Value = firstName;
command.Parameters.Add("@LastName", SqlDbType.NVarChar, 128).Value = lastName;
command.Parameters.Add("@DateOfBirth", SqlDbType.DateTime).Value = dateOfBirth;
command.Parameters.Add("@Email", SqlDbType.VarChar, 128).Value = email;
command.Parameters.Add("@Hash", SqlDbType.NVarChar, -1).Value = hash;
await command.ExecuteNonQueryAsync(); await command.ExecuteNonQueryAsync();
return (Guid)idParam.Value;
} }
private static string GeneratePasswordHash(string pwd) private static string GeneratePasswordHash(string pwd)
@@ -199,39 +213,6 @@ namespace DBSeed
return $"{Convert.ToBase64String(salt)}:{Convert.ToBase64String(hash)}"; return $"{Convert.ToBase64String(salt)}:{Convert.ToBase64String(hash)}";
} }
private static async Task<bool> HasUserCredentialAsync(
SqlConnection connection,
Guid userAccountId
)
{
const string sql = $"""
SELECT 1
FROM dbo.UserCredential
WHERE UserAccountId = @UserAccountId;
""";
await using var command = new SqlCommand(sql, connection);
command.Parameters.AddWithValue("@UserAccountId", userAccountId);
object? result = await command.ExecuteScalarAsync();
return result is not null;
}
private static async Task AddUserCredentialAsync(
SqlConnection connection,
Guid userAccountId,
string hash
)
{
await using var command = new SqlCommand(
"dbo.USP_AddUserCredential",
connection
);
command.CommandType = CommandType.StoredProcedure;
command.Parameters.AddWithValue("@UserAccountId", userAccountId);
command.Parameters.AddWithValue("@Hash", hash);
await command.ExecuteNonQueryAsync();
}
private static async Task<bool> HasUserVerificationAsync( private static async Task<bool> HasUserVerificationAsync(
SqlConnection connection, SqlConnection connection,
Guid userAccountId Guid userAccountId
@@ -258,7 +239,7 @@ namespace DBSeed
connection connection
); );
command.CommandType = CommandType.StoredProcedure; command.CommandType = CommandType.StoredProcedure;
command.Parameters.AddWithValue("@UserAccountID", userAccountId); command.Parameters.AddWithValue("@UserAccountID_", userAccountId);
await command.ExecuteNonQueryAsync(); await command.ExecuteNonQueryAsync();
} }