formatting

This commit is contained in:
Aaron Po
2026-06-20 15:09:40 -04:00
parent 254431928f
commit cd99c1fbc3
131 changed files with 1306 additions and 1447 deletions

View File

@@ -6,21 +6,15 @@
</PropertyGroup>
<ItemGroup>
<SupportedPlatform Include="browser"/>
<SupportedPlatform Include="browser" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="10.0.1" />
<PackageReference
Include="Microsoft.AspNetCore.Components.Web"
Version="10.0.1"
/>
<PackageReference
Include="Microsoft.Extensions.DependencyInjection.Abstractions"
Version="10.0.1"
/>
<PackageReference
Include="Microsoft.Extensions.Logging.Abstractions"
Version="10.0.1"
Include="Microsoft.Extensions.DependencyInjection.Abstractions"
Version="10.0.1"
/>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.1" />
</ItemGroup>
</Project>

View File

@@ -14,10 +14,8 @@ namespace Infrastructure.Email.Templates.Rendering;
/// pipeline.
/// </param>
/// <param name="loggerFactory">The logger factory passed to the <see cref="HtmlRenderer" /> used to render components.</param>
public class EmailTemplateProvider(
IServiceProvider serviceProvider,
ILoggerFactory loggerFactory
) : IEmailTemplateProvider
public class EmailTemplateProvider(IServiceProvider serviceProvider, ILoggerFactory loggerFactory)
: IEmailTemplateProvider
{
/// <summary>
/// Renders the UserRegisteredEmail template with the specified parameters.
@@ -33,7 +31,7 @@ public class EmailTemplateProvider(
Dictionary<string, object?> parameters = new()
{
{ nameof(UserRegistration.Username), username },
{ nameof(UserRegistration.ConfirmationLink), confirmationLink }
{ nameof(UserRegistration.ConfirmationLink), confirmationLink },
};
return await RenderComponentAsync<UserRegistration>(parameters);
@@ -53,7 +51,7 @@ public class EmailTemplateProvider(
Dictionary<string, object?> parameters = new()
{
{ nameof(ResendConfirmation.Username), username },
{ nameof(ResendConfirmation.ConfirmationLink), confirmationLink }
{ nameof(ResendConfirmation.ConfirmationLink), confirmationLink },
};
return await RenderComponentAsync<ResendConfirmation>(parameters);
@@ -72,10 +70,7 @@ public class EmailTemplateProvider(
)
where TComponent : IComponent
{
await using HtmlRenderer htmlRenderer = new(
serviceProvider,
loggerFactory
);
await using HtmlRenderer htmlRenderer = new(serviceProvider, loggerFactory);
string html = await htmlRenderer.Dispatcher.InvokeAsync(async () =>
{
@@ -89,4 +84,4 @@ public class EmailTemplateProvider(
return html;
}
}
}

View File

@@ -11,10 +11,7 @@ public interface IEmailTemplateProvider
/// <param name="username">The username to include in the email</param>
/// <param name="confirmationLink">The email confirmation link</param>
/// <returns>The rendered HTML string</returns>
Task<string> RenderUserRegisteredEmailAsync(
string username,
string confirmationLink
);
Task<string> RenderUserRegisteredEmailAsync(string username, string confirmationLink);
/// <summary>
/// Renders the ResendConfirmation template with the specified parameters.
@@ -22,8 +19,5 @@ public interface IEmailTemplateProvider
/// <param name="username">The username to include in the email</param>
/// <param name="confirmationLink">The new confirmation link</param>
/// <returns>The rendered HTML string</returns>
Task<string> RenderResendConfirmationEmailAsync(
string username,
string confirmationLink
);
}
Task<string> RenderResendConfirmationEmailAsync(string username, string confirmationLink);
}

View File

@@ -21,10 +21,5 @@ public interface IEmailProvider
/// <param name="subject">Email subject line</param>
/// <param name="body">Email body (HTML or plain text)</param>
/// <param name="isHtml">Whether the body is HTML (default: true)</param>
Task SendAsync(
IEnumerable<string> to,
string subject,
string body,
bool isHtml = true
);
}
Task SendAsync(IEnumerable<string> to, string subject, string body, bool isHtml = true);
}

View File

@@ -7,6 +7,6 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MailKit" Version="4.15.1"/>
<PackageReference Include="MailKit" Version="4.15.1" />
</ItemGroup>
</Project>

View File

