Format ./web/backend/Database/Database.Seed

This commit is contained in:
Aaron Po
2026-06-20 15:13:32 -04:00
parent bf0d29fa54
commit 27f28d5207
5 changed files with 54 additions and 92 deletions

View File

@@ -8,16 +8,13 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="idunno.Password.Generator" Version="1.0.1"/>
<PackageReference
Include="Konscious.Security.Cryptography.Argon2"
Version="1.3.1"
/>
<PackageReference Include="Microsoft.Data.SqlClient" Version="6.1.4"/>
<PackageReference Include="dbup" Version="5.0.41"/>
<PackageReference Include="idunno.Password.Generator" Version="1.0.1" />
<PackageReference Include="Konscious.Security.Cryptography.Argon2" Version="1.3.1" />
<PackageReference Include="Microsoft.Data.SqlClient" Version="6.1.4" />
<PackageReference Include="dbup" Version="5.0.41" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Domain\Domain.Entities\Domain.Entities.csproj"/>
<ProjectReference Include="..\..\Domain\Domain.Entities\Domain.Entities.csproj" />
</ItemGroup>
</Project>

View File

@@ -16,4 +16,4 @@ internal interface ISeeder
/// <param name="connection">An open connection to the target database.</param>
/// <returns>A task that completes when seeding is finished.</returns>
Task SeedAsync(SqlConnection connection);
}
}

View File

@@ -15,24 +15,22 @@ namespace Database.Seed;
internal class LocationSeeder : ISeeder
{
/// <summary>The set of countries to seed, identified by name and ISO 3166-1 code.</summary>
private static readonly IReadOnlyList<(
string CountryName,
string CountryCode
)> Countries =
private static readonly IReadOnlyList<(string CountryName, string CountryCode)> Countries =
[
("Canada", "CA"),
("Mexico", "MX"),
("United States", "US")
("United States", "US"),
];
/// <summary>
/// The set of states/provinces to seed, each identified by name, ISO 3166-2 code,
/// and the ISO 3166-1 code of its parent country.
/// </summary>
private static IReadOnlyList<(string StateProvinceName, string StateProvinceCode, string CountryCode)> States
{
get;
} =
private static IReadOnlyList<(
string StateProvinceName,
string StateProvinceCode,
string CountryCode
)> States { get; } =
[
("Alabama", "US-AL", "US"),
("Alaska", "US-AK", "US"),
@@ -134,7 +132,7 @@ internal class LocationSeeder : ISeeder
("Veracruz de Ignacio de la Llave", "MX-VER", "MX"),
("Yucatán", "MX-YUC", "MX"),
("Zacatecas", "MX-ZAC", "MX"),
("Ciudad de México", "MX-CMX", "MX")
("Ciudad de México", "MX-CMX", "MX"),
];
/// <summary>
@@ -255,7 +253,7 @@ internal class LocationSeeder : ISeeder
("MX-COA", "Saltillo"),
("MX-BCS", "La Paz"),
("MX-NAY", "Tepic"),
("MX-ZAC", "Zacatecas")
("MX-ZAC", "Zacatecas"),
];
/// <summary>
@@ -269,9 +267,7 @@ internal class LocationSeeder : ISeeder
foreach ((string countryName, string countryCode) in Countries)
await CreateCountryAsync(connection, countryName, countryCode);
foreach (
(string stateProvinceName, string stateProvinceCode, string countryCode) in States
)
foreach ((string stateProvinceName, string stateProvinceCode, string countryCode) in States)
await CreateStateProvinceAsync(
connection,
stateProvinceName,
@@ -294,10 +290,7 @@ internal class LocationSeeder : ISeeder
string countryCode
)
{
await using SqlCommand command = new(
"dbo.USP_CreateCountry",
connection
);
await using SqlCommand command = new("dbo.USP_CreateCountry", connection);
command.CommandType = CommandType.StoredProcedure;
command.Parameters.AddWithValue("@CountryName", countryName);
command.Parameters.AddWithValue("@ISO3166_1", countryCode);
@@ -318,15 +311,9 @@ internal class LocationSeeder : ISeeder
string countryCode
)
{
await using SqlCommand command = new(
"dbo.USP_CreateStateProvince",
connection
);
await using SqlCommand command = new("dbo.USP_CreateStateProvince", connection);
command.CommandType = CommandType.StoredProcedure;
command.Parameters.AddWithValue(
"@StateProvinceName",
stateProvinceName
);
command.Parameters.AddWithValue("@StateProvinceName", stateProvinceName);
command.Parameters.AddWithValue("@ISO3166_2", stateProvinceCode);
command.Parameters.AddWithValue("@CountryCode", countryCode);
@@ -344,17 +331,11 @@ internal class LocationSeeder : ISeeder
string stateProvinceCode
)
{
await using SqlCommand command = new(
"dbo.USP_CreateCity",
connection
);
await using SqlCommand command = new("dbo.USP_CreateCity", connection);
command.CommandType = CommandType.StoredProcedure;
command.Parameters.AddWithValue("@CityName", cityName);
command.Parameters.AddWithValue(
"@StateProvinceCode",
stateProvinceCode
);
command.Parameters.AddWithValue("@StateProvinceCode", stateProvinceCode);
await command.ExecuteNonQueryAsync();
}
}
}

View File

