This commit is contained in:
Aaron Po
2026-08-04 06:22:19 -04:00
parent 7a0620337d
commit 48ee6b0fef
19 changed files with 117 additions and 109 deletions

View File

@@ -0,0 +1,13 @@
{
"version": 1,
"isRoot": true,
"tools": {
"csharpier": {
"version": "1.3.0",
"commands": [
"csharpier"
],
"rollForward": false
}
}
}

View File

@@ -1,37 +1,35 @@
<Solution>
<Folder Name="/API/">
<Project Path="API/API.Core/API.Core.csproj"/>
<Project Path="API/API.Specs/API.Specs.csproj"/>
<Project Path="API/API.Core/API.Core.csproj" />
<Project Path="API/API.Specs/API.Specs.csproj" />
</Folder>
<Folder Name="/Database/">
<Project Path="Database/Database.Migrations/Database.Migrations.csproj"/>
<Project Path="Database/Database.Seed/Database.Seed.csproj"/>
<Project Path="Database/Database.Migrations/Database.Migrations.csproj" />
<Project Path="Database/Database.Seed/Database.Seed.csproj" />
</Folder>
<Folder Name="/Domain/">
<Project Path="Domain/Domain.Entities/Domain.Entities.csproj"/>
<Project Path="Domain/Domain.Exceptions/Domain.Exceptions.csproj"/>
<Project Path="Domain/Domain.Entities/Domain.Entities.csproj" />
<Project Path="Domain/Domain.Exceptions/Domain.Exceptions.csproj" />
</Folder>
<Folder Name="/Infrastructure/">
<Project Path="Infrastructure/Infrastructure.Email/Infrastructure.Email.csproj"/>
<Project
Path="Infrastructure/Infrastructure.Email.Templates/Infrastructure.Email.Templates.csproj"/>
<Project Path="Infrastructure/Infrastructure.Jwt/Infrastructure.Jwt.csproj"/>
<Project
Path="Infrastructure/Infrastructure.PasswordHashing/Infrastructure.PasswordHashing.csproj"/>
<Project Path="Infrastructure/Infrastructure.Sql/Infrastructure.Sql.csproj"/>
<Project Path="Infrastructure/Infrastructure.Email/Infrastructure.Email.csproj" />
<Project Path="Infrastructure/Infrastructure.Email.Templates/Infrastructure.Email.Templates.csproj" />
<Project Path="Infrastructure/Infrastructure.Jwt/Infrastructure.Jwt.csproj" />
<Project Path="Infrastructure/Infrastructure.PasswordHashing/Infrastructure.PasswordHashing.csproj" />
<Project Path="Infrastructure/Infrastructure.Sql/Infrastructure.Sql.csproj" />
</Folder>
<Folder Name="/Features/">
<Project Path="Features/Features.Breweries/Features.Breweries.csproj"/>
<Project Path="Features/Features.Breweries.Tests/Features.Breweries.Tests.csproj"/>
<Project Path="Features/Features.UserManagement/Features.UserManagement.csproj"/>
<Project Path="Features/Features.UserManagement.Tests/Features.UserManagement.Tests.csproj"/>
<Project Path="Features/Features.Auth/Features.Auth.csproj"/>
<Project Path="Features/Features.Auth.Tests/Features.Auth.Tests.csproj"/>
<Project Path="Features/Features.Emails/Features.Emails.csproj"/>
<Project Path="Features/Features.Emails.Tests/Features.Emails.Tests.csproj"/>
<Project Path="Features/Features.Breweries/Features.Breweries.csproj" />
<Project Path="Features/Features.Breweries.Tests/Features.Breweries.Tests.csproj" />
<Project Path="Features/Features.UserManagement/Features.UserManagement.csproj" />
<Project Path="Features/Features.UserManagement.Tests/Features.UserManagement.Tests.csproj" />
<Project Path="Features/Features.Auth/Features.Auth.csproj" />
<Project Path="Features/Features.Auth.Tests/Features.Auth.Tests.csproj" />
<Project Path="Features/Features.Emails/Features.Emails.csproj" />
<Project Path="Features/Features.Emails.Tests/Features.Emails.Tests.csproj" />
</Folder>
<Folder Name="/Shared/">
<Project Path="Shared/Shared.Contracts/Shared.Contracts.csproj"/>
<Project Path="Shared/Shared.Application/Shared.Application.csproj"/>
<Project Path="Shared/Shared.Contracts/Shared.Contracts.csproj" />
<Project Path="Shared/Shared.Application/Shared.Application.csproj" />
</Folder>
</Solution>

View File