@@ -58,22 +58,16 @@ public class SmtpEmailProvider : IEmailProvider
{
_host =
Environment.GetEnvironmentVariable("SMTP_HOST")
?? throw new InvalidOperationException(
"SMTP_HOST environment variable is not set"
);
?? throw new InvalidOperationException("SMTP_HOST environment variable is not set");
string portString =
Environment.GetEnvironmentVariable("SMTP_PORT") ?? "587";
string portString = Environment.GetEnvironmentVariable("SMTP_PORT") ?? "587";
if (!int.TryParse(portString, out _port))
throw new InvalidOperationException(
$"SMTP_PORT '{portString}' is not a valid integer"
);
throw new InvalidOperationException($"SMTP_PORT '{portString}' is not a valid integer");
_username = Environment.GetEnvironmentVariable("SMTP_USERNAME");
_password = Environment.GetEnvironmentVariable("SMTP_PASSWORD");
string useSslString =
Environment.GetEnvironmentVariable("SMTP_USE_SSL") ?? "true";
string useSslString = Environment.GetEnvironmentVariable("SMTP_USE_SSL") ?? "true";
_useSsl = bool.Parse(useSslString);
_fromEmail =
@@ -82,9 +76,7 @@ public class SmtpEmailProvider : IEmailProvider
"SMTP_FROM_EMAIL environment variable is not set"
);
_fromName =
Environment.GetEnvironmentVariable("SMTP_FROM_NAME")
?? "The Biergarten";
_fromName = Environment.GetEnvironmentVariable("SMTP_FROM_NAME") ?? "The Biergarten";
}
/// <summary>
@@ -95,12 +87,7 @@ public class SmtpEmailProvider : IEmailProvider
/// <param name="body">Email body (HTML or plain text)</param>
/// <param name="isHtml">Whether the body is HTML (default: true)</param>
/// <exception cref="InvalidOperationException">Thrown when connecting, authenticating, or sending via SMTP fails.</exception>
public async Task SendAsync(
string to,
string subject,
string body,
bool isHtml = true
)
public async Task SendAsync(string to, string subject, string body, bool isHtml = true)
{
await SendAsync([to], subject, body, isHtml);
}
@@ -128,7 +115,8 @@ public class SmtpEmailProvider : IEmailProvider
MimeMessage message = new();
message.From.Add(new MailboxAddress(_fromName, _fromEmail));
foreach (string recipient in to) message.To.Add(MailboxAddress.Parse(recipient));
foreach (string recipient in to)
message.To.Add(MailboxAddress.Parse(recipient));
message.Subject = subject;
@@ -152,10 +140,7 @@ public class SmtpEmailProvider : IEmailProvider
await client.ConnectAsync(_host, _port, secureSocketOptions);
// Authenticate if credentials are provided
if (
!string.IsNullOrEmpty(_username)
&& !string.IsNullOrEmpty(_password)
)
if (!string.IsNullOrEmpty(_username) && !string.IsNullOrEmpty(_password))
await client.AuthenticateAsync(_username, _password);
await client.SendAsync(message);
@@ -163,10 +148,7 @@ public class SmtpEmailProvider : IEmailProvider
}
catch (Exception ex)
{
throw new InvalidOperationException(
$"Failed to send email: {ex.Message}",
ex
);
throw new InvalidOperationException($"Failed to send email: {ex.Message}", ex);
}
}
}
}

View File

@@ -15,12 +15,7 @@ public interface ITokenInfrastructure
/// <param name="expiry">The date and time at which the token expires.</param>
/// <param name="secret">The symmetric secret used to sign the token.</param>
/// <returns>The serialized, signed JWT string.</returns>
string GenerateJwt(
Guid userId,
string username,
DateTime expiry,
string secret
);
string GenerateJwt(Guid userId, string username, DateTime expiry, string secret);
/// <summary>
/// Validates a JWT and returns the resulting claims principal.
@@ -33,4 +28,4 @@ public interface ITokenInfrastructure
/// validation.
/// </exception>
Task<ClaimsPrincipal> ValidateJwtAsync(string token, string secret);
}
}

View File

@@ -7,17 +7,11 @@
</PropertyGroup>
<ItemGroup>
<PackageReference
Include="Microsoft.IdentityModel.JsonWebTokens"
Version="8.2.1"
/>
<PackageReference
Include="System.IdentityModel.Tokens.Jwt"
Version="8.2.1"
/>
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.2.1" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.2.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Domain\Domain.Exceptions\Domain.Exceptions.csproj"/>
<ProjectReference Include="..\..\Domain\Domain.Exceptions\Domain.Exceptions.csproj" />
</ItemGroup>
</Project>

