From 0053d84de881a0b1e9dc2e0b983bcef82ccd1e36 Mon Sep 17 00:00:00 2001 From: Aaron Po Date: Thu, 29 Jan 2026 23:05:08 -0500 Subject: [PATCH] Initiate db drop/recreation in seed application, update broken procs --- .../01-UserAccount/USP_CreateUserAccount.sql | 5 +- .../03-crud/02-Auth/USP_RegisterUser.sql | 7 +- .../Database.Seed/Database.Seed.csproj | 2 + src/Core/Database/Database.Seed/Program.cs | 42 ++++++ src/Core/Database/Database.Seed/UserSeeder.cs | 121 ++++++++---------- 5 files changed, 102 insertions(+), 75 deletions(-) diff --git a/src/Core/Database/Database.Core/scripts/03-crud/01-UserAccount/USP_CreateUserAccount.sql b/src/Core/Database/Database.Core/scripts/03-crud/01-UserAccount/USP_CreateUserAccount.sql index a99b12d..80f71d5 100644 --- a/src/Core/Database/Database.Core/scripts/03-crud/01-UserAccount/USP_CreateUserAccount.sql +++ b/src/Core/Database/Database.Core/scripts/03-crud/01-UserAccount/USP_CreateUserAccount.sql @@ -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; diff --git a/src/Core/Database/Database.Core/scripts/03-crud/02-Auth/USP_RegisterUser.sql b/src/Core/Database/Database.Core/scripts/03-crud/02-Auth/USP_RegisterUser.sql index 8f10b83..a784788 100644 --- a/src/Core/Database/Database.Core/scripts/03-crud/02-Auth/USP_RegisterUser.sql +++ b/src/Core/Database/Database.Core/scripts/03-crud/02-Auth/USP_RegisterUser.sql @@ -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 diff --git a/src/Core/Database/Database.Seed/Database.Seed.csproj b/src/Core/Database/Database.Seed/Database.Seed.csproj index 70839e4..e9b8521 100644 --- a/src/Core/Database/Database.Seed/Database.Seed.csproj +++ b/src/Core/Database/Database.Seed/Database.Seed.csproj @@ -14,9 +14,11 @@ Version="1.3.1" /> + + diff --git a/src/Core/Database/Database.Seed/Program.cs b/src/Core/Database/Database.Seed/Program.cs index 39640cd..8612f2e 100644 --- a/src/Core/Database/Database.Seed/Program.cs +++ b/src/Core/Database/Database.Seed/Program.cs @@ -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) diff --git a/src/Core/Database/Database.Seed/UserSeeder.cs b/src/Core/Database/Database.Seed/UserSeeder.cs index 6efdfc3..437ad46 100644 --- a/src/Core/Database/Database.Seed/UserSeeder.cs +++ b/src/Core/Database/Database.Seed/UserSeeder.cs @@ -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 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 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 HasUserVerificationAsync( SqlConnection connection, Guid userAccountId @@ -258,11 +239,11 @@ namespace DBSeed connection ); command.CommandType = CommandType.StoredProcedure; - command.Parameters.AddWithValue("@UserAccountID", userAccountId); + command.Parameters.AddWithValue("@UserAccountID_", userAccountId); await command.ExecuteNonQueryAsync(); } - + private static DateTime GenerateDateOfBirth(Random random) { int age = 19 + random.Next(0, 30);