@@ -180,8 +180,7 @@ public static class Program
!bool.TryParse(
Environment.GetEnvironmentVariable("DB_TRUST_SERVER_CERTIFICATE"),
out bool parsedTrust
)
|| parsedTrust;
) || parsedTrust;
bool clearDatabase = string.Equals(
Environment.GetEnvironmentVariable("CLEAR_DATABASE"),
@@ -204,7 +203,7 @@ public static class Program
UserID = user,
Password = password,
Encrypt = true,
TrustServerCertificate = trustServerCertificate
TrustServerCertificate = trustServerCertificate,
}.ConnectionString;
}

View File

@@ -21,8 +21,7 @@
<ItemGroup>
<ProjectReference Include="..\..\Domain\Domain.Entities\Domain.Entities.csproj" />
<ProjectReference
Include="..\..\Infrastructure\Infrastructure.Sql\Infrastructure.Sql.csproj" />
<ProjectReference Include="..\..\Infrastructure\Infrastructure.Sql\Infrastructure.Sql.csproj" />
<ProjectReference Include="..\..\Features\Features.Breweries\Features.Breweries.csproj" />
<ProjectReference Include="..\..\Features\Features.Auth\Features.Auth.csproj" />
</ItemGroup>
@@ -30,7 +29,9 @@
<ItemGroup>
<!-- Pipeline-generated seed data, copied to the output directory so it can be opened via
ConnectionStrings.SqliteConnectionString at runtime. -->
<None CopyToOutputDirectory="PreserveNewest"
Include="SeedData\biergarten_seed_2026-07-20T05-46-02.993511Z.sqlite" />
<None
CopyToOutputDirectory="PreserveNewest"
Include="SeedData\biergarten_seed_2026-07-20T05-46-02.993511Z.sqlite"
/>
</ItemGroup>
</Project>

View File

@@ -9,7 +9,8 @@ namespace Database.Seed.DatabaseHelpers;
public class ConnectionStrings
{
public static string SqlServerConnectionString => GetSqlServerConnectionString();
public static string SqliteConnectionString => "Data Source=SeedData/biergarten_seed_2026-07-20T05-46-02.993511Z.sqlite";
public static string SqliteConnectionString =>
"Data Source=SeedData/biergarten_seed_2026-07-20T05-46-02.993511Z.sqlite";
private static string GetSqlServerConnectionString()
{
@@ -39,7 +40,7 @@ public class ConnectionStrings
UserID = user,
Password = password,
TrustServerCertificate = bool.Parse(trustServerCertificate),
Encrypt = true
Encrypt = true,
};
return builder.ConnectionString;

View File

@@ -1,23 +1,20 @@
using Database.Seed.DatabaseHelpers;
using Database.Seed.PipelineData;
using Database.Seed.Sqlite;
using Domain.Entities;
using Features.Auth.DependencyInjection;
using Features.Auth.Dtos;
using Features.Auth.Repository;
using Features.Breweries.DependencyInjection;
using Features.Breweries.Repository;
using idunno.Password;
using Infrastructure.PasswordHashing;
using Infrastructure.Sql;
using Microsoft.Data.Sqlite;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using idunno.Password;
using Infrastructure.PasswordHashing;
using Domain.Entities;
async static Task<int> main()
static async Task<int> main()
{
IReadOnlyList<BreweryRecord> breweries;
IReadOnlyList<UserRecord> users;
@@ -61,16 +58,15 @@ async static Task<int> main()
}
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine(
$"Loaded {breweries.Count} breweries."
);
Console.WriteLine($"Loaded {breweries.Count} breweries.");
Console.ResetColor();
for (int i = 0; i < breweries.Count; i++)
{
BreweryRecord brewery = breweries[i];
Console.WriteLine(
$"{i + 1}:\t{brewery.Brewery.NameEn}\t({brewery.Address.City.CityName}, {brewery.Address.City.Country})");
$"{i + 1}:\t{brewery.Brewery.NameEn}\t({brewery.Address.City.CityName}, {brewery.Address.City.Country})"
);
}
Console.ForegroundColor = ConsoleColor.Green;
@@ -87,7 +83,11 @@ async static Task<int> main()
// Generate a password that is 64 characters long with 10 digits, 10 symbols,
// allowing upper and lower case letters, disallowing repeat characters.
string generatedPassword = PasswordGenerator.Generate(length: 64, numberOfDigits: 10, numberOfSymbols: 10);
string generatedPassword = PasswordGenerator.Generate(
length: 64,
numberOfDigits: 10,
numberOfSymbols: 10
);
string hashedPassword = passwordInfrastructure.Hash(generatedPassword);
UserRegistrationDto registrationDto = new(
@@ -102,7 +102,9 @@ async static Task<int> main()
try
{
UserAccount createdUser = await authRepository.RegisterUserAsync(registrationDto);
Console.WriteLine($"Created user: {createdUser.Username} ({createdUser.UserAccountId})");
Console.WriteLine(
$"Created user: {createdUser.Username} ({createdUser.UserAccountId})"
);
}
catch (Exception ex)
{
@@ -110,11 +112,9 @@ async static Task<int> main()
Console.WriteLine($"Error creating user {username}: {ex.Message}");
Console.ResetColor();
}
}
return 0;
}
return await main();

