mirror of
https://github.com/aaronpo97/the-biergarten-app.git
synced 2026-07-16 17:47:22 +00:00
code style cleanup
This commit is contained in:
@@ -1,23 +1,23 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<RootNamespace>Database.Seed</RootNamespace>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<RootNamespace>Database.Seed</RootNamespace>
|
||||
</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" />
|
||||
</ItemGroup>
|
||||
<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"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Domain\Domain.Entities\Domain.Entities.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Domain\Domain.Entities\Domain.Entities.csproj"/>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -3,15 +3,15 @@ using Microsoft.Data.SqlClient;
|
||||
namespace Database.Seed;
|
||||
|
||||
/// <summary>
|
||||
/// Defines a unit of seed data that can be applied to the database. Implementations
|
||||
/// should be safe to run against an already-seeded database (e.g. by checking for
|
||||
/// existing data before inserting) and may depend on data created by seeders that run
|
||||
/// before them.
|
||||
/// Defines a unit of seed data that can be applied to the database. Implementations
|
||||
/// should be safe to run against an already-seeded database (e.g. by checking for
|
||||
/// existing data before inserting) and may depend on data created by seeders that run
|
||||
/// before them.
|
||||
/// </summary>
|
||||
internal interface ISeeder
|
||||
{
|
||||
/// <summary>
|
||||
/// Inserts this seeder's data into the database using the supplied open connection.
|
||||
/// Inserts this seeder's data into the database using the supplied open connection.
|
||||
/// </summary>
|
||||
/// <param name="connection">An open connection to the target database.</param>
|
||||
/// <returns>A task that completes when seeding is finished.</returns>
|
||||
|
||||
@@ -4,13 +4,13 @@ using Microsoft.Data.SqlClient;
|
||||
namespace Database.Seed;
|
||||
|
||||
/// <summary>
|
||||
/// Seeds the location hierarchy (countries, states/provinces, and cities) used to
|
||||
/// associate other entities (e.g. breweries, users) with a geographic location.
|
||||
/// Countries must be seeded before states/provinces, which must be seeded before
|
||||
/// cities, since each level references its parent by code. The underlying stored
|
||||
/// procedures (<c>USP_CreateCountry</c>, <c>USP_CreateStateProvince</c>,
|
||||
/// <c>USP_CreateCity</c>) are expected to be idempotent, so re-running this seeder
|
||||
/// against an already-seeded database is safe.
|
||||
/// Seeds the location hierarchy (countries, states/provinces, and cities) used to
|
||||
/// associate other entities (e.g. breweries, users) with a geographic location.
|
||||
/// Countries must be seeded before states/provinces, which must be seeded before
|
||||
/// cities, since each level references its parent by code. The underlying stored
|
||||
/// procedures (<c>USP_CreateCountry</c>, <c>USP_CreateStateProvince</c>,
|
||||
/// <c>USP_CreateCity</c>) are expected to be idempotent, so re-running this seeder
|
||||
/// against an already-seeded database is safe.
|
||||
/// </summary>
|
||||
internal class LocationSeeder : ISeeder
|
||||
{
|
||||
@@ -22,12 +22,12 @@ internal class LocationSeeder : ISeeder
|
||||
[
|
||||
("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.
|
||||
/// 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
|
||||
{
|
||||
@@ -134,12 +134,12 @@ 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>
|
||||
/// The set of cities to seed, each identified by name and the ISO 3166-2 code of its
|
||||
/// parent state/province.
|
||||
/// The set of cities to seed, each identified by name and the ISO 3166-2 code of its
|
||||
/// parent state/province.
|
||||
/// </summary>
|
||||
private static IReadOnlyList<(string StateProvinceCode, string CityName)> Cities { get; } =
|
||||
[
|
||||
@@ -255,38 +255,32 @@ internal class LocationSeeder : ISeeder
|
||||
("MX-COA", "Saltillo"),
|
||||
("MX-BCS", "La Paz"),
|
||||
("MX-NAY", "Tepic"),
|
||||
("MX-ZAC", "Zacatecas"),
|
||||
("MX-ZAC", "Zacatecas")
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// Seeds all countries, then states/provinces, then cities, in that order, so that
|
||||
/// each level's parent reference already exists by the time it is created.
|
||||
/// Seeds all countries, then states/provinces, then cities, in that order, so that
|
||||
/// each level's parent reference already exists by the time it is created.
|
||||
/// </summary>
|
||||
/// <param name="connection">An open connection to the target database.</param>
|
||||
/// <returns>A task that completes when all locations have been seeded.</returns>
|
||||
public async Task SeedAsync(SqlConnection connection)
|
||||
{
|
||||
foreach (var (countryName, countryCode) in Countries)
|
||||
{
|
||||
foreach ((string countryName, string countryCode) in Countries)
|
||||
await CreateCountryAsync(connection, countryName, countryCode);
|
||||
}
|
||||
|
||||
foreach (
|
||||
var (stateProvinceName, stateProvinceCode, countryCode) in States
|
||||
(string stateProvinceName, string stateProvinceCode, string countryCode) in States
|
||||
)
|
||||
{
|
||||
await CreateStateProvinceAsync(
|
||||
connection,
|
||||
stateProvinceName,
|
||||
stateProvinceCode,
|
||||
countryCode
|
||||
);
|
||||
}
|
||||
|
||||
foreach (var (stateProvinceCode, cityName) in Cities)
|
||||
{
|
||||
foreach ((string stateProvinceCode, string cityName) in Cities)
|
||||
await CreateCityAsync(connection, cityName, stateProvinceCode);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Creates a single country by invoking the <c>dbo.USP_CreateCountry</c> stored procedure.</summary>
|
||||
@@ -300,7 +294,7 @@ internal class LocationSeeder : ISeeder
|
||||
string countryCode
|
||||
)
|
||||
{
|
||||
await using var command = new SqlCommand(
|
||||
await using SqlCommand command = new(
|
||||
"dbo.USP_CreateCountry",
|
||||
connection
|
||||
);
|
||||
@@ -324,7 +318,7 @@ internal class LocationSeeder : ISeeder
|
||||
string countryCode
|
||||
)
|
||||
{
|
||||
await using var command = new SqlCommand(
|
||||
await using SqlCommand command = new(
|
||||
"dbo.USP_CreateStateProvince",
|
||||
connection
|
||||
);
|
||||
@@ -350,7 +344,7 @@ internal class LocationSeeder : ISeeder
|
||||
string stateProvinceCode
|
||||
)
|
||||
{
|
||||
await using var command = new SqlCommand(
|
||||
await using SqlCommand command = new(
|
||||
"dbo.USP_CreateCity",
|
||||
connection
|
||||
);
|
||||
@@ -363,4 +357,4 @@ internal class LocationSeeder : ISeeder
|
||||
|
||||
await command.ExecuteNonQueryAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
using Microsoft.Data.SqlClient;
|
||||
using DbUp;
|
||||
using System.Reflection;
|
||||
using Database.Seed;
|
||||
using Database.Seed;
|
||||
using Microsoft.Data.SqlClient;
|
||||
|
||||
// Entry point for the database seeding utility. Connects to the target database
|
||||
// (retrying on transient failures), then runs each registered ISeeder in order to
|
||||
@@ -19,22 +17,22 @@ using Database.Seed;
|
||||
/// </exception>
|
||||
string BuildConnectionString()
|
||||
{
|
||||
var 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");
|
||||
|
||||
var 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");
|
||||
|
||||
var 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");
|
||||
|
||||
var 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");
|
||||
|
||||
var trustServerCertificate = Environment.GetEnvironmentVariable("DB_TRUST_SERVER_CERTIFICATE")
|
||||
?? "True";
|
||||
string trustServerCertificate = Environment.GetEnvironmentVariable("DB_TRUST_SERVER_CERTIFICATE")
|
||||
?? "True";
|
||||
|
||||
var builder = new SqlConnectionStringBuilder
|
||||
SqlConnectionStringBuilder builder = new()
|
||||
{
|
||||
DataSource = server,
|
||||
InitialCatalog = dbName,
|
||||
@@ -50,7 +48,7 @@ string BuildConnectionString()
|
||||
|
||||
try
|
||||
{
|
||||
var connectionString = BuildConnectionString();
|
||||
string connectionString = BuildConnectionString();
|
||||
|
||||
Console.WriteLine("Attempting to connect to database...");
|
||||
|
||||
@@ -60,7 +58,6 @@ try
|
||||
int retryDelayMs = 2000;
|
||||
|
||||
for (int attempt = 1; attempt <= maxRetries; attempt++)
|
||||
{
|
||||
try
|
||||
{
|
||||
connection = new SqlConnection(connectionString);
|
||||
@@ -76,12 +73,8 @@ try
|
||||
connection?.Dispose();
|
||||
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...");
|
||||
|
||||
@@ -90,10 +83,10 @@ try
|
||||
ISeeder[] seeders =
|
||||
[
|
||||
new LocationSeeder(),
|
||||
new UserSeeder(),
|
||||
new UserSeeder()
|
||||
];
|
||||
|
||||
foreach (var seeder in seeders)
|
||||
foreach (ISeeder seeder in seeders)
|
||||
{
|
||||
Console.WriteLine($"Seeding {seeder.GetType().Name}...");
|
||||
await seeder.SeedAsync(connection);
|
||||
|
||||
@@ -8,13 +8,13 @@ using Microsoft.Data.SqlClient;
|
||||
namespace Database.Seed;
|
||||
|
||||
/// <summary>
|
||||
/// Seeds user accounts, credentials, and verification records. Creates one fixed
|
||||
/// "Test User" account (<c>test.user@thebiergarten.app</c> / password <c>"password"</c>)
|
||||
/// for testing, followed by a randomly-generated account for each name in
|
||||
/// <see cref="SeedNames"/>, each with a random password and date of birth. Skips
|
||||
/// adding a verification record for a user that already has one, so this seeder is
|
||||
/// safe to re-run, though re-running will still attempt to register (and may fail on)
|
||||
/// users that already exist.
|
||||
/// Seeds user accounts, credentials, and verification records. Creates one fixed
|
||||
/// "Test User" account (<c>test.user@thebiergarten.app</c> / password <c>"password"</c>)
|
||||
/// for testing, followed by a randomly-generated account for each name in
|
||||
/// <see cref="SeedNames" />, each with a random password and date of birth. Skips
|
||||
/// adding a verification record for a user that already has one, so this seeder is
|
||||
/// safe to re-run, though re-running will still attempt to register (and may fail on)
|
||||
/// users that already exist.
|
||||
/// </summary>
|
||||
internal class UserSeeder : ISeeder
|
||||
{
|
||||
@@ -123,21 +123,21 @@ internal class UserSeeder : ISeeder
|
||||
("Zara", "Wilkinson"),
|
||||
("Zaria", "Gibson"),
|
||||
("Zion", "Watkins"),
|
||||
("Zoie", "Armstrong"),
|
||||
("Zoie", "Armstrong")
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// Registers a fixed test user account followed by one randomly-generated account
|
||||
/// per entry in <see cref="SeedNames"/>, adding a user verification record for each
|
||||
/// newly created account that does not already have one. Progress counts are
|
||||
/// written to the console on completion.
|
||||
/// Registers a fixed test user account followed by one randomly-generated account
|
||||
/// per entry in <see cref="SeedNames" />, adding a user verification record for each
|
||||
/// newly created account that does not already have one. Progress counts are
|
||||
/// written to the console on completion.
|
||||
/// </summary>
|
||||
/// <param name="connection">An open connection to the target database.</param>
|
||||
/// <returns>A task that completes when all users have been seeded.</returns>
|
||||
public async Task SeedAsync(SqlConnection connection)
|
||||
{
|
||||
var generator = new PasswordGenerator();
|
||||
var rng = new Random();
|
||||
PasswordGenerator generator = new();
|
||||
Random rng = new();
|
||||
int createdUsers = 0;
|
||||
int createdCredentials = 0;
|
||||
int createdVerifications = 0;
|
||||
@@ -147,8 +147,8 @@ internal class UserSeeder : ISeeder
|
||||
const string firstName = "Test";
|
||||
const string lastName = "User";
|
||||
const string email = "test.user@thebiergarten.app";
|
||||
var dob = new DateTime(1985, 03, 01);
|
||||
var hash = GeneratePasswordHash("password");
|
||||
DateTime dob = new(1985, 03, 01);
|
||||
string hash = GeneratePasswordHash("password");
|
||||
|
||||
await RegisterUserAsync(
|
||||
connection,
|
||||
@@ -160,24 +160,24 @@ internal class UserSeeder : ISeeder
|
||||
hash
|
||||
);
|
||||
}
|
||||
foreach (var (firstName, lastName) in SeedNames)
|
||||
foreach ((string firstName, string lastName) in SeedNames)
|
||||
{
|
||||
// prepare user fields
|
||||
var username = $"{firstName[0]}.{lastName}";
|
||||
var email = $"{firstName}.{lastName}@thebiergarten.app";
|
||||
var dob = GenerateDateOfBirth(rng);
|
||||
string username = $"{firstName[0]}.{lastName}";
|
||||
string email = $"{firstName}.{lastName}@thebiergarten.app";
|
||||
DateTime dob = GenerateDateOfBirth(rng);
|
||||
|
||||
// generate a password and hash it
|
||||
string pwd = generator.Generate(
|
||||
length: 64,
|
||||
numberOfDigits: 10,
|
||||
numberOfSymbols: 10
|
||||
64,
|
||||
10,
|
||||
10
|
||||
);
|
||||
string hash = GeneratePasswordHash(pwd);
|
||||
|
||||
|
||||
// register the user (creates account + credential)
|
||||
var id = await RegisterUserAsync(
|
||||
Guid id = await RegisterUserAsync(
|
||||
connection,
|
||||
username,
|
||||
firstName,
|
||||
@@ -203,8 +203,8 @@ internal class UserSeeder : ISeeder
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a new user account and its credential by invoking the
|
||||
/// <c>dbo.USP_RegisterUser</c> stored procedure.
|
||||
/// Registers a new user account and its credential by invoking the
|
||||
/// <c>dbo.USP_RegisterUser</c> stored procedure.
|
||||
/// </summary>
|
||||
/// <param name="connection">An open connection to the target database.</param>
|
||||
/// <param name="username">The unique username for the account.</param>
|
||||
@@ -212,8 +212,8 @@ internal class UserSeeder : ISeeder
|
||||
/// <param name="lastName">The user's last name.</param>
|
||||
/// <param name="dateOfBirth">The user's date of birth.</param>
|
||||
/// <param name="email">The user's email address.</param>
|
||||
/// <param name="hash">The salted password hash, as produced by <see cref="GeneratePasswordHash"/>.</param>
|
||||
/// <returns>The newly created user account's <see cref="Guid"/> identifier.</returns>
|
||||
/// <param name="hash">The salted password hash, as produced by <see cref="GeneratePasswordHash" />.</param>
|
||||
/// <returns>The newly created user account's <see cref="Guid" /> identifier.</returns>
|
||||
private static async Task<Guid> RegisterUserAsync(
|
||||
SqlConnection connection,
|
||||
string username,
|
||||
@@ -224,7 +224,7 @@ internal class UserSeeder : ISeeder
|
||||
string hash
|
||||
)
|
||||
{
|
||||
await using var command = new SqlCommand("dbo.USP_RegisterUser", connection);
|
||||
await using SqlCommand command = new("dbo.USP_RegisterUser", connection);
|
||||
command.CommandType = CommandType.StoredProcedure;
|
||||
|
||||
|
||||
@@ -235,16 +235,16 @@ internal class UserSeeder : ISeeder
|
||||
command.Parameters.Add("@Email", SqlDbType.VarChar, 128).Value = email;
|
||||
command.Parameters.Add("@Hash", SqlDbType.NVarChar, -1).Value = hash;
|
||||
|
||||
var result = await command.ExecuteScalarAsync();
|
||||
object? result = await command.ExecuteScalarAsync();
|
||||
|
||||
|
||||
return (Guid)result!;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hashes a plaintext password using Argon2id with a randomly generated 16-byte
|
||||
/// salt, a degree of parallelism matching the available processor count, 64 MB of
|
||||
/// memory, and 4 iterations.
|
||||
/// Hashes a plaintext password using Argon2id with a randomly generated 16-byte
|
||||
/// salt, a degree of parallelism matching the available processor count, 64 MB of
|
||||
/// memory, and 4 iterations.
|
||||
/// </summary>
|
||||
/// <param name="pwd">The plaintext password to hash.</param>
|
||||
/// <returns>A string containing the base64-encoded salt and hash, separated by a colon.</returns>
|
||||
@@ -252,12 +252,12 @@ internal class UserSeeder : ISeeder
|
||||
{
|
||||
byte[] salt = RandomNumberGenerator.GetBytes(16);
|
||||
|
||||
var argon2 = new Argon2id(Encoding.UTF8.GetBytes(pwd))
|
||||
Argon2id argon2 = new(Encoding.UTF8.GetBytes(pwd))
|
||||
{
|
||||
Salt = salt,
|
||||
DegreeOfParallelism = Math.Max(Environment.ProcessorCount, 1),
|
||||
MemorySize = 65536,
|
||||
Iterations = 4,
|
||||
Iterations = 4
|
||||
};
|
||||
|
||||
byte[] hash = argon2.GetBytes(32);
|
||||
@@ -278,9 +278,9 @@ internal class UserSeeder : ISeeder
|
||||
FROM dbo.UserVerification
|
||||
WHERE UserAccountId = @UserAccountId;
|
||||
""";
|
||||
await using var command = new SqlCommand(sql, connection);
|
||||
await using SqlCommand command = new(sql, connection);
|
||||
command.Parameters.AddWithValue("@UserAccountId", userAccountId);
|
||||
var result = await command.ExecuteScalarAsync();
|
||||
object? result = await command.ExecuteScalarAsync();
|
||||
return result is not null;
|
||||
}
|
||||
|
||||
@@ -293,7 +293,7 @@ internal class UserSeeder : ISeeder
|
||||
Guid userAccountId
|
||||
)
|
||||
{
|
||||
await using var command = new SqlCommand(
|
||||
await using SqlCommand command = new(
|
||||
"dbo.USP_CreateUserVerification",
|
||||
connection
|
||||
);
|
||||
@@ -304,8 +304,8 @@ internal class UserSeeder : ISeeder
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a random date of birth corresponding to an age between 19 and 48
|
||||
/// years (inclusive), with a random day offset within that birth year.
|
||||
/// Generates a random date of birth corresponding to an age between 19 and 48
|
||||
/// years (inclusive), with a random day offset within that birth year.
|
||||
/// </summary>
|
||||
/// <param name="random">The random number source to use.</param>
|
||||
/// <returns>A randomly generated date of birth.</returns>
|
||||
@@ -316,4 +316,4 @@ internal class UserSeeder : ISeeder
|
||||
int offsetDays = random.Next(0, 365);
|
||||
return baseDate.AddDays(-offsetDays);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user