View File

@@ -25,12 +25,7 @@ public class JwtInfrastructure : ITokenInfrastructure
/// <param name="expiry">The date and time at which the token expires.</param>
/// <param name="secret">The symmetric secret used to sign the token (encoded as UTF-8 bytes).</param>
/// <returns>The serialized, signed JWT string.</returns>
public string GenerateJwt(
Guid userId,
string username,
DateTime expiry,
string secret
)
public string GenerateJwt(Guid userId, string username, DateTime expiry, string secret)
{
JsonWebTokenHandler handler = new();
byte[] key = Encoding.UTF8.GetBytes(secret);
@@ -46,7 +41,7 @@ public class JwtInfrastructure : ITokenInfrastructure
JwtRegisteredClaimNames.Exp,
new DateTimeOffset(expiry).ToUnixTimeSeconds().ToString()
),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
};
SecurityTokenDescriptor tokenDescriptor = new()
@@ -56,13 +51,12 @@ public class JwtInfrastructure : ITokenInfrastructure
SigningCredentials = new SigningCredentials(
new SymmetricSecurityKey(key),
SecurityAlgorithms.HmacSha256
)
),
};
return handler.CreateToken(tokenDescriptor);
}
/// <summary>
/// Validates a JWT's signature and lifetime (issuer and audience validation are disabled),
/// using the provided secret as the HMAC-SHA256 symmetric signing key.
@@ -74,21 +68,16 @@ public class JwtInfrastructure : ITokenInfrastructure
/// Thrown when the token is invalid, has no claims identity, is expired, or otherwise fails validation
/// (including when validation itself throws, e.g. due to a malformed token or signature mismatch).
/// </exception>
public async Task<ClaimsPrincipal> ValidateJwtAsync(
string token,
string secret
)
public async Task<ClaimsPrincipal> ValidateJwtAsync(string token, string secret)
{
JsonWebTokenHandler handler = new();
byte[] keyBytes = Encoding.UTF8.GetBytes(
secret
);
byte[] keyBytes = Encoding.UTF8.GetBytes(secret);
TokenValidationParameters parameters = new()
{
ValidateIssuer = false,
ValidateAudience = false,
ValidateLifetime = true,
IssuerSigningKey = new SymmetricSecurityKey(keyBytes)
IssuerSigningKey = new SymmetricSecurityKey(keyBytes),
};
try
@@ -104,4 +93,4 @@ public class JwtInfrastructure : ITokenInfrastructure
throw new UnauthorizedException("Invalid token");
}
}
}
}

View File

@@ -32,7 +32,7 @@ public class Argon2Infrastructure : IPasswordInfrastructure
Salt = salt,
DegreeOfParallelism = Math.Max(Environment.ProcessorCount, 1),
MemorySize = ArgonMemoryKb,
Iterations = ArgonIterations
Iterations = ArgonIterations,
};
byte[] hash = argon2.GetBytes(HashSize);
@@ -57,10 +57,7 @@ public class Argon2Infrastructure : IPasswordInfrastructure
{
try
{
string[] parts = stored.Split(
':',
StringSplitOptions.RemoveEmptyEntries
);
string[] parts = stored.Split(':', StringSplitOptions.RemoveEmptyEntries);
if (parts.Length != 2)
return false;
@@ -72,7 +69,7 @@ public class Argon2Infrastructure : IPasswordInfrastructure
Salt = salt,
DegreeOfParallelism = Math.Max(Environment.ProcessorCount, 1),
MemorySize = ArgonMemoryKb,
Iterations = ArgonIterations
Iterations = ArgonIterations,
};
byte[] actual = argon2.GetBytes(expected.Length);
@@ -83,4 +80,4 @@ public class Argon2Infrastructure : IPasswordInfrastructure
return false;
}
}
}
}

View File

@@ -19,4 +19,4 @@ public interface IPasswordInfrastructure
/// <param name="stored">The stored salt/hash string previously produced by <see cref="Hash" />.</param>
/// <returns><c>true</c> if the password matches the stored hash; otherwise <c>false</c>.</returns>
public bool Verify(string password, string stored);
}
}

View File

@@ -7,9 +7,6 @@
</PropertyGroup>
<ItemGroup>
<PackageReference
Include="Konscious.Security.Cryptography.Argon2"
Version="1.3.1"
/>
<PackageReference Include="Konscious.Security.Cryptography.Argon2" Version="1.3.1" />
</ItemGroup>
</Project>

