diff --git a/web/backend/Features/Features.Auth/Commands/ConfirmUser/ConfirmUserCommand.cs b/web/backend/Features/Features.Auth/Commands/ConfirmUser/ConfirmUserCommand.cs index 0a3c3aae..841392fa 100644 --- a/web/backend/Features/Features.Auth/Commands/ConfirmUser/ConfirmUserCommand.cs +++ b/web/backend/Features/Features.Auth/Commands/ConfirmUser/ConfirmUserCommand.cs @@ -3,8 +3,5 @@ using MediatR; namespace Features.Auth.Commands.ConfirmUser; -/// -/// Validates a confirmation token and confirms the corresponding user account. -/// -/// The confirmation token issued to the user, typically delivered via email. +/// Validates a confirmation token and confirms the corresponding user account. public record ConfirmUserCommand(string Token) : IRequest; diff --git a/web/backend/Features/Features.Auth/Commands/ConfirmUser/ConfirmUserHandler.cs b/web/backend/Features/Features.Auth/Commands/ConfirmUser/ConfirmUserHandler.cs index 230d799a..cbf4f91c 100644 --- a/web/backend/Features/Features.Auth/Commands/ConfirmUser/ConfirmUserHandler.cs +++ b/web/backend/Features/Features.Auth/Commands/ConfirmUser/ConfirmUserHandler.cs @@ -11,8 +11,6 @@ namespace Features.Auth.Commands.ConfirmUser; /// Handles by validating the confirmation token and marking the /// corresponding user account as confirmed. /// -/// Repository used to look up and confirm user accounts. -/// Service used to validate the confirmation token. public class ConfirmUserHandler(IAuthRepository authRepository, ITokenService tokenService) : IRequestHandler { diff --git a/web/backend/Features/Features.Auth/Commands/RefreshToken/RefreshTokenHandler.cs b/web/backend/Features/Features.Auth/Commands/RefreshToken/RefreshTokenHandler.cs index 91c9d496..4b311b73 100644 --- a/web/backend/Features/Features.Auth/Commands/RefreshToken/RefreshTokenHandler.cs +++ b/web/backend/Features/Features.Auth/Commands/RefreshToken/RefreshTokenHandler.cs @@ -8,7 +8,6 @@ namespace Features.Auth.Commands.RefreshToken; /// Handles by validating the refresh token and issuing a new /// access/refresh token pair. /// -/// Service used to validate and exchange the refresh token. public class RefreshTokenHandler(ITokenService tokenService) : IRequestHandler { diff --git a/web/backend/Features/Features.Auth/Commands/RefreshToken/RefreshTokenValidator.cs b/web/backend/Features/Features.Auth/Commands/RefreshToken/RefreshTokenValidator.cs index b6f2a2e7..19489ddf 100644 --- a/web/backend/Features/Features.Auth/Commands/RefreshToken/RefreshTokenValidator.cs +++ b/web/backend/Features/Features.Auth/Commands/RefreshToken/RefreshTokenValidator.cs @@ -2,14 +2,8 @@ using FluentValidation; namespace Features.Auth.Commands.RefreshToken; -/// -/// Validates instances before they are processed. -/// public class RefreshTokenValidator : AbstractValidator { - /// - /// Configures a validation rule requiring to be non-empty. - /// public RefreshTokenValidator() { RuleFor(x => x.RefreshToken).NotEmpty().WithMessage("Refresh token is required"); diff --git a/web/backend/Features/Features.Auth/Commands/RegisterUser/RegisterUserHandler.cs b/web/backend/Features/Features.Auth/Commands/RegisterUser/RegisterUserHandler.cs index d056894c..2663a5ef 100644 --- a/web/backend/Features/Features.Auth/Commands/RegisterUser/RegisterUserHandler.cs +++ b/web/backend/Features/Features.Auth/Commands/RegisterUser/RegisterUserHandler.cs @@ -14,10 +14,6 @@ namespace Features.Auth.Commands.RegisterUser; /// account, issues access/refresh/confirmation tokens, and attempts to send the registration /// confirmation email via Features.Emails. /// -/// Repository used to check for existing users and persist the new account. -/// Infrastructure component used to hash the user's plain-text password. -/// Service used to generate access, refresh, and confirmation tokens. -/// Used to send the cross-slice command that triggers the confirmation email. public class RegisterUserHandler( IAuthRepository authRepo, IPasswordInfrastructure passwordInfrastructure, diff --git a/web/backend/Features/Features.Auth/Commands/RegisterUser/RegisterUserValidator.cs b/web/backend/Features/Features.Auth/Commands/RegisterUser/RegisterUserValidator.cs index 04125df8..de31bddf 100644 --- a/web/backend/Features/Features.Auth/Commands/RegisterUser/RegisterUserValidator.cs +++ b/web/backend/Features/Features.Auth/Commands/RegisterUser/RegisterUserValidator.cs @@ -2,15 +2,8 @@ using FluentValidation; namespace Features.Auth.Commands.RegisterUser; -/// -/// Validates instances before they are processed. -/// public class RegisterUserValidator : AbstractValidator { - /// - /// Configures validation rules for username format and length, first/last name length, email format and - /// length, minimum age based on date of birth, and password strength requirements. - /// public RegisterUserValidator() { RuleFor(x => x.Username) diff --git a/web/backend/Features/Features.Auth/Commands/ResendConfirmationEmail/ResendConfirmationEmailCommand.cs b/web/backend/Features/Features.Auth/Commands/ResendConfirmationEmail/ResendConfirmationEmailCommand.cs index 5b276fc0..216371fe 100644 --- a/web/backend/Features/Features.Auth/Commands/ResendConfirmationEmail/ResendConfirmationEmailCommand.cs +++ b/web/backend/Features/Features.Auth/Commands/ResendConfirmationEmail/ResendConfirmationEmailCommand.cs @@ -2,8 +2,5 @@ using MediatR; namespace Features.Auth.Commands.ResendConfirmationEmail; -/// -/// Resends the account confirmation email to a user, generating a fresh confirmation token. -/// -/// The unique identifier of the user requesting the resend. +/// Resends the account confirmation email to a user, generating a fresh confirmation token. public record ResendConfirmationEmailCommand(Guid UserId) : IRequest; diff --git a/web/backend/Features/Features.Auth/Commands/ResendConfirmationEmail/ResendConfirmationEmailHandler.cs b/web/backend/Features/Features.Auth/Commands/ResendConfirmationEmail/ResendConfirmationEmailHandler.cs index 33eb3f98..0fa817b1 100644 --- a/web/backend/Features/Features.Auth/Commands/ResendConfirmationEmail/ResendConfirmationEmailHandler.cs +++ b/web/backend/Features/Features.Auth/Commands/ResendConfirmationEmail/ResendConfirmationEmailHandler.cs @@ -10,9 +10,6 @@ namespace Features.Auth.Commands.ResendConfirmationEmail; /// Handles by generating a fresh confirmation token and /// sending it via Features.Emails. /// -/// Repository used to look up the user and check verification status. -/// Service used to generate the confirmation token. -/// Used to send the cross-slice command that triggers the email. /// /// Returns silently without sending an email if the user does not exist (to prevent user enumeration) /// or if the user's account is already verified. diff --git a/web/backend/Features/Features.Auth/Controllers/AuthController.cs b/web/backend/Features/Features.Auth/Controllers/AuthController.cs index 308c36d8..2e0aa868 100644 --- a/web/backend/Features/Features.Auth/Controllers/AuthController.cs +++ b/web/backend/Features/Features.Auth/Controllers/AuthController.cs @@ -18,21 +18,11 @@ namespace Features.Auth.Controllers; /// The controller is decorated with [Authorize(AuthenticationSchemes = "JWT")] by default, but most /// actions opt out via [AllowAnonymous] since they are entry points used before a caller holds a token. /// -/// Used to dispatch auth commands and queries to their handlers. [ApiController] [Route("api/[controller]")] [Authorize(AuthenticationSchemes = "JWT")] public class AuthController(IMediator mediator) : ControllerBase { - /// - /// Registers a new user account. - /// - /// - /// Allows anonymous access. On success, responds with 201 Created containing the new user's ID, - /// username, issued refresh/access tokens, and whether a confirmation email was sent. - /// - /// The registration details, including username, name, email, date of birth, and password. - /// A 201 Created result wrapping a of . [AllowAnonymous] [HttpPost("register")] public async Task>> Register( @@ -50,15 +40,6 @@ public class AuthController(IMediator mediator) : ControllerBase ); } - /// - /// Authenticates a user with a username and password. - /// - /// - /// Allows anonymous access. On success, responds with 200 OK containing the user's ID, - /// username, and a newly issued refresh/access token pair. - /// - /// The login credentials (username and password). - /// An 200 OK result wrapping a of . [AllowAnonymous] [HttpPost("login")] public async Task>> Login([FromBody] LoginQuery query) @@ -73,15 +54,6 @@ public class AuthController(IMediator mediator) : ControllerBase ); } - /// - /// Confirms a user account using a confirmation token. - /// - /// - /// Requires JWT authentication. On success, responds with 200 OK containing the confirmed - /// user's ID and the confirmation timestamp. - /// - /// The confirmation token supplied via the confirmation email link. - /// A 200 OK result wrapping a of . [HttpPost("confirm")] public async Task>> Confirm( [FromQuery] string token @@ -97,14 +69,6 @@ public class AuthController(IMediator mediator) : ControllerBase ); } - /// - /// Resends the account confirmation email for the specified user. - /// - /// - /// Requires JWT authentication. On success, responds with 200 OK. - /// - /// The unique identifier of the user account to resend the confirmation email for. - /// A 200 OK result wrapping a confirming the email was resent. [HttpPost("confirm/resend")] public async Task> ResendConfirmation([FromQuery] Guid userId) { @@ -112,15 +76,6 @@ public class AuthController(IMediator mediator) : ControllerBase return Ok(new ResponseBody { Message = "confirmation email has been resent" }); } - /// - /// Exchanges a valid refresh token for a new access/refresh token pair. - /// - /// - /// Allows anonymous access. On success, responds with 200 OK containing the user's ID, - /// username, and the newly issued refresh/access token pair. - /// - /// The request containing the refresh token to exchange. - /// A 200 OK result wrapping a of . [AllowAnonymous] [HttpPost("refresh")] public async Task>> Refresh( diff --git a/web/backend/Features/Features.Auth/DependencyInjection/FeaturesAuthServiceCollectionExtensions.cs b/web/backend/Features/Features.Auth/DependencyInjection/FeaturesAuthServiceCollectionExtensions.cs index 7e475731..c3181b51 100644 --- a/web/backend/Features/Features.Auth/DependencyInjection/FeaturesAuthServiceCollectionExtensions.cs +++ b/web/backend/Features/Features.Auth/DependencyInjection/FeaturesAuthServiceCollectionExtensions.cs @@ -5,9 +5,6 @@ using Microsoft.Extensions.DependencyInjection; namespace Features.Auth.DependencyInjection; -/// -/// Registers the services owned by the Auth feature slice. -/// public static class FeaturesAuthServiceCollectionExtensions { public static IServiceCollection AddFeaturesAuth(this IServiceCollection services) diff --git a/web/backend/Features/Features.Auth/Dtos/AuthPayloads.cs b/web/backend/Features/Features.Auth/Dtos/AuthPayloads.cs index 64e5a855..43070541 100644 --- a/web/backend/Features/Features.Auth/Dtos/AuthPayloads.cs +++ b/web/backend/Features/Features.Auth/Dtos/AuthPayloads.cs @@ -1,12 +1,6 @@ namespace Features.Auth.Dtos; -/// -/// Payload returned to the client after a successful login or token refresh. -/// -/// The unique identifier of the authenticated user account. -/// The username of the authenticated user account. -/// The newly issued refresh token used to obtain new access tokens. -/// The newly issued JWT access token used to authorize subsequent requests. +/// Payload returned to the client after a successful login or token refresh. public record LoginPayload( Guid UserAccountId, string Username, @@ -14,14 +8,7 @@ public record LoginPayload( string AccessToken ); -/// -/// Payload returned to the client after a successful registration. -/// -/// The unique identifier of the newly created user account. -/// The username of the newly created user account. -/// The refresh token issued for the new account. -/// The JWT access token issued for the new account. -/// Whether a confirmation email was successfully sent to the new user. +/// Payload returned to the client after a successful registration. public record RegistrationPayload( Guid UserAccountId, string Username, @@ -30,9 +17,5 @@ public record RegistrationPayload( bool ConfirmationEmailSent ); -/// -/// Payload returned to the client after a user account's email has been confirmed. -/// -/// The unique identifier of the user account that was confirmed. -/// The date and time at which the account was confirmed. +/// Payload returned to the client after a user account's email has been confirmed. public record ConfirmationPayload(Guid UserAccountId, DateTime ConfirmedDate); diff --git a/web/backend/Features/Features.Auth/Queries/Login/LoginHandler.cs b/web/backend/Features/Features.Auth/Queries/Login/LoginHandler.cs index d3ee64d4..2cd27f59 100644 --- a/web/backend/Features/Features.Auth/Queries/Login/LoginHandler.cs +++ b/web/backend/Features/Features.Auth/Queries/Login/LoginHandler.cs @@ -11,12 +11,6 @@ namespace Features.Auth.Queries.Login; /// /// Handles by verifying credentials and issuing access/refresh tokens. /// -/// Repository used to look up the user account and its active credential. -/// -/// Infrastructure component used to verify a plain-text password against a stored -/// hash. -/// -/// Service used to generate access and refresh tokens for the authenticated user. public class LoginHandler( IAuthRepository authRepo, IPasswordInfrastructure passwordInfrastructure, diff --git a/web/backend/Features/Features.Auth/Queries/Login/LoginValidator.cs b/web/backend/Features/Features.Auth/Queries/Login/LoginValidator.cs index 8458414e..c90e9fb7 100644 --- a/web/backend/Features/Features.Auth/Queries/Login/LoginValidator.cs +++ b/web/backend/Features/Features.Auth/Queries/Login/LoginValidator.cs @@ -2,15 +2,8 @@ using FluentValidation; namespace Features.Auth.Queries.Login; -/// -/// Validates instances before they are processed. -/// public class LoginValidator : AbstractValidator { - /// - /// Configures validation rules requiring both and - /// to be non-empty. - /// public LoginValidator() { RuleFor(x => x.Username).NotEmpty().WithMessage("Username is required"); diff --git a/web/backend/Features/Features.Auth/Repository/AuthRepository.cs b/web/backend/Features/Features.Auth/Repository/AuthRepository.cs index 46f8aca7..020c2458 100644 --- a/web/backend/Features/Features.Auth/Repository/AuthRepository.cs +++ b/web/backend/Features/Features.Auth/Repository/AuthRepository.cs @@ -11,7 +11,6 @@ namespace Features.Auth.Repository; /// ADO.NET-based implementation of backed by SQL Server stored procedures, /// handling user registration, credential lookup/rotation, and account verification. /// -/// The factory used to create database connections. public class AuthRepository(ISqlConnectionFactory connectionFactory) : Repository(connectionFactory), IAuthRepository @@ -26,15 +25,7 @@ public class AuthRepository(ISqlConnectionFactory connectionFactory) /// If the result cannot be interpreted, is used, which will cause the /// subsequent lookup to fail. /// - /// Unique username for the user - /// User's first name - /// User's last name - /// User's email address - /// User's date of birth - /// Hashed password - /// The newly created UserAccount with generated ID /// Thrown when the newly registered user cannot be retrieved after registration. - /// Thrown when the database command fails. public async Task RegisterUserAsync( UserRegistrationDto userRegistrationDto ) @@ -84,13 +75,7 @@ public class AuthRepository(ISqlConnectionFactory connectionFactory) ?? throw new Exception("Failed to retrieve newly registered user."); } - /// - /// Retrieves a user account by email address (typically used for login) using the - /// usp_GetUserAccountByEmail stored procedure. - /// - /// Email address to search for - /// UserAccount if found, null otherwise - /// Thrown when the database command fails. + /// Retrieves a user account by email using the usp_GetUserAccountByEmail stored procedure. public async Task GetUserByEmailAsync(string email) { await using DbConnection connection = await CreateConnection(); @@ -104,13 +89,7 @@ public class AuthRepository(ISqlConnectionFactory connectionFactory) return await reader.ReadAsync() ? MapToEntity(reader) : null; } - /// - /// Retrieves a user account by username (typically used for login) using the - /// usp_GetUserAccountByUsername stored procedure. - /// - /// Username to search for - /// UserAccount if found, null otherwise - /// Thrown when the database command fails. + /// Retrieves a user account by username using the usp_GetUserAccountByUsername stored procedure. public async Task GetUserByUsernameAsync(string username) { await using DbConnection connection = await CreateConnection(); @@ -128,9 +107,6 @@ public class AuthRepository(ISqlConnectionFactory connectionFactory) /// Retrieves the active (non-revoked) credential for a user account using the /// USP_GetActiveUserCredentialByUserAccountId stored procedure. /// - /// ID of the user account - /// Active UserCredential if found, null otherwise - /// Thrown when the database command fails. public async Task GetActiveCredentialByUserAccountIdAsync(Guid userAccountId) { await using DbConnection connection = await CreateConnection(); @@ -148,9 +124,6 @@ public class AuthRepository(ISqlConnectionFactory connectionFactory) /// Rotates a user's credential by invalidating all existing credentials and creating a new one, /// using the USP_RotateUserCredential stored procedure. /// - /// ID of the user account - /// New hashed password - /// Thrown when the database command fails. public async Task RotateCredentialAsync(Guid userAccountId, string newPasswordHash) { await using DbConnection connection = await CreateConnection(); @@ -164,12 +137,7 @@ public class AuthRepository(ISqlConnectionFactory connectionFactory) await command.ExecuteNonQueryAsync(); } - /// - /// Retrieves a user account by ID using the usp_GetUserAccountById stored procedure. - /// - /// ID of the user account - /// UserAccount if found, null otherwise - /// Thrown when the database command fails. + /// Retrieves a user account by ID using the usp_GetUserAccountById stored procedure. public async Task GetUserByIdAsync(Guid userAccountId) { await using DbConnection connection = await CreateConnection(); @@ -189,8 +157,6 @@ public class AuthRepository(ISqlConnectionFactory connectionFactory) /// no-op and the existing user is returned (idempotent). If a concurrent request verifies the user /// first, the resulting duplicate-key SQL exception (error 2601/2627) is swallowed. /// - /// ID of the user account to confirm - /// The confirmed , or null if the user account does not exist. /// /// Thrown when the database command fails for a reason other than /// a duplicate verification record. @@ -225,13 +191,7 @@ public class AuthRepository(ISqlConnectionFactory connectionFactory) return await GetUserByIdAsync(userAccountId); } - /// - /// Checks whether a user account has been verified by querying the - /// dbo.UserVerification table for a matching record. - /// - /// ID of the user account - /// True if the user has a verification record, false otherwise - /// Thrown when the database command fails. + /// Checks whether a user account has been verified by querying the dbo.UserVerification table. public async Task IsUserVerifiedAsync(Guid userAccountId) { await using DbConnection connection = await CreateConnection(); @@ -246,23 +206,13 @@ public class AuthRepository(ISqlConnectionFactory connectionFactory) return result != null && result != DBNull.Value; } - /// - /// Determines whether a represents a duplicate key violation - /// (SQL Server error 2601 or 2627), used to detect a concurrent duplicate verification insert. - /// - /// The SQL exception to inspect. - /// True if the exception represents a duplicate key violation, false otherwise. + /// Determines whether a is a duplicate key violation (SQL Server error 2601 or 2627). private static bool IsDuplicateVerificationViolation(SqlException ex) { // 2601/2627 are duplicate key violations in SQL Server. return ex.Number == 2601 || ex.Number == 2627; } - /// - /// Maps a data reader row to a UserAccount entity. - /// - /// The data reader positioned on the row to map. - /// The mapped instance. protected override UserAccount MapToEntity(DbDataReader reader) { return new UserAccount @@ -285,8 +235,6 @@ public class AuthRepository(ISqlConnectionFactory connectionFactory) /// Maps a data reader row to a UserCredential entity. The Timer column is mapped only if /// present in the reader's schema, allowing this method to support result sets that omit it. /// - /// The data reader positioned on the row to map. - /// The mapped instance. private static UserCredential MapToCredentialEntity(DbDataReader reader) { UserCredential entity = new() @@ -319,13 +267,7 @@ public class AuthRepository(ISqlConnectionFactory connectionFactory) return entity; } - /// - /// Helper method to add a parameter to a database command, converting null values to - /// . - /// - /// The command to add the parameter to. - /// The parameter name (including any prefix, e.g. "@Username"). - /// The parameter value, or null to bind . + /// Adds a parameter to a database command, converting null values to . private static void AddParameter(DbCommand command, string name, object? value) { DbParameter p = command.CreateParameter(); diff --git a/web/backend/Features/Features.Auth/Repository/IAuthRepository.cs b/web/backend/Features/Features.Auth/Repository/IAuthRepository.cs index 6a2570fc..092cfd59 100644 --- a/web/backend/Features/Features.Auth/Repository/IAuthRepository.cs +++ b/web/backend/Features/Features.Auth/Repository/IAuthRepository.cs @@ -7,64 +7,23 @@ namespace Features.Auth.Repository; /// public interface IAuthRepository { - /// - /// Registers a new user with account details and initial credential. - /// Uses stored procedure: USP_RegisterUser - /// Task RegisterUserAsync( UserRegistrationDto userRegistrationDto ); - /// - /// Retrieves a user account by email address (typically used for login). - /// Uses stored procedure: usp_GetUserAccountByEmail - /// - /// Email address to search for - /// UserAccount if found, null otherwise Task GetUserByEmailAsync(string email); - /// - /// Retrieves a user account by username (typically used for login). - /// Uses stored procedure: usp_GetUserAccountByUsername - /// - /// Username to search for - /// UserAccount if found, null otherwise Task GetUserByUsernameAsync(string username); - /// - /// Retrieves the active (non-revoked) credential for a user account. - /// Uses stored procedure: USP_GetActiveUserCredentialByUserAccountId - /// - /// ID of the user account - /// Active UserCredential if found, null otherwise Task GetActiveCredentialByUserAccountIdAsync(Guid userAccountId); - /// - /// Rotates a user's credential by invalidating all existing credentials and creating a new one. - /// Uses stored procedure: USP_RotateUserCredential - /// - /// ID of the user account - /// New hashed password + /// Rotates a user's credential by invalidating all existing credentials and creating a new one. Task RotateCredentialAsync(Guid userAccountId, string newPasswordHash); - /// - /// Marks a user account as confirmed. - /// - /// ID of the user account to confirm - /// The confirmed UserAccount entity, or null if the user account does not exist + /// Marks a user account as confirmed. Idempotent if the account is already verified. Task ConfirmUserAccountAsync(Guid userAccountId); - /// - /// Retrieves a user account by ID. - /// - /// ID of the user account - /// UserAccount if found, null otherwise Task GetUserByIdAsync(Guid userAccountId); - /// - /// Checks whether a user account has been verified. - /// - /// ID of the user account - /// True if the user has a verification record, false otherwise Task IsUserVerifiedAsync(Guid userAccountId); } diff --git a/web/backend/Features/Features.Auth/Services/ITokenService.cs b/web/backend/Features/Features.Auth/Services/ITokenService.cs index 4285367a..5ac81ffc 100644 --- a/web/backend/Features/Features.Auth/Services/ITokenService.cs +++ b/web/backend/Features/Features.Auth/Services/ITokenService.cs @@ -18,20 +18,10 @@ public enum TokenType ConfirmationToken, } -/// -/// Represents the result of successfully validating a token. -/// -/// The unique identifier of the user the token belongs to. -/// The username extracted from the token's claims. -/// The produced from the validated token. +/// Represents the result of successfully validating a token. public record ValidatedToken(Guid UserId, string Username, ClaimsPrincipal Principal); -/// -/// Represents the result of refreshing a user's session. -/// -/// The user account associated with the refreshed session. -/// The newly issued refresh token. -/// The newly issued access token. +/// Represents the result of refreshing a user's session. public record RefreshTokenResult(UserAccount UserAccount, string RefreshToken, string AccessToken); /// @@ -39,14 +29,13 @@ public record RefreshTokenResult(UserAccount UserAccount, string RefreshToken, s /// public static class TokenServiceExpirationHours { - /// The expiration window, in hours, for access tokens. public const double AccessTokenHours = 1; - /// The expiration window, in hours, for refresh tokens (21 days). - public const double RefreshTokenHours = 504; // 21 days + /// 21 days. + public const double RefreshTokenHours = 504; - /// The expiration window, in hours, for confirmation tokens (30 minutes). - public const double ConfirmationTokenHours = 0.5; // 30 minutes + /// 30 minutes. + public const double ConfirmationTokenHours = 0.5; } /// @@ -54,61 +43,23 @@ public static class TokenServiceExpirationHours /// public interface ITokenService { - /// - /// Generates a new access token for the given user. - /// - /// The user account to generate the token for. - /// The signed access token string. string GenerateAccessToken(UserAccount user); - /// - /// Generates a new refresh token for the given user. - /// - /// The user account to generate the token for. - /// The signed refresh token string. string GenerateRefreshToken(UserAccount user); - /// - /// Generates a new confirmation token for the given user. - /// - /// The user account to generate the token for. - /// The signed confirmation token string. string GenerateConfirmationToken(UserAccount user); - /// - /// Generates a token of the type specified by , which must be . - /// - /// The enum type identifying which kind of token to generate. Must be . - /// The user account to generate the token for. - /// The signed token string corresponding to the requested token type. + /// Generates a token of the type specified by . + /// Must be . string GenerateToken(UserAccount user) where T : struct, Enum; - /// - /// Validates an access token. - /// - /// The access token string to validate. - /// A describing the token's claims. Task ValidateAccessTokenAsync(string token); - /// - /// Validates a refresh token. - /// - /// The refresh token string to validate. - /// A describing the token's claims. Task ValidateRefreshTokenAsync(string token); - /// - /// Validates a confirmation token. - /// - /// The confirmation token string to validate. - /// A describing the token's claims. Task ValidateConfirmationTokenAsync(string token); - /// - /// Validates a refresh token and issues a new access/refresh token pair for the associated user. - /// - /// The refresh token string to validate and exchange. - /// A containing the user and newly issued tokens. + /// Validates a refresh token and issues a new access/refresh token pair for the associated user. Task RefreshTokenAsync(string refreshTokenString); } diff --git a/web/backend/Features/Features.Auth/Services/TokenService.cs b/web/backend/Features/Features.Auth/Services/TokenService.cs index 1bbf13f2..8d389bab 100644 --- a/web/backend/Features/Features.Auth/Services/TokenService.cs +++ b/web/backend/Features/Features.Auth/Services/TokenService.cs @@ -19,12 +19,7 @@ public class TokenService : ITokenService private readonly string _refreshTokenSecret; private readonly ITokenInfrastructure _tokenInfrastructure; - /// - /// Initializes a new instance of , loading the access, refresh, and - /// confirmation token signing secrets from environment variables. - /// - /// The infrastructure component used to generate and validate JWTs. - /// Repository used to look up user accounts during token refresh. + /// Initializes a new instance of the class. /// /// Thrown when any of the ACCESS_TOKEN_SECRET, REFRESH_TOKEN_SECRET, or /// CONFIRMATION_TOKEN_SECRET environment variables are not set. @@ -53,12 +48,6 @@ public class TokenService : ITokenService ); } - /// - /// Generates an access token for the given user, signed with the access token secret and - /// expiring after hours. - /// - /// The user account to generate the token for. - /// The signed access token string. public string GenerateAccessToken(UserAccount user) { DateTime expiresAt = DateTime.UtcNow.AddHours(TokenServiceExpirationHours.AccessTokenHours); @@ -70,12 +59,6 @@ public class TokenService : ITokenService ); } - /// - /// Generates a refresh token for the given user, signed with the refresh token secret and - /// expiring after hours. - /// - /// The user account to generate the token for. - /// The signed refresh token string. public string GenerateRefreshToken(UserAccount user) { DateTime expiresAt = DateTime.UtcNow.AddHours( @@ -89,12 +72,6 @@ public class TokenService : ITokenService ); } - /// - /// Generates a confirmation token for the given user, signed with the confirmation token secret and - /// expiring after hours. - /// - /// The user account to generate the token for. - /// The signed confirmation token string. public string GenerateConfirmationToken(UserAccount user) { DateTime expiresAt = DateTime.UtcNow.AddHours( @@ -111,11 +88,9 @@ public class TokenService : ITokenService /// /// Generates a token of the kind specified by , dispatching to /// , , or - /// based on the corresponding value. + /// . /// - /// The enum type identifying which kind of token to generate. Must be . - /// The user account to generate the token for. - /// The signed token string corresponding to the requested token type. + /// Must be . /// /// Thrown when is not or does not resolve to a known token type. /// @@ -139,11 +114,6 @@ public class TokenService : ITokenService }; } - /// - /// Validates an access token against the access token secret. - /// - /// The access token string to validate. - /// A describing the token's claims. /// /// Thrown when the token is missing required claims, has a malformed user ID, or otherwise fails validation. /// @@ -152,11 +122,6 @@ public class TokenService : ITokenService return await ValidateTokenInternalAsync(token, _accessTokenSecret, "access"); } - /// - /// Validates a refresh token against the refresh token secret. - /// - /// The refresh token string to validate. - /// A describing the token's claims. /// /// Thrown when the token is missing required claims, has a malformed user ID, or otherwise fails validation. /// @@ -165,11 +130,6 @@ public class TokenService : ITokenService return await ValidateTokenInternalAsync(token, _refreshTokenSecret, "refresh"); } - /// - /// Validates a confirmation token against the confirmation token secret. - /// - /// The confirmation token string to validate. - /// A describing the token's claims. /// /// Thrown when the token is missing required claims, has a malformed user ID, or otherwise fails validation. /// @@ -178,12 +138,6 @@ public class TokenService : ITokenService return await ValidateTokenInternalAsync(token, _confirmationTokenSecret, "confirmation"); } - /// - /// Validates the given refresh token, looks up the associated user, and issues a fresh - /// access/refresh token pair. - /// - /// The refresh token string to validate and exchange. - /// A containing the user and newly issued tokens. /// /// Thrown when the refresh token is invalid, or when the user account it refers to no longer exists. /// @@ -204,10 +158,7 @@ public class TokenService : ITokenService /// Performs the shared validation logic for access, refresh, and confirmation tokens: /// validates the JWT signature/expiration, then extracts and parses the user ID and username claims. /// - /// The token string to validate. - /// The secret key to validate the token's signature against. /// A human-readable label (e.g. "access", "refresh", "confirmation") used in error messages. - /// A describing the token's claims. /// /// Thrown when required claims are missing, the user ID claim is not a valid , /// or the underlying token validation fails for any other reason (e.g. expired or invalid signature). diff --git a/web/backend/Features/Features.Breweries/Commands/CreateBrewery/CreateBreweryCommand.cs b/web/backend/Features/Features.Breweries/Commands/CreateBrewery/CreateBreweryCommand.cs index a06dadc3..19ad1406 100644 --- a/web/backend/Features/Features.Breweries/Commands/CreateBrewery/CreateBreweryCommand.cs +++ b/web/backend/Features/Features.Breweries/Commands/CreateBrewery/CreateBreweryCommand.cs @@ -3,9 +3,6 @@ using MediatR; namespace Features.Breweries.Commands.CreateBrewery; -/// -/// Location data required to create a new brewery post, supplied as part of . -/// public record CreateBreweryLocation( Guid CityId, string AddressLine1, diff --git a/web/backend/Features/Features.Breweries/Commands/CreateBrewery/CreateBreweryHandler.cs b/web/backend/Features/Features.Breweries/Commands/CreateBrewery/CreateBreweryHandler.cs index 07c0f1d3..324eca28 100644 --- a/web/backend/Features/Features.Breweries/Commands/CreateBrewery/CreateBreweryHandler.cs +++ b/web/backend/Features/Features.Breweries/Commands/CreateBrewery/CreateBreweryHandler.cs @@ -5,19 +5,10 @@ using MediatR; namespace Features.Breweries.Commands.CreateBrewery; -/// -/// Handles by persisting a new brewery post and its location. -/// -/// Repository used to persist the new brewery post. public class CreateBreweryHandler(IBreweryRepository repository) : IRequestHandler { - /// - /// Creates a new brewery post, generating new identifiers for the post and its location. - /// - /// The details of the brewery post to create. - /// A token to observe for cancellation requests. - /// The newly created brewery post. + /// Creates a new brewery post, generating new identifiers for the post and its location. public async Task Handle( CreateBreweryCommand request, CancellationToken cancellationToken diff --git a/web/backend/Features/Features.Breweries/Commands/CreateBrewery/CreateBreweryValidator.cs b/web/backend/Features/Features.Breweries/Commands/CreateBrewery/CreateBreweryValidator.cs index ad5f528f..3f9e44f4 100644 --- a/web/backend/Features/Features.Breweries/Commands/CreateBrewery/CreateBreweryValidator.cs +++ b/web/backend/Features/Features.Breweries/Commands/CreateBrewery/CreateBreweryValidator.cs @@ -2,17 +2,8 @@ using FluentValidation; namespace Features.Breweries.Commands.CreateBrewery; -/// -/// Validates instances before they are processed. -/// public class CreateBreweryValidator : AbstractValidator { - /// - /// Configures validation rules requiring , - /// , , and - /// to be present, with length limits on the name, description, - /// address line 1, and postal code fields. - /// public CreateBreweryValidator() { RuleFor(x => x.PostedById).NotEmpty().WithMessage("PostedById is required."); diff --git a/web/backend/Features/Features.Breweries/Commands/DeleteBrewery/DeleteBreweryCommand.cs b/web/backend/Features/Features.Breweries/Commands/DeleteBrewery/DeleteBreweryCommand.cs index 2b07ec6f..5e939d16 100644 --- a/web/backend/Features/Features.Breweries/Commands/DeleteBrewery/DeleteBreweryCommand.cs +++ b/web/backend/Features/Features.Breweries/Commands/DeleteBrewery/DeleteBreweryCommand.cs @@ -2,7 +2,4 @@ using MediatR; namespace Features.Breweries.Commands.DeleteBrewery; -/// -/// Deletes a brewery post by its unique identifier. -/// public record DeleteBreweryCommand(Guid BreweryPostId) : IRequest; diff --git a/web/backend/Features/Features.Breweries/Commands/DeleteBrewery/DeleteBreweryHandler.cs b/web/backend/Features/Features.Breweries/Commands/DeleteBrewery/DeleteBreweryHandler.cs index 226a0d04..7cf2dac9 100644 --- a/web/backend/Features/Features.Breweries/Commands/DeleteBrewery/DeleteBreweryHandler.cs +++ b/web/backend/Features/Features.Breweries/Commands/DeleteBrewery/DeleteBreweryHandler.cs @@ -3,10 +3,6 @@ using MediatR; namespace Features.Breweries.Commands.DeleteBrewery; -/// -/// Handles by deleting the matching brewery post. -/// -/// Repository used to delete the brewery post. public class DeleteBreweryHandler(IBreweryRepository repository) : IRequestHandler { diff --git a/web/backend/Features/Features.Breweries/Commands/UpdateBrewery/UpdateBreweryCommand.cs b/web/backend/Features/Features.Breweries/Commands/UpdateBrewery/UpdateBreweryCommand.cs index f2dc90db..00377407 100644 --- a/web/backend/Features/Features.Breweries/Commands/UpdateBrewery/UpdateBreweryCommand.cs +++ b/web/backend/Features/Features.Breweries/Commands/UpdateBrewery/UpdateBreweryCommand.cs @@ -3,9 +3,6 @@ using MediatR; namespace Features.Breweries.Commands.UpdateBrewery; -/// -/// Location data for an existing brewery post, supplied as part of . -/// public record UpdateBreweryLocation( Guid BreweryPostLocationId, Guid CityId, diff --git a/web/backend/Features/Features.Breweries/Commands/UpdateBrewery/UpdateBreweryHandler.cs b/web/backend/Features/Features.Breweries/Commands/UpdateBrewery/UpdateBreweryHandler.cs index cb6c033a..5ba89607 100644 --- a/web/backend/Features/Features.Breweries/Commands/UpdateBrewery/UpdateBreweryHandler.cs +++ b/web/backend/Features/Features.Breweries/Commands/UpdateBrewery/UpdateBreweryHandler.cs @@ -5,10 +5,6 @@ using MediatR; namespace Features.Breweries.Commands.UpdateBrewery; -/// -/// Handles by persisting changes to an existing brewery post. -/// -/// Repository used to persist the updated brewery post. public class UpdateBreweryHandler(IBreweryRepository repository) : IRequestHandler { @@ -16,9 +12,6 @@ public class UpdateBreweryHandler(IBreweryRepository repository) /// Updates an existing brewery post. If has no Location, /// the brewery's location is cleared. /// - /// The updated details of the brewery post. - /// A token to observe for cancellation requests. - /// The updated brewery post. public async Task Handle( UpdateBreweryCommand request, CancellationToken cancellationToken diff --git a/web/backend/Features/Features.Breweries/Controllers/BreweryController.cs b/web/backend/Features/Features.Breweries/Controllers/BreweryController.cs index 25eb393c..115d6008 100644 --- a/web/backend/Features/Features.Breweries/Controllers/BreweryController.cs +++ b/web/backend/Features/Features.Breweries/Controllers/BreweryController.cs @@ -11,28 +11,17 @@ using Shared.Contracts; namespace Features.Breweries.Controllers; -/// -/// Provides CRUD endpoints for managing brewery posts. -/// /// -/// The controller is decorated with [Authorize(AuthenticationSchemes = "JWT")] by default; read endpoints -/// ( and ) opt out via [AllowAnonymous]. +/// Requires JWT authentication by default; and opt out via +/// [AllowAnonymous]. /// -/// Used to dispatch brewery commands and queries to their handlers. [ApiController] [Route("api/[controller]")] [Authorize(AuthenticationSchemes = "JWT")] public class BreweryController(IMediator mediator) : ControllerBase { - /// - /// Retrieves a single brewery post by its unique identifier. - /// /// Allows anonymous access. - /// The unique identifier of the brewery post to retrieve. - /// - /// A 200 OK result wrapping a of if found; - /// otherwise a 404 Not Found result wrapping a error message. - /// + /// 200 OK with the brewery if found; otherwise 404 Not Found. [AllowAnonymous] [HttpGet("{id:guid}")] public async Task>> GetById(Guid id) @@ -50,13 +39,7 @@ public class BreweryController(IMediator mediator) : ControllerBase ); } - /// - /// Retrieves a paginated list of brewery posts. - /// /// Allows anonymous access. - /// The maximum number of brewery posts to return, or null for no limit. - /// The number of brewery posts to skip before returning results, or null for no offset. - /// A 200 OK result wrapping a of a collection of . [AllowAnonymous] [HttpGet] public async Task>>> GetAll( @@ -76,14 +59,7 @@ public class BreweryController(IMediator mediator) : ControllerBase ); } - /// - /// Creates a new brewery post. - /// - /// The brewery details to create, including the posting user, name, description, and location. - /// - /// A 201 Created result wrapping a of the newly created - /// . - /// + /// 201 Created with the newly created brewery. [HttpPost] public async Task>> Create( [FromBody] CreateBreweryCommand command @@ -100,22 +76,8 @@ public class BreweryController(IMediator mediator) : ControllerBase ); } - /// - /// Updates an existing brewery post. - /// - /// - /// The unique identifier of the brewery post from the route, which must match - /// 's ID. - /// - /// - /// The updated brewery details, including the posting user, name, description, and optional - /// location. - /// - /// - /// A 200 OK result wrapping a of the updated if the - /// update succeeds; otherwise a 400 Bad Request result wrapping a error message - /// when the route ID does not match the payload ID. - /// + /// Must match 's BreweryPostId. + /// 200 OK with the updated brewery; or 400 Bad Request if the route ID does not match the payload ID. [HttpPut("{id:guid}")] public async Task>> Update( Guid id, @@ -135,11 +97,7 @@ public class BreweryController(IMediator mediator) : ControllerBase ); } - /// - /// Deletes a brewery post. - /// - /// The unique identifier of the brewery post to delete. - /// A 200 OK result wrapping a confirming the deletion. + /// 200 OK confirming the deletion. [HttpDelete("{id:guid}")] public async Task> Delete(Guid id) { diff --git a/web/backend/Features/Features.Breweries/Dtos/BreweryDto.cs b/web/backend/Features/Features.Breweries/Dtos/BreweryDto.cs index 616cacf4..cbbfc2a3 100644 --- a/web/backend/Features/Features.Breweries/Dtos/BreweryDto.cs +++ b/web/backend/Features/Features.Breweries/Dtos/BreweryDto.cs @@ -1,89 +1,39 @@ namespace Features.Breweries.Dtos; -/// -/// Represents the location details of an existing brewery, as returned in . -/// public class BreweryLocationDto { - /// - /// The unique identifier of the brewery's location record. - /// public Guid BreweryPostLocationId { get; set; } - /// - /// The unique identifier of the brewery post that this location belongs to. - /// public Guid BreweryPostId { get; set; } - /// - /// The unique identifier of the city in which the brewery is located. - /// public Guid CityId { get; set; } - /// - /// The primary street address line of the brewery. - /// public string AddressLine1 { get; set; } = string.Empty; - /// - /// An optional secondary address line (e.g. suite or unit number). - /// public string? AddressLine2 { get; set; } - /// - /// The postal/ZIP code of the brewery's address. - /// public string PostalCode { get; set; } = string.Empty; - /// - /// The optional geographic coordinates of the brewery, in a raw binary representation. - /// + /// Gets or sets the brewery's geographic coordinates, in a raw binary representation. public byte[]? Coordinates { get; set; } } -/// -/// Represents a brewery post as returned by the brewery endpoints, including its metadata and -/// optional location. -/// public class BreweryDto { - /// - /// The unique identifier of the brewery post. - /// public Guid BreweryPostId { get; set; } - /// - /// The unique identifier of the user account that created the brewery post. - /// public Guid PostedById { get; set; } - /// - /// The name of the brewery. - /// public string BreweryName { get; set; } = string.Empty; - /// - /// A description of the brewery. - /// public string Description { get; set; } = string.Empty; - /// - /// The date and time at which the brewery post was created. - /// public DateTime CreatedAt { get; set; } - /// - /// The date and time at which the brewery post was last updated, or null if it has never been updated. - /// public DateTime? UpdatedAt { get; set; } - /// - /// A row-version/concurrency token used to detect conflicting concurrent updates to the brewery post. - /// + /// Gets or sets the row-version/concurrency token used to detect conflicting concurrent updates. public byte[]? Timer { get; set; } - /// - /// The location details of the brewery, or null if no location is associated with it. - /// public BreweryLocationDto? Location { get; set; } } diff --git a/web/backend/Features/Features.Breweries/Dtos/BreweryDtoMapper.cs b/web/backend/Features/Features.Breweries/Dtos/BreweryDtoMapper.cs index 4cba3518..89ce7e54 100644 --- a/web/backend/Features/Features.Breweries/Dtos/BreweryDtoMapper.cs +++ b/web/backend/Features/Features.Breweries/Dtos/BreweryDtoMapper.cs @@ -2,17 +2,8 @@ using Domain.Entities; namespace Features.Breweries.Dtos; -/// -/// Maps domain entities to their wire representation. -/// public static class BreweryDtoMapper { - /// - /// Maps a domain entity to its representation, - /// including its location, if present. - /// - /// The brewery post entity to map. - /// The mapped . public static BreweryDto ToDto(this BreweryPost brewery) { return new BreweryDto diff --git a/web/backend/Features/Features.Breweries/Queries/GetAllBreweries/GetAllBreweriesHandler.cs b/web/backend/Features/Features.Breweries/Queries/GetAllBreweries/GetAllBreweriesHandler.cs index e9a37484..6f06ada8 100644 --- a/web/backend/Features/Features.Breweries/Queries/GetAllBreweries/GetAllBreweriesHandler.cs +++ b/web/backend/Features/Features.Breweries/Queries/GetAllBreweries/GetAllBreweriesHandler.cs @@ -5,10 +5,6 @@ using MediatR; namespace Features.Breweries.Queries.GetAllBreweries; -/// -/// Handles by retrieving a paginated list of brewery posts. -/// -/// Repository used to query brewery post data. public class GetAllBreweriesHandler(IBreweryRepository repository) : IRequestHandler> { diff --git a/web/backend/Features/Features.Breweries/Queries/GetBreweryById/GetBreweryByIdHandler.cs b/web/backend/Features/Features.Breweries/Queries/GetBreweryById/GetBreweryByIdHandler.cs index 72f510e1..e80ec1c8 100644 --- a/web/backend/Features/Features.Breweries/Queries/GetBreweryById/GetBreweryByIdHandler.cs +++ b/web/backend/Features/Features.Breweries/Queries/GetBreweryById/GetBreweryByIdHandler.cs @@ -5,10 +5,6 @@ using MediatR; namespace Features.Breweries.Queries.GetBreweryById; -/// -/// Handles by looking up the matching brewery post. -/// -/// Repository used to query brewery post data. public class GetBreweryByIdHandler(IBreweryRepository repository) : IRequestHandler { diff --git a/web/backend/Features/Features.Breweries/Queries/GetBreweryById/GetBreweryByIdQuery.cs b/web/backend/Features/Features.Breweries/Queries/GetBreweryById/GetBreweryByIdQuery.cs index 5f4fc13f..ece9133d 100644 --- a/web/backend/Features/Features.Breweries/Queries/GetBreweryById/GetBreweryByIdQuery.cs +++ b/web/backend/Features/Features.Breweries/Queries/GetBreweryById/GetBreweryByIdQuery.cs @@ -3,7 +3,4 @@ using MediatR; namespace Features.Breweries.Queries.GetBreweryById; -/// -/// Retrieves a single brewery post by its unique identifier. -/// public record GetBreweryByIdQuery(Guid BreweryPostId) : IRequest; diff --git a/web/backend/Features/Features.Breweries/Repository/BreweryRepository.cs b/web/backend/Features/Features.Breweries/Repository/BreweryRepository.cs index cc5e24b6..4baa9c43 100644 --- a/web/backend/Features/Features.Breweries/Repository/BreweryRepository.cs +++ b/web/backend/Features/Features.Breweries/Repository/BreweryRepository.cs @@ -9,17 +9,11 @@ namespace Features.Breweries.Repository; /// ADO.NET-based implementation of backed by SQL Server stored /// procedures. /// -/// The factory used to create database connections. public class BreweryRepository(ISqlConnectionFactory connectionFactory) : Repository(connectionFactory), IBreweryRepository { - /// - /// Retrieves a brewery post by ID using the USP_GetBreweryById stored procedure. - /// - /// The unique identifier of the brewery post. - /// The matching , or null if not found. - /// Thrown when the database command fails. + /// Retrieves a brewery post by ID using the USP_GetBreweryById stored procedure. public async Task GetByIdAsync(Guid id) { await using DbConnection connection = await CreateConnection(); @@ -40,10 +34,6 @@ public class BreweryRepository(ISqlConnectionFactory connectionFactory) /// stored procedure. The @Limit and @Offset parameters are only added when their /// corresponding argument has a value. /// - /// The maximum number of records to return, or null for no limit. - /// The number of records to skip, or null for no offset. - /// The collection of matching records. - /// Thrown when the database command fails. public async Task> GetAllAsync(int? limit, int? offset) { await using DbConnection connection = await CreateConnection(); @@ -71,8 +61,6 @@ public class BreweryRepository(ISqlConnectionFactory connectionFactory) /// USP_UpdateBrewery stored procedure. When .Location is /// null, any existing location for the brewery is removed. /// - /// The brewery post containing updated values. - /// Thrown when the database command fails. public async Task UpdateAsync(BreweryPost brewery) { await using DbConnection connection = await CreateConnection(); @@ -97,8 +85,6 @@ public class BreweryRepository(ISqlConnectionFactory connectionFactory) /// Deletes a brewery post by ID using the USP_DeleteBrewery stored procedure. Its location /// and photos are removed via cascading foreign keys. /// - /// The unique identifier of the brewery post to delete. - /// Thrown when the database command fails. public async Task DeleteAsync(Guid id) { await using DbConnection connection = await CreateConnection(); @@ -110,12 +96,8 @@ public class BreweryRepository(ISqlConnectionFactory connectionFactory) await command.ExecuteNonQueryAsync(); } - /// - /// Creates a new brewery post and its location using the USP_CreateBrewery stored procedure. - /// - /// The brewery post to create. Must have a non-null Location. + /// Creates a new brewery post and its location using the USP_CreateBrewery stored procedure. /// Thrown when .Location is null. - /// Thrown when the database command fails. public async Task CreateAsync(BreweryPost brewery) { await using DbConnection connection = await CreateConnection(); @@ -139,12 +121,9 @@ public class BreweryRepository(ISqlConnectionFactory connectionFactory) } /// - /// Maps the current row of a data reader to a entity, including its - /// rowversion Timer field and, if location columns are present in the result set, its - /// associated . + /// Maps the current row of to a , including its + /// associated if location columns are present in the result set. /// - /// The data reader positioned on the row to map. - /// The mapped instance. protected override BreweryPost MapToEntity(DbDataReader reader) { BreweryPost brewery = new(); @@ -212,13 +191,7 @@ public class BreweryRepository(ISqlConnectionFactory connectionFactory) return brewery; } - /// - /// Helper method to add a parameter to a database command, converting null values to - /// . - /// - /// The command to add the parameter to. - /// The parameter name (including any prefix, e.g. "@BreweryName"). - /// The parameter value, or null to bind . + /// Adds a parameter to , converting null to . private static void AddParameter(DbCommand command, string name, object? value) { DbParameter p = command.CreateParameter(); diff --git a/web/backend/Features/Features.Breweries/Repository/IBreweryRepository.cs b/web/backend/Features/Features.Breweries/Repository/IBreweryRepository.cs index e976c2ad..a23ab59e 100644 --- a/web/backend/Features/Features.Breweries/Repository/IBreweryRepository.cs +++ b/web/backend/Features/Features.Breweries/Repository/IBreweryRepository.cs @@ -7,37 +7,16 @@ namespace Features.Breweries.Repository; /// public interface IBreweryRepository { - /// - /// Retrieves a brewery post by its unique identifier. - /// - /// The unique identifier of the brewery post. - /// The matching , or null if not found. + /// Retrieves a brewery post by ID, or null if not found. Task GetByIdAsync(Guid id); - /// - /// Retrieves all brewery posts, optionally paginated. - /// - /// The maximum number of records to return, or null for no limit. - /// The number of records to skip, or null for no offset. - /// The collection of matching records. Task> GetAllAsync(int? limit, int? offset); - /// - /// Updates an existing brewery post. - /// - /// The brewery post containing updated values. Task UpdateAsync(BreweryPost brewery); - /// - /// Deletes a brewery post by its unique identifier. - /// - /// The unique identifier of the brewery post to delete. Task DeleteAsync(Guid id); - /// - /// Creates a new brewery post, including its location details. - /// - /// The brewery post to create. Must have a non-null Location. + /// Creates a new brewery post, including its location details. /// Thrown when has no Location. Task CreateAsync(BreweryPost brewery); } diff --git a/web/backend/Features/Features.Emails/Commands/SendRegistrationEmail/SendRegistrationEmailHandler.cs b/web/backend/Features/Features.Emails/Commands/SendRegistrationEmail/SendRegistrationEmailHandler.cs index 0da0cdce..31981b69 100644 --- a/web/backend/Features/Features.Emails/Commands/SendRegistrationEmail/SendRegistrationEmailHandler.cs +++ b/web/backend/Features/Features.Emails/Commands/SendRegistrationEmail/SendRegistrationEmailHandler.cs @@ -8,7 +8,6 @@ namespace Features.Emails.Commands.SendRegistrationEmail; /// Handles , the cross-slice command sent by Features.Auth /// after a new user registers. /// -/// Dispatcher used to render and send the email. public class SendRegistrationEmailHandler(IEmailDispatcher emailDispatcher) : IRequestHandler { diff --git a/web/backend/Features/Features.Emails/Commands/SendResendConfirmationEmail/SendResendConfirmationEmailHandler.cs b/web/backend/Features/Features.Emails/Commands/SendResendConfirmationEmail/SendResendConfirmationEmailHandler.cs index 17f52009..d04419b9 100644 --- a/web/backend/Features/Features.Emails/Commands/SendResendConfirmationEmail/SendResendConfirmationEmailHandler.cs +++ b/web/backend/Features/Features.Emails/Commands/SendResendConfirmationEmail/SendResendConfirmationEmailHandler.cs @@ -8,7 +8,6 @@ namespace Features.Emails.Commands.SendResendConfirmationEmail; /// Handles , the cross-slice command sent by Features.Auth /// when a user requests a fresh confirmation link. /// -/// Dispatcher used to render and send the email. public class SendResendConfirmationEmailHandler(IEmailDispatcher emailDispatcher) : IRequestHandler { diff --git a/web/backend/Features/Features.Emails/DependencyInjection/FeaturesEmailsServiceCollectionExtensions.cs b/web/backend/Features/Features.Emails/DependencyInjection/FeaturesEmailsServiceCollectionExtensions.cs index 8b97dbdb..5a64d9db 100644 --- a/web/backend/Features/Features.Emails/DependencyInjection/FeaturesEmailsServiceCollectionExtensions.cs +++ b/web/backend/Features/Features.Emails/DependencyInjection/FeaturesEmailsServiceCollectionExtensions.cs @@ -5,9 +5,6 @@ using Microsoft.Extensions.DependencyInjection; namespace Features.Emails.DependencyInjection; -/// -/// Registers the services owned by the Emails feature slice. -/// public static class FeaturesEmailsServiceCollectionExtensions { public static IServiceCollection AddFeaturesEmails(this IServiceCollection services) diff --git a/web/backend/Features/Features.Emails/Services/EmailDispatcher.cs b/web/backend/Features/Features.Emails/Services/EmailDispatcher.cs index 436560e6..01c42c37 100644 --- a/web/backend/Features/Features.Emails/Services/EmailDispatcher.cs +++ b/web/backend/Features/Features.Emails/Services/EmailDispatcher.cs @@ -4,11 +4,8 @@ using Infrastructure.Email.Templates.Rendering; namespace Features.Emails.Services; /// -/// Default implementation of that renders email templates and dispatches -/// them via an . +/// Default implementation of . /// -/// Provider used to deliver the rendered emails. -/// Provider used to render HTML email bodies from templates. public class EmailDispatcher( IEmailProvider emailProvider, IEmailTemplateProvider emailTemplateProvider @@ -25,10 +22,6 @@ public class EmailDispatcher( Environment.GetEnvironmentVariable("WEBSITE_BASE_URL") ?? throw new InvalidOperationException("WEBSITE_BASE_URL environment variable is not set"); - /// - /// Builds a confirmation link from the given token, renders the registration welcome email template, - /// and sends it to the newly created user. - /// public async Task SendRegistrationEmailAsync( string firstName, string email, @@ -45,10 +38,6 @@ public class EmailDispatcher( await emailProvider.SendAsync(email, "Welcome to The Biergarten App!", emailHtml, true); } - /// - /// Builds a confirmation link from the given token, renders the resend-confirmation email template, - /// and sends it to the user. - /// public async Task SendResendConfirmationEmailAsync( string firstName, string email, diff --git a/web/backend/Features/Features.Emails/Services/IEmailDispatcher.cs b/web/backend/Features/Features.Emails/Services/IEmailDispatcher.cs index 8a4b3267..686a98a6 100644 --- a/web/backend/Features/Features.Emails/Services/IEmailDispatcher.cs +++ b/web/backend/Features/Features.Emails/Services/IEmailDispatcher.cs @@ -1,25 +1,17 @@ namespace Features.Emails.Services; /// -/// Defines operations for sending account-related emails, such as registration and confirmation resend emails. +/// Defines operations for sending account-related emails. /// public interface IEmailDispatcher { /// /// Sends a welcome email containing an account confirmation link to a newly registered user. /// - /// The recipient's first name, used to personalize the email. - /// The recipient's email address. - /// The confirmation token to embed in the confirmation link. - /// A task that completes once the email has been sent. Task SendRegistrationEmailAsync(string firstName, string email, string confirmationToken); /// /// Sends an email containing a fresh account confirmation link to a user who requested a resend. /// - /// The recipient's first name, used to personalize the email. - /// The recipient's email address. - /// The confirmation token to embed in the confirmation link. - /// A task that completes once the email has been sent. Task SendResendConfirmationEmailAsync(string firstName, string email, string confirmationToken); } diff --git a/web/backend/Features/Features.UserManagement/Commands/UpdateUser/UpdateUserHandler.cs b/web/backend/Features/Features.UserManagement/Commands/UpdateUser/UpdateUserHandler.cs index cee83da1..4967b127 100644 --- a/web/backend/Features/Features.UserManagement/Commands/UpdateUser/UpdateUserHandler.cs +++ b/web/backend/Features/Features.UserManagement/Commands/UpdateUser/UpdateUserHandler.cs @@ -3,10 +3,6 @@ using MediatR; namespace Features.UserManagement.Commands.UpdateUser; -/// -/// Handles by persisting changes to an existing user account. -/// -/// Repository used to persist the updated user account. public class UpdateUserHandler(IUserAccountRepository repository) : IRequestHandler { diff --git a/web/backend/Features/Features.UserManagement/Controllers/UserController.cs b/web/backend/Features/Features.UserManagement/Controllers/UserController.cs index 90987d65..1bd98ce7 100644 --- a/web/backend/Features/Features.UserManagement/Controllers/UserController.cs +++ b/web/backend/Features/Features.UserManagement/Controllers/UserController.cs @@ -6,20 +6,12 @@ using Microsoft.AspNetCore.Mvc; namespace Features.UserManagement.Controllers; -/// -/// Provides read-only endpoints for retrieving user accounts. -/// -/// Used to dispatch user queries to their handlers. [ApiController] [Route("api/[controller]")] public class UserController(IMediator mediator) : ControllerBase { - /// - /// Retrieves a paginated list of user accounts. - /// - /// The maximum number of user accounts to return, or null for no limit. - /// The number of user accounts to skip before returning results, or null for no offset. - /// A 200 OK result containing the collection of entities. + /// null for no limit. + /// null for no offset. [HttpGet] public async Task>> GetAll( [FromQuery] int? limit, @@ -30,11 +22,6 @@ public class UserController(IMediator mediator) : ControllerBase return Ok(users); } - /// - /// Retrieves a single user account by its unique identifier. - /// - /// The unique identifier of the user account to retrieve. - /// A 200 OK result containing the matching . [HttpGet("{id:guid}")] public async Task> GetById(Guid id) { diff --git a/web/backend/Features/Features.UserManagement/DependencyInjection/FeaturesUserManagementServiceCollectionExtensions.cs b/web/backend/Features/Features.UserManagement/DependencyInjection/FeaturesUserManagementServiceCollectionExtensions.cs index 1c6c7947..ebd3c081 100644 --- a/web/backend/Features/Features.UserManagement/DependencyInjection/FeaturesUserManagementServiceCollectionExtensions.cs +++ b/web/backend/Features/Features.UserManagement/DependencyInjection/FeaturesUserManagementServiceCollectionExtensions.cs @@ -3,9 +3,6 @@ using Microsoft.Extensions.DependencyInjection; namespace Features.UserManagement.DependencyInjection; -/// -/// Registers the services owned by the UserManagement feature slice. -/// public static class FeaturesUserManagementServiceCollectionExtensions { public static IServiceCollection AddFeaturesUserManagement(this IServiceCollection services) diff --git a/web/backend/Features/Features.UserManagement/Queries/GetAllUsers/GetAllUsersHandler.cs b/web/backend/Features/Features.UserManagement/Queries/GetAllUsers/GetAllUsersHandler.cs index 10b566af..4e8afe82 100644 --- a/web/backend/Features/Features.UserManagement/Queries/GetAllUsers/GetAllUsersHandler.cs +++ b/web/backend/Features/Features.UserManagement/Queries/GetAllUsers/GetAllUsersHandler.cs @@ -4,10 +4,6 @@ using MediatR; namespace Features.UserManagement.Queries.GetAllUsers; -/// -/// Handles by retrieving a paginated list of user accounts. -/// -/// Repository used to query user account data. public class GetAllUsersHandler(IUserAccountRepository repository) : IRequestHandler> { diff --git a/web/backend/Features/Features.UserManagement/Queries/GetAllUsers/GetAllUsersQuery.cs b/web/backend/Features/Features.UserManagement/Queries/GetAllUsers/GetAllUsersQuery.cs index a1a32416..90adf926 100644 --- a/web/backend/Features/Features.UserManagement/Queries/GetAllUsers/GetAllUsersQuery.cs +++ b/web/backend/Features/Features.UserManagement/Queries/GetAllUsers/GetAllUsersQuery.cs @@ -3,7 +3,4 @@ using MediatR; namespace Features.UserManagement.Queries.GetAllUsers; -/// -/// Retrieves a paginated list of user accounts. -/// public record GetAllUsersQuery(int? Limit, int? Offset) : IRequest>; diff --git a/web/backend/Features/Features.UserManagement/Queries/GetUserById/GetUserByIdHandler.cs b/web/backend/Features/Features.UserManagement/Queries/GetUserById/GetUserByIdHandler.cs index 405afec7..d664310e 100644 --- a/web/backend/Features/Features.UserManagement/Queries/GetUserById/GetUserByIdHandler.cs +++ b/web/backend/Features/Features.UserManagement/Queries/GetUserById/GetUserByIdHandler.cs @@ -5,10 +5,6 @@ using MediatR; namespace Features.UserManagement.Queries.GetUserById; -/// -/// Handles by looking up the matching user account. -/// -/// Repository used to query user account data. public class GetUserByIdHandler(IUserAccountRepository repository) : IRequestHandler { diff --git a/web/backend/Features/Features.UserManagement/Queries/GetUserById/GetUserByIdQuery.cs b/web/backend/Features/Features.UserManagement/Queries/GetUserById/GetUserByIdQuery.cs index dab2a1cd..8337b7a9 100644 --- a/web/backend/Features/Features.UserManagement/Queries/GetUserById/GetUserByIdQuery.cs +++ b/web/backend/Features/Features.UserManagement/Queries/GetUserById/GetUserByIdQuery.cs @@ -3,7 +3,4 @@ using MediatR; namespace Features.UserManagement.Queries.GetUserById; -/// -/// Retrieves a single user account by its unique identifier. -/// public record GetUserByIdQuery(Guid UserAccountId) : IRequest; diff --git a/web/backend/Features/Features.UserManagement/Repository/IUserAccountRepository.cs b/web/backend/Features/Features.UserManagement/Repository/IUserAccountRepository.cs index 94973010..7ba4c08d 100644 --- a/web/backend/Features/Features.UserManagement/Repository/IUserAccountRepository.cs +++ b/web/backend/Features/Features.UserManagement/Repository/IUserAccountRepository.cs @@ -8,43 +8,29 @@ namespace Features.UserManagement.Repository; public interface IUserAccountRepository { /// - /// Retrieves a user account by its unique identifier. + /// Retrieves a user account by its unique identifier, or null if not found. /// - /// The unique identifier of the user account. - /// The matching , or null if not found. Task GetByIdAsync(Guid id); /// /// Retrieves all user accounts, optionally paginated. /// - /// The maximum number of records to return, or null for no limit. - /// The number of records to skip, or null for no offset. - /// The collection of matching records. + /// null for no limit. + /// null for no offset. Task> GetAllAsync(int? limit, int? offset); - /// - /// Updates an existing user account's details. - /// - /// The user account containing updated values. Must have a valid UserAccountId. + /// Must have a valid UserAccountId. Task UpdateAsync(UserAccount userAccount); - /// - /// Deletes a user account by its unique identifier. - /// - /// The unique identifier of the user account to delete. Task DeleteAsync(Guid id); /// - /// Retrieves a user account by username. + /// Retrieves a user account by username, or null if not found. /// - /// The username to search for. - /// The matching , or null if not found. Task GetByUsernameAsync(string username); /// - /// Retrieves a user account by email address. + /// Retrieves a user account by email address, or null if not found. /// - /// The email address to search for. - /// The matching , or null if not found. Task GetByEmailAsync(string email); } diff --git a/web/backend/Features/Features.UserManagement/Repository/UserAccountRepository.cs b/web/backend/Features/Features.UserManagement/Repository/UserAccountRepository.cs index 743d05e1..f03d49d6 100644 --- a/web/backend/Features/Features.UserManagement/Repository/UserAccountRepository.cs +++ b/web/backend/Features/Features.UserManagement/Repository/UserAccountRepository.cs @@ -9,17 +9,12 @@ namespace Features.UserManagement.Repository; /// ADO.NET-based implementation of backed by SQL Server /// stored procedures. /// -/// The factory used to create database connections. public class UserAccountRepository(ISqlConnectionFactory connectionFactory) : Repository(connectionFactory), IUserAccountRepository { - /// - /// Retrieves a user account by ID using the usp_GetUserAccountById stored procedure. - /// - /// The unique identifier of the user account. - /// The matching , or null if not found. - /// Thrown when the database command fails. + /// Uses the usp_GetUserAccountById stored procedure. + /// public async Task GetByIdAsync(Guid id) { await using DbConnection connection = await CreateConnection(); @@ -34,14 +29,10 @@ public class UserAccountRepository(ISqlConnectionFactory connectionFactory) } /// - /// Retrieves all user accounts, optionally paginated, using the usp_GetAllUserAccounts - /// stored procedure. The @Limit and @Offset parameters are only added when their - /// corresponding argument has a value. + /// Uses the usp_GetAllUserAccounts stored procedure. The @Limit and @Offset + /// parameters are only added when their corresponding argument has a value. /// - /// The maximum number of records to return, or null for no limit. - /// The number of records to skip, or null for no offset. - /// The collection of matching records. - /// Thrown when the database command fails. + /// public async Task> GetAllAsync(int? limit, int? offset) { await using DbConnection connection = await CreateConnection(); @@ -68,8 +59,7 @@ public class UserAccountRepository(ISqlConnectionFactory connectionFactory) /// Updates a user account's username, first name, last name, email, and date of birth using /// the usp_UpdateUserAccount stored procedure. /// - /// The user account containing updated values. Must have a valid UserAccountId. - /// Thrown when the database command fails. + /// public async Task UpdateAsync(UserAccount userAccount) { await using DbConnection connection = await CreateConnection(); @@ -87,11 +77,8 @@ public class UserAccountRepository(ISqlConnectionFactory connectionFactory) await command.ExecuteNonQueryAsync(); } - /// - /// Deletes a user account by ID using the usp_DeleteUserAccount stored procedure. - /// - /// The unique identifier of the user account to delete. - /// Thrown when the database command fails. + /// Uses the usp_DeleteUserAccount stored procedure. + /// public async Task DeleteAsync(Guid id) { await using DbConnection connection = await CreateConnection(); @@ -103,12 +90,8 @@ public class UserAccountRepository(ISqlConnectionFactory connectionFactory) await command.ExecuteNonQueryAsync(); } - /// - /// Retrieves a user account by username using the usp_GetUserAccountByUsername stored procedure. - /// - /// The username to search for. - /// The matching , or null if not found. - /// Thrown when the database command fails. + /// Uses the usp_GetUserAccountByUsername stored procedure. + /// public async Task GetByUsernameAsync(string username) { await using DbConnection connection = await CreateConnection(); @@ -122,12 +105,8 @@ public class UserAccountRepository(ISqlConnectionFactory connectionFactory) return await reader.ReadAsync() ? MapToEntity(reader) : null; } - /// - /// Retrieves a user account by email address using the usp_GetUserAccountByEmail stored procedure. - /// - /// The email address to search for. - /// The matching , or null if not found. - /// Thrown when the database command fails. + /// Uses the usp_GetUserAccountByEmail stored procedure. + /// public async Task GetByEmailAsync(string email) { await using DbConnection connection = await CreateConnection(); @@ -141,11 +120,6 @@ public class UserAccountRepository(ISqlConnectionFactory connectionFactory) return await reader.ReadAsync() ? MapToEntity(reader) : null; } - /// - /// Maps the current row of a data reader to a entity. - /// - /// The data reader positioned on the row to map. - /// The mapped instance. protected override UserAccount MapToEntity(DbDataReader reader) { return new UserAccount @@ -164,13 +138,7 @@ public class UserAccountRepository(ISqlConnectionFactory connectionFactory) }; } - /// - /// Helper method to add a parameter to a database command, converting null values to - /// . - /// - /// The command to add the parameter to. - /// The parameter name (including any prefix, e.g. "@Username"). - /// The parameter value, or null to bind . + /// Converts null values to when adding the parameter. private static void AddParameter(DbCommand command, string name, object? value) { DbParameter p = command.CreateParameter();