Refactor comments and update documentation for backend and pipeline (#246)

* Remove restating comments in backend

* Remove restating comments in frontend stories

* Remove restating comments in pipeline

* Remove restating comments in pipeline scripts

* Restore location-clear comment in BreweryRepository

* Remove stale/restating comments found in second pass

* Restore mock curated-data branch comment in main.cc

* Remove more restating/filler comments in backend

* Document account-management endpoints, compose files, and env generator

* Squashed commit of the following:

commit a8a5b8abd9969859c695092fae6990d03b2135ed
Author: Aaron Po <aaron.po97@gmail.com>
Date:   Sun Aug 23 23:53:56 2026 -0400

    Add reverse geocoding for brewery street addresses (#245)

    * Add geocoding to seed application, remove postal codes from generator

    Update comments

* Update pipeline class diagram for OpenAI generator

* Update pipeline activity diagram for brewery coordinates and OpenAI backend

* Add mailpit, production, db-only, and minimal environments to deployment diagram

* Fix authentication flow diagram for Identity migration

* Add account-management flows to authentication diagram

* Fix database schema diagram note referencing removed AuthRepository
This commit is contained in:
2026-08-24 01:05:42 -04:00
committed by Aaron Po
parent cd5fd2d998
commit c825d99b0a
46 changed files with 1037 additions and 218 deletions

View File

@@ -39,7 +39,6 @@ public class JwtAuthenticationHandler(
if (string.IsNullOrWhiteSpace(secret))
return AuthenticateResult.Fail("JWT secret is not configured");
// Check if Authorization header exists
if (!Request.Headers.TryGetValue("Authorization", out StringValues authHeaderValue))
return AuthenticateResult.Fail("Authorization header is missing");

View File

@@ -11,13 +11,13 @@ namespace API.Core.Controllers;
/// </remarks>
[ApiController]
[ApiExplorerSettings(IgnoreApi = true)]
[Route("error")] // required
[Route("error")]
public class NotFoundController : ControllerBase
{
/// <summary>
/// Returns a 404 response for any request that did not match a route.
/// </summary>
[HttpGet("404")] //required
[HttpGet("404")]
public IActionResult Handle404()
{
return NotFound(new { message = "Route not found." });

View File

@@ -1,5 +1,3 @@
// API.Core/Filters/GlobalExceptionFilter.cs
using Domain.Exceptions;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;

View File

@@ -61,7 +61,6 @@ builder.Services.AddSwaggerGen(options =>
});
builder.Services.AddOpenApi();
// Add FluentValidation
builder.Services.AddValidatorsFromAssemblyContaining<Program>();
builder.Services.AddValidatorsFromAssembly(typeof(BreweryController).Assembly);
builder.Services.AddValidatorsFromAssembly(typeof(UserController).Assembly);
@@ -80,7 +79,6 @@ builder.Services.AddMediatR(cfg =>
cfg.AddOpenBehavior(typeof(ValidationBehavior<,>));
});
// Add health checks
builder.Services.AddHealthChecks();
// Configure logging for container output
@@ -89,8 +87,6 @@ builder.Logging.AddConsole();
if (!builder.Environment.IsProduction())
builder.Logging.AddDebug();
// Configure Dependency Injection -------------------------------------------------------------------------------------
builder.Services.AddInfrastructureSql();
builder.Services.AddFeaturesBreweries();
@@ -101,10 +97,8 @@ builder.Services.AddFeaturesEmails();
// ITokenInfrastructure is registered here because JwtAuthenticationHandler depends on it directly.
builder.Services.AddScoped<ITokenInfrastructure, JwtInfrastructure>();
// Register the exception filter
builder.Services.AddScoped<GlobalExceptionFilter>();
// Configure JWT Authentication
builder
.Services.AddAuthentication("JWT")
.AddScheme<JwtAuthenticationOptions, JwtAuthenticationHandler>("JWT", options => { });
@@ -128,7 +122,6 @@ app.MapHealthChecks("/health");
app.MapControllers();
app.MapFallbackToController("Handle404", "NotFound");
// Graceful shutdown handling
IHostApplicationLifetime lifetime = app.Services.GetRequiredService<IHostApplicationLifetime>();
lifetime.ApplicationStopping.Register(() =>
{

View File

@@ -264,7 +264,6 @@ public class AuthSteps(ScenarioContext scenario)
public async Task WhenISubmitALoginRequestUsingAgetRequest()
{
HttpClient client = GetClient();
// testing GET
HttpRequestMessage requestMessage = new(HttpMethod.Get, "/api/auth/login")
{
Content = new StringContent("{}", Encoding.UTF8, "application/json"),
@@ -703,7 +702,6 @@ public class AuthSteps(ScenarioContext scenario)
[Given("I have an access token signed with the wrong secret")]
public void GivenIHaveAnAccessTokenSignedWithTheWrongSecret()
{
// Create a token with a different secret
scenario["accessToken"] =
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c";
}

View File

@@ -15,7 +15,6 @@ public class TestApiFactory : WebApplicationFactory<Program>
builder.ConfigureServices(services =>
{
// Replace the real email provider with mock for testing
ServiceDescriptor? emailProviderDescriptor = services.SingleOrDefault(d =>
d.ServiceType == typeof(IEmailProvider)
);
@@ -25,7 +24,6 @@ public class TestApiFactory : WebApplicationFactory<Program>
services.AddScoped<IEmailProvider, MockEmailProvider>();
// Replace the real email dispatcher with mock for testing
ServiceDescriptor? emailDispatcherDescriptor = services.SingleOrDefault(d =>
d.ServiceType == typeof(IEmailDispatcher)
);

View File

@@ -11,8 +11,6 @@ public class ConflictException(string message) : Exception(message);
/// </summary>
public class NotFoundException(string message) : Exception(message);
// Domain.Exceptions/UnauthorizedException.cs
/// <summary>
/// The exception that is thrown when authentication fails or is required. Maps to HTTP 401 Unauthorized.
/// </summary>

View File

@@ -62,12 +62,10 @@ public class TokenServiceRefreshTests
ApplicationUser user = new() { Id = userId, UserName = username };
// Mock the validation of refresh token
_tokenInfraMock
.Setup(x => x.ValidateJwtAsync(refreshToken, It.IsAny<string>()))
.ReturnsAsync(principal);
// Mock the generation of new tokens
_tokenInfraMock
.Setup(x => x.GenerateJwt(userId, username, It.IsAny<DateTime>(), It.IsAny<string>()))
.Returns(

View File

@@ -66,7 +66,6 @@ public class RegisterUserHandler(
{
await Console.Error.WriteLineAsync(ex.Message);
Console.WriteLine("Could not send email.");
// ignored
}
return new RegistrationPayload(user.Id, user.UserName, refreshToken, accessToken, emailSent);

View File

@@ -20,7 +20,6 @@ public class TokenService : ITokenService
private readonly ITokenInfrastructure _tokenInfrastructure;
private readonly UserManager<ApplicationUser> _userManager;
/// <summary>Initializes a new instance of the <see cref="TokenService" /> class.</summary>
/// <exception cref="InvalidOperationException">
/// Thrown when any of the <c>ACCESS_TOKEN_SECRET</c>, <c>REFRESH_TOKEN_SECRET</c>, or
/// <c>CONFIRMATION_TOKEN_SECRET</c> configuration values are not set.

View File

@@ -3,9 +3,6 @@ using MediatR;
namespace Features.UserManagement.Commands.UpdateUser;
/// <summary>
/// Handles <see cref="UpdateUserCommand" />.
/// </summary>
public class UpdateUserHandler(IUserAccountRepository repository)
: IRequestHandler<UpdateUserCommand>
{

View File

@@ -4,9 +4,6 @@ using MediatR;
namespace Features.UserManagement.Queries.GetAllUsers;
/// <summary>
/// Handles <see cref="GetAllUsersQuery" />.
/// </summary>
public class GetAllUsersHandler(IUserAccountRepository repository)
: IRequestHandler<GetAllUsersQuery, IEnumerable<UserAccount>>
{

View File

@@ -5,9 +5,6 @@ using MediatR;
namespace Features.UserManagement.Queries.GetUserById;
/// <summary>
/// Handles <see cref="GetUserByIdQuery" />.
/// </summary>
public class GetUserByIdHandler(IUserAccountRepository repository)
: IRequestHandler<GetUserByIdQuery, UserAccount>
{

View File

@@ -3,7 +3,4 @@ using MediatR;
namespace Features.UserManagement.Queries.GetUserById;
/// <summary>
/// Requests the user account with the given ID.
/// </summary>
public record GetUserByIdQuery(Guid UserAccountId) : IRequest<UserAccount>;

View File

@@ -93,14 +93,12 @@ public class SmtpEmailProvider : IEmailProvider
try
{
// Determine the SecureSocketOptions based on SSL setting
SecureSocketOptions secureSocketOptions = _useSsl
? SecureSocketOptions.StartTls
: SecureSocketOptions.None;
await client.ConnectAsync(_host, _port, secureSocketOptions);
// Authenticate if credentials are provided
if (!string.IsNullOrEmpty(_username) && !string.IsNullOrEmpty(_password))
await client.AuthenticateAsync(_username, _password);

View File

@@ -12,9 +12,6 @@ public interface ITokenInfrastructure
/// </summary>
string GenerateJwt(Guid userId, string username, DateTime expiry, string secret);
/// <summary>
/// Validates a JWT and returns the resulting claims principal.
/// </summary>
/// <exception cref="Domain.Exceptions.UnauthorizedException">Thrown when the token is invalid, expired, or fails validation.</exception>
Task<ClaimsPrincipal> ValidateJwtAsync(string token, string secret);
}

View File

@@ -26,7 +26,6 @@ public class DefaultSqlConnectionFactory(IConfiguration configuration) : ISqlCon
/// <exception cref="InvalidOperationException">Thrown when no connection string can be resolved from any source.</exception>
private static string GetConnectionString(IConfiguration configuration)
{
// Check for full connection string first
string? fullConnectionString = configuration["DB_CONNECTION_STRING"];
if (!string.IsNullOrEmpty(fullConnectionString))
return fullConnectionString;

View File

@@ -38,9 +38,6 @@ public static class SqlConnectionStringHelper
return builder.ConnectionString;
}
/// <summary>
/// Builds a connection string to the master database using configuration.
/// </summary>
public static string BuildMasterConnectionString(IConfiguration configuration)
{
return BuildConnectionString(configuration, "master");

View File

@@ -5,10 +5,8 @@ namespace Shared.Contracts;
/// </summary>
public record ResponseBody<T>
{
/// <summary>Gets the human-readable message describing the response.</summary>
public required string Message { get; init; }
/// <summary>Gets the response's data payload.</summary>
public required T Payload { get; init; }
}
@@ -17,6 +15,5 @@ public record ResponseBody<T>
/// </summary>
public record ResponseBody
{
/// <summary>Gets the human-readable message describing the response.</summary>
public required string Message { get; init; }
}