@@ -17,20 +17,24 @@ using Microsoft.Data.SqlClient;
/// </exception>
string BuildConnectionString()
{
string server = Environment.GetEnvironmentVariable("DB_SERVER")
?? throw new InvalidOperationException("DB_SERVER environment variable is not set");
string server =
Environment.GetEnvironmentVariable("DB_SERVER")
?? throw new InvalidOperationException("DB_SERVER environment variable is not set");
string dbName = Environment.GetEnvironmentVariable("DB_NAME")
?? throw new InvalidOperationException("DB_NAME environment variable is not set");
string dbName =
Environment.GetEnvironmentVariable("DB_NAME")
?? throw new InvalidOperationException("DB_NAME environment variable is not set");
string user = Environment.GetEnvironmentVariable("DB_USER")
?? throw new InvalidOperationException("DB_USER environment variable is not set");
string user =
Environment.GetEnvironmentVariable("DB_USER")
?? throw new InvalidOperationException("DB_USER environment variable is not set");
string password = Environment.GetEnvironmentVariable("DB_PASSWORD")
?? throw new InvalidOperationException("DB_PASSWORD environment variable is not set");
string password =
Environment.GetEnvironmentVariable("DB_PASSWORD")
?? throw new InvalidOperationException("DB_PASSWORD environment variable is not set");
string trustServerCertificate = Environment.GetEnvironmentVariable("DB_TRUST_SERVER_CERTIFICATE")
?? "True";
string trustServerCertificate =
Environment.GetEnvironmentVariable("DB_TRUST_SERVER_CERTIFICATE") ?? "True";
SqlConnectionStringBuilder builder = new()
{
@@ -39,13 +43,12 @@ string BuildConnectionString()
UserID = user,
Password = password,
TrustServerCertificate = bool.Parse(trustServerCertificate),
Encrypt = true
Encrypt = true,
};
return builder.ConnectionString;
}
try
{
string connectionString = BuildConnectionString();
@@ -74,17 +77,14 @@ try
connection = null;
}
if (connection == null) throw new Exception($"Failed to connect to database after {maxRetries} attempts.");
if (connection == null)
throw new Exception($"Failed to connect to database after {maxRetries} attempts.");
Console.WriteLine("Starting seeding...");
using (connection)
{
ISeeder[] seeders =
[
new LocationSeeder(),
new UserSeeder()
];
ISeeder[] seeders = [new LocationSeeder(), new UserSeeder()];
foreach (ISeeder seeder in seeders)
{
@@ -103,4 +103,4 @@ catch (Exception ex)
Console.Error.WriteLine("Seed failed:");
Console.Error.WriteLine(ex);
return 1;
}
}

View File

@@ -19,10 +19,7 @@ namespace Database.Seed;
internal class UserSeeder : ISeeder
{
/// <summary>The first/last name pairs used to generate seed user accounts.</summary>
private static readonly IReadOnlyList<(
string FirstName,
string LastName
)> SeedNames =
private static readonly IReadOnlyList<(string FirstName, string LastName)> SeedNames =
[
("Aarya", "Mathews"),
("Aiden", "Wells"),
@@ -123,7 +120,7 @@ internal class UserSeeder : ISeeder
("Zara", "Wilkinson"),
("Zaria", "Gibson"),
("Zion", "Watkins"),
("Zoie", "Armstrong")
("Zoie", "Armstrong"),
];
/// <summary>
@@ -168,14 +165,9 @@ internal class UserSeeder : ISeeder
DateTime dob = GenerateDateOfBirth(rng);
// generate a password and hash it
string pwd = generator.Generate(
64,
10,
10
);
string pwd = generator.Generate(64, 10, 10);
string hash = GeneratePasswordHash(pwd);
// register the user (creates account + credential)
Guid id = await RegisterUserAsync(
connection,
@@ -189,9 +181,9 @@ internal class UserSeeder : ISeeder
createdUsers++;
createdCredentials++;
// add user verification
if (await HasUserVerificationAsync(connection, id)) continue;
if (await HasUserVerificationAsync(connection, id))
continue;
await AddUserVerificationAsync(connection, id);
createdVerifications++;
@@ -227,7 +219,6 @@ internal class UserSeeder : ISeeder
await using SqlCommand command = new("dbo.USP_RegisterUser", connection);
command.CommandType = CommandType.StoredProcedure;
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;
@@ -237,7 +228,6 @@ internal class UserSeeder : ISeeder
object? result = await command.ExecuteScalarAsync();
return (Guid)result!;
}
@@ -257,7 +247,7 @@ internal class UserSeeder : ISeeder
Salt = salt,
DegreeOfParallelism = Math.Max(Environment.ProcessorCount, 1),
MemorySize = 65536,
Iterations = 4
Iterations = 4,
};
byte[] hash = argon2.GetBytes(32);
@@ -274,10 +264,10 @@ internal class UserSeeder : ISeeder
)
{
const string sql = """
SELECT 1
FROM dbo.UserVerification
WHERE UserAccountId = @UserAccountId;
""";
SELECT 1
FROM dbo.UserVerification
WHERE UserAccountId = @UserAccountId;
""";
await using SqlCommand command = new(sql, connection);
command.Parameters.AddWithValue("@UserAccountId", userAccountId);
object? result = await command.ExecuteScalarAsync();
@@ -288,15 +278,9 @@ internal class UserSeeder : ISeeder
/// <param name="connection">An open connection to the target database.</param>
/// <param name="userAccountId">The identifier of the user account to verify.</param>
/// <returns>A task that completes when the verification record has been created.</returns>
private static async Task AddUserVerificationAsync(
SqlConnection connection,
Guid userAccountId
)
private static async Task AddUserVerificationAsync(SqlConnection connection, Guid userAccountId)
{
await using SqlCommand command = new(
"dbo.USP_CreateUserVerification",
connection
);
await using SqlCommand command = new("dbo.USP_CreateUserVerification", connection);
command.CommandType = CommandType.StoredProcedure;
command.Parameters.AddWithValue("@UserAccountID_", userAccountId);
@@ -316,4 +300,4 @@ internal class UserSeeder : ISeeder
int offsetDays = random.Next(0, 365);
return baseDate.AddDays(-offsetDays);
}
}
}