View File

@@ -9,12 +9,9 @@ namespace Infrastructure.Sql;
/// resolving the connection string from environment variables or application configuration.
/// </summary>
/// <param name="configuration">The application configuration, used as a fallback source for the connection string.</param>
public class DefaultSqlConnectionFactory(IConfiguration configuration)
: ISqlConnectionFactory
public class DefaultSqlConnectionFactory(IConfiguration configuration) : ISqlConnectionFactory
{
private readonly string _connectionString = GetConnectionString(
configuration
);
private readonly string _connectionString = GetConnectionString(configuration);
/// <summary>
/// Creates a new, unopened <see cref="SqlConnection" /> using the resolved connection string.
@@ -39,10 +36,9 @@ public class DefaultSqlConnectionFactory(IConfiguration configuration)
private static string GetConnectionString(IConfiguration configuration)
{
// Check for full connection string first
string? fullConnectionString = Environment.GetEnvironmentVariable(
"DB_CONNECTION_STRING"
);
if (!string.IsNullOrEmpty(fullConnectionString)) return fullConnectionString;
string? fullConnectionString = Environment.GetEnvironmentVariable("DB_CONNECTION_STRING");
if (!string.IsNullOrEmpty(fullConnectionString))
return fullConnectionString;
// Try to build from individual environment variables (preferred method for Docker)
try
@@ -53,11 +49,12 @@ public class DefaultSqlConnectionFactory(IConfiguration configuration)
{
// Fall back to configuration-based connection string if env vars are not set
string? connString = configuration.GetConnectionString("Default");
if (!string.IsNullOrEmpty(connString)) return connString;
if (!string.IsNullOrEmpty(connString))
return connString;
throw new InvalidOperationException(
"Database connection string not configured. Set DB_CONNECTION_STRING or DB_SERVER, DB_NAME, DB_USER, DB_PASSWORD env vars or ConnectionStrings:Default."
);
}
}
}
}

View File

@@ -12,4 +12,4 @@ public interface ISqlConnectionFactory
/// </summary>
/// <returns>A new <see cref="DbConnection" /> instance.</returns>
DbConnection CreateConnection();
}
}

View File

@@ -6,15 +6,12 @@
<RootNamespace>Infrastructure.Sql</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Data.SqlClient" Version="6.1.4"/>
<PackageReference Include="Microsoft.Data.SqlClient" Version="6.1.4" />
<PackageReference Include="Microsoft.SqlServer.Types" Version="160.1000.6" />
<PackageReference Include="System.Data.SqlClient" Version="4.9.0" />
<PackageReference
Include="Microsoft.SqlServer.Types"
Version="160.1000.6"
/>
<PackageReference Include="System.Data.SqlClient" Version="4.9.0"/>
<PackageReference
Include="Microsoft.Extensions.Configuration.Abstractions"
Version="9.0.0"
Include="Microsoft.Extensions.Configuration.Abstractions"
Version="9.0.0"
/>
</ItemGroup>
</Project>

View File

@@ -29,4 +29,4 @@ public abstract class Repository<T>(ISqlConnectionFactory connectionFactory)
/// <param name="reader">The data reader positioned on the row to map.</param>
/// <returns>The mapped entity instance.</returns>
protected abstract T MapToEntity(DbDataReader reader);
}
}

View File

@@ -17,28 +17,20 @@ public static class SqlConnectionStringHelper
{
string server =
Environment.GetEnvironmentVariable("DB_SERVER")
?? throw new InvalidOperationException(
"DB_SERVER environment variable is not set"
);
?? throw new InvalidOperationException("DB_SERVER environment variable is not set");
string dbName =
databaseName
?? Environment.GetEnvironmentVariable("DB_NAME")
?? throw new InvalidOperationException(
"DB_NAME environment variable is not set"
);
?? 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"
);
?? 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"
);
?? throw new InvalidOperationException("DB_PASSWORD environment variable is not set");
SqlConnectionStringBuilder builder = new()
{
@@ -47,7 +39,7 @@ public static class SqlConnectionStringHelper
UserID = user,
Password = password,
TrustServerCertificate = true,
Encrypt = true
Encrypt = true,
};
return builder.ConnectionString;
@@ -61,4 +53,4 @@ public static class SqlConnectionStringHelper
{
return BuildConnectionString("master");
}
}
}