View File

@@ -44,13 +44,13 @@ public sealed class PipelineSeedDataReader
NameEn = reader.GetString(0),
DescriptionEn = reader.GetString(1),
NameLocal = reader.GetString(2),
DescriptionLocal = reader.GetString(3)
DescriptionLocal = reader.GetString(3),
},
Address = new BreweryAddress
{
City = cities[reader.GetInt64(4)],
PostalCode = reader.GetString(5)
}
PostalCode = reader.GetString(5),
},
}
);
@@ -87,15 +87,15 @@ public sealed class PipelineSeedDataReader
Gender = reader.GetString(2),
Username = reader.GetString(3),
Bio = reader.GetString(4),
ActivityWeight = (float)reader.GetDouble(5)
ActivityWeight = (float)reader.GetDouble(5),
},
Email = reader.GetString(6),
DateOfBirth = reader.GetString(7),
Address = new UserAddress
{
City = cities[reader.GetInt64(8)],
PostalCode = reader.GetString(9)
}
PostalCode = reader.GetString(9),
},
}
);
@@ -144,14 +144,13 @@ public sealed class PipelineSeedDataReader
PostalCode = new PostalCodeSpec
{
CountryFormatRegex = reader.GetString(countryFormatRegexBindIndex),
CityRegexes = DeserializeStringArray(reader.GetString(cityRegexesBindIndex))
}
CityRegexes = DeserializeStringArray(reader.GetString(cityRegexesBindIndex)),
},
};
return cities;
}
private static IReadOnlyList<string> DeserializeStringArray(string json)
{
return JsonSerializer.Deserialize<List<string>>(json) ?? [];

View File

@@ -1,6 +1,5 @@
namespace Database.Seed;
public interface IExportDatabase
{
void SeedLocations(IEnumerable<Location> locations);

View File

@@ -34,12 +34,14 @@ public class RegisterUserHandler(
string hashed = passwordInfrastructure.Hash(request.Password);
UserAccount createdUser = await authRepo.RegisterUserAsync(
new UserRegistrationDto(request.Username,
new UserRegistrationDto(
request.Username,
request.FirstName,
request.LastName,
request.Email,
request.DateOfBirth,
hashed)
hashed
)
);
string accessToken = tokenService.GenerateAccessToken(createdUser);

View File

@@ -26,9 +26,7 @@ public class AuthRepository(ISqlConnectionFactory connectionFactory)
/// subsequent lookup to fail.
/// </remarks>
/// <exception cref="Exception">Thrown when the newly registered user cannot be retrieved after registration.</exception>
public async Task<UserAccount> RegisterUserAsync(
UserRegistrationDto userRegistrationDto
)
public async Task<UserAccount> RegisterUserAsync(UserRegistrationDto userRegistrationDto)
{
await using DbConnection connection = await CreateConnection();
await using DbCommand command = connection.CreateCommand();
@@ -36,8 +34,7 @@ public class AuthRepository(ISqlConnectionFactory connectionFactory)
command.CommandText = "USP_RegisterUser";
command.CommandType = CommandType.StoredProcedure;
var (username, firstName, lastName, email, dateOfBirth, passwordHash) =
userRegistrationDto;
var (username, firstName, lastName, email, dateOfBirth, passwordHash) = userRegistrationDto;
AddParameter(command, "@Username", username);
AddParameter(command, "@FirstName", firstName);

View File

@@ -2,14 +2,13 @@ using Domain.Entities;
using Features.Auth.Dtos;
namespace Features.Auth.Repository;
/// <summary>
/// Repository for authentication-related database operations including user registration and credential management.
/// </summary>
public interface IAuthRepository
{
Task<UserAccount> RegisterUserAsync(
UserRegistrationDto userRegistrationDto
);
Task<UserAccount> RegisterUserAsync(UserRegistrationDto userRegistrationDto);
Task<UserAccount?> GetUserByEmailAsync(string email);