Initiate db drop/recreation in seed application, update broken procs

This commit is contained in:
Aaron Po
2026-01-29 23:05:08 -05:00
parent ca49d19bf7
commit 0053d84de8
5 changed files with 102 additions and 75 deletions

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,7 @@
using DBSeed;
using Microsoft.Data.SqlClient;
using DbUp;
using System.Reflection;
try
{
@@ -14,8 +16,47 @@ try
await using var connection = new SqlConnection(connectionString);
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("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 =
[
new LocationSeeder(),
@@ -30,6 +71,7 @@ try
}
Console.WriteLine("Seed completed successfully.");
await connection.CloseAsync();
return 0;
}
catch (Exception ex)

View File

@@ -9,11 +9,8 @@ using Microsoft.Data.SqlClient;
namespace DBSeed
{
internal class UserSeeder : ISeeder
{
private static readonly IReadOnlyList<(
string FirstName,
string LastName
@@ -129,36 +126,38 @@ namespace DBSeed
int createdCredentials = 0;
int createdVerifications = 0;
foreach (var (firstName, lastName) in SeedNames)
{
// create the user in the database
var userAccountId = Guid.NewGuid();
await AddUserAccountAsync(connection, new UserAccount
{
UserAccountId = userAccountId,
FirstName = firstName,
LastName = lastName,
Email = $"{firstName}.{lastName}@thebiergarten.app",
Username = $"{firstName[0]}.{lastName}",
DateOfBirth = GenerateDateOfBirth(rng)
});
createdUsers++;
// prepare user fields
var username = $"{firstName[0]}.{lastName}";
var email = $"{firstName}.{lastName}@thebiergarten.app";
var dob = GenerateDateOfBirth(rng);
// add user credentials
if (!await HasUserCredentialAsync(connection, userAccountId))
{
string pwd = generator.Generate(
length: 64,
numberOfDigits: 10,
numberOfSymbols: 10
);
string hash = GeneratePasswordHash(pwd);
await AddUserCredentialAsync(connection, userAccountId, hash);
createdCredentials++;
}
// generate a password and hash it
string pwd = generator.Generate(
length: 64,
numberOfDigits: 10,
numberOfSymbols: 10
);
string hash = GeneratePasswordHash(pwd);
// register the user (creates account + credential)
var userAccountId = await RegisterUserAsync(
connection,
username,
firstName,
lastName,
dob,
email,
hash
);
createdUsers++;
createdCredentials++;
// add user verification
if (await HasUserVerificationAsync(connection, userAccountId)) continue;
await AddUserVerificationAsync(connection, userAccountId);
createdVerifications++;
}
@@ -168,19 +167,34 @@ namespace DBSeed
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.Parameters.Add("@UserAccountId", SqlDbType.UniqueIdentifier).Value = ua.UserAccountId;
command.Parameters.Add("@Username", SqlDbType.NVarChar, 100).Value = ua.Username;
command.Parameters.Add("@FirstName", SqlDbType.NVarChar, 100).Value = ua.FirstName;
command.Parameters.Add("@LastName", SqlDbType.NVarChar, 100).Value = ua.LastName;
command.Parameters.Add("@Email", SqlDbType.NVarChar, 256).Value = ua.Email;
command.Parameters.Add("@DateOfBirth", SqlDbType.Date).Value = ua.DateOfBirth;
var idParam = new SqlParameter("@UserAccountId_", SqlDbType.UniqueIdentifier)
{
Direction = ParameterDirection.Output
};
command.Parameters.Add(idParam);
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();
return (Guid)idParam.Value;
}
private static string GeneratePasswordHash(string pwd)
@@ -199,39 +213,6 @@ namespace DBSeed
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(
SqlConnection connection,
Guid userAccountId
@@ -258,7 +239,7 @@ namespace DBSeed
connection
);
command.CommandType = CommandType.StoredProcedure;
command.Parameters.AddWithValue("@UserAccountID", userAccountId);
command.Parameters.AddWithValue("@UserAccountID_", userAccountId);
await command.ExecuteNonQueryAsync();
}