mirror of
https://github.com/aaronpo97/the-biergarten-app.git
synced 2026-07-16 17:47:22 +00:00
Migrate Auth and Emails to vertical slices (Features.Auth, Features.Emails)
Auth and Emails land together since Auth's registration/resend flows need Emails to exist first. Service.Auth's four services (Register, Login, Confirmation, Token) collapse into MediatR commands/queries in Features.Auth; TokenService stays as a slice-internal service since multiple handlers call it. Auth no longer references Emails directly — it sends SendRegistrationEmailCommand/SendResendConfirmationEmailCommand (defined in Shared.Application) which Features.Emails handles, so neither slice has a project reference on the other. Service.Emails' IEmailService becomes Features.Emails' IEmailDispatcher, simplified to take (firstName, email, token) instead of a full UserAccount since that's all it ever used. API.Specs' TestApiFactory/MockEmailService are updated to swap in the relocated interface. Also deletes Infrastructure.Repository now that Breweries, UserManagement, and Auth have all moved their repos out of it, and replaces the two now-dead docker-compose test services (repository.tests, service.auth.tests, both pointing at deleted Dockerfiles) with a single unit.tests service that runs every Features.*.Tests project via Dockerfile.tests.
This commit is contained in:
@@ -27,14 +27,12 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Domain\Domain.Entities\Domain.Entities.csproj" />
|
||||
<ProjectReference Include="..\..\Domain\Domain.Exceptions\Domain.Exceptions.csproj" />
|
||||
<ProjectReference Include="..\..\Infrastructure\Infrastructure.Email\Infrastructure.Email.csproj" />
|
||||
<ProjectReference Include="..\..\Infrastructure\Infrastructure.Email.Templates\Infrastructure.Email.Templates.csproj" />
|
||||
<ProjectReference Include="..\..\Infrastructure\Infrastructure.Repository\Infrastructure.Repository.csproj" />
|
||||
<ProjectReference Include="..\..\Infrastructure\Infrastructure.Sql\Infrastructure.Sql.csproj" />
|
||||
<ProjectReference Include="..\..\Infrastructure\Infrastructure.Jwt\Infrastructure.Jwt.csproj" />
|
||||
<ProjectReference Include="..\..\Service\Service.Auth\Service.Auth.csproj" />
|
||||
<ProjectReference Include="..\..\Features\Features.Breweries\Features.Breweries.csproj" />
|
||||
<ProjectReference Include="..\..\Features\Features.UserManagement\Features.UserManagement.csproj" />
|
||||
<ProjectReference Include="..\..\Features\Features.Auth\Features.Auth.csproj" />
|
||||
<ProjectReference Include="..\..\Features\Features.Emails\Features.Emails.csproj" />
|
||||
<ProjectReference Include="..\..\Shared\Shared.Contracts\Shared.Contracts.csproj" />
|
||||
<ProjectReference Include="..\..\Shared\Shared.Application\Shared.Application.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
using Domain.Entities;
|
||||
using Org.BouncyCastle.Asn1.Cms;
|
||||
|
||||
namespace API.Core.Contracts.Auth;
|
||||
|
||||
/// <summary>
|
||||
/// Payload returned to the client after a successful login or token refresh.
|
||||
/// </summary>
|
||||
/// <param name="UserAccountId">The unique identifier of the authenticated user account.</param>
|
||||
/// <param name="Username">The username of the authenticated user account.</param>
|
||||
/// <param name="RefreshToken">The newly issued refresh token used to obtain new access tokens.</param>
|
||||
/// <param name="AccessToken">The newly issued JWT access token used to authorize subsequent requests.</param>
|
||||
public record LoginPayload(
|
||||
Guid UserAccountId,
|
||||
string Username,
|
||||
string RefreshToken,
|
||||
string AccessToken
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Payload returned to the client after a successful registration.
|
||||
/// </summary>
|
||||
/// <param name="UserAccountId">The unique identifier of the newly created user account.</param>
|
||||
/// <param name="Username">The username of the newly created user account.</param>
|
||||
/// <param name="RefreshToken">The refresh token issued for the new account.</param>
|
||||
/// <param name="AccessToken">The JWT access token issued for the new account.</param>
|
||||
/// <param name="ConfirmationEmailSent">Whether a confirmation email was successfully sent to the new user.</param>
|
||||
public record RegistrationPayload(
|
||||
Guid UserAccountId,
|
||||
string Username,
|
||||
string RefreshToken,
|
||||
string AccessToken,
|
||||
bool ConfirmationEmailSent
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Payload returned to the client after a user account's email has been confirmed.
|
||||
/// </summary>
|
||||
/// <param name="UserAccountId">The unique identifier of the user account that was confirmed.</param>
|
||||
/// <param name="ConfirmedDate">The date and time at which the account was confirmed.</param>
|
||||
public record ConfirmationPayload(Guid UserAccountId, DateTime ConfirmedDate);
|
||||
@@ -1,37 +0,0 @@
|
||||
using Shared.Contracts;
|
||||
using FluentValidation;
|
||||
|
||||
namespace API.Core.Contracts.Auth;
|
||||
|
||||
/// <summary>
|
||||
/// Request body for the login endpoint, containing the credentials used to authenticate a user.
|
||||
/// </summary>
|
||||
public record LoginRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// The username of the account attempting to log in.
|
||||
/// </summary>
|
||||
public string Username { get; init; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// The plaintext password of the account attempting to log in.
|
||||
/// </summary>
|
||||
public string Password { get; init; } = default!;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates <see cref="LoginRequest"/> instances before they are processed by the login endpoint.
|
||||
/// </summary>
|
||||
public class LoginRequestValidator : AbstractValidator<LoginRequest>
|
||||
{
|
||||
/// <summary>
|
||||
/// Configures validation rules requiring both <see cref="LoginRequest.Username"/> and
|
||||
/// <see cref="LoginRequest.Password"/> to be non-empty.
|
||||
/// </summary>
|
||||
public LoginRequestValidator()
|
||||
{
|
||||
RuleFor(x => x.Username).NotEmpty().WithMessage("Username is required");
|
||||
|
||||
RuleFor(x => x.Password).NotEmpty().WithMessage("Password is required");
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
using FluentValidation;
|
||||
|
||||
namespace API.Core.Contracts.Auth;
|
||||
|
||||
/// <summary>
|
||||
/// Request body for the token refresh endpoint, containing the refresh token used to obtain a new access token.
|
||||
/// </summary>
|
||||
public record RefreshTokenRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// The refresh token previously issued to the client during login, registration, or a prior refresh.
|
||||
/// </summary>
|
||||
public string RefreshToken { get; init; } = default!;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates <see cref="RefreshTokenRequest"/> instances before they are processed by the refresh endpoint.
|
||||
/// </summary>
|
||||
public class RefreshTokenRequestValidator
|
||||
: AbstractValidator<RefreshTokenRequest>
|
||||
{
|
||||
/// <summary>
|
||||
/// Configures a validation rule requiring <see cref="RefreshTokenRequest.RefreshToken"/> to be non-empty.
|
||||
/// </summary>
|
||||
public RefreshTokenRequestValidator()
|
||||
{
|
||||
RuleFor(x => x.RefreshToken)
|
||||
.NotEmpty()
|
||||
.WithMessage("Refresh token is required");
|
||||
}
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
using Shared.Contracts;
|
||||
using FluentValidation;
|
||||
|
||||
namespace API.Core.Contracts.Auth;
|
||||
|
||||
/// <summary>
|
||||
/// Request body for the registration endpoint, containing the details needed to create a new user account.
|
||||
/// </summary>
|
||||
/// <param name="Username">The desired username; must be 3-64 characters and contain only letters, numbers, dots, underscores, and hyphens.</param>
|
||||
/// <param name="FirstName">The user's first name; up to 128 characters.</param>
|
||||
/// <param name="LastName">The user's last name; up to 128 characters.</param>
|
||||
/// <param name="Email">The user's email address; up to 128 characters and must be a valid email format.</param>
|
||||
/// <param name="DateOfBirth">The user's date of birth; the user must be at least 19 years old.</param>
|
||||
/// <param name="Password">The desired plaintext password; must be at least 8 characters and contain an uppercase letter, a lowercase letter, a number, and a special character.</param>
|
||||
public record RegisterRequest(
|
||||
string Username,
|
||||
string FirstName,
|
||||
string LastName,
|
||||
string Email,
|
||||
DateTime DateOfBirth,
|
||||
string Password
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Validates <see cref="RegisterRequest"/> instances before they are processed by the registration endpoint.
|
||||
/// </summary>
|
||||
public class RegisterRequestValidator : AbstractValidator<RegisterRequest>
|
||||
{
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public RegisterRequestValidator()
|
||||
{
|
||||
RuleFor(x => x.Username)
|
||||
.NotEmpty()
|
||||
.WithMessage("Username is required")
|
||||
.Length(3, 64)
|
||||
.WithMessage("Username must be between 3 and 64 characters")
|
||||
.Matches("^[a-zA-Z0-9._-]+$")
|
||||
.WithMessage(
|
||||
"Username can only contain letters, numbers, dots, underscores, and hyphens"
|
||||
);
|
||||
|
||||
RuleFor(x => x.FirstName)
|
||||
.NotEmpty()
|
||||
.WithMessage("First name is required")
|
||||
.MaximumLength(128)
|
||||
.WithMessage("First name cannot exceed 128 characters");
|
||||
|
||||
RuleFor(x => x.LastName)
|
||||
.NotEmpty()
|
||||
.WithMessage("Last name is required")
|
||||
.MaximumLength(128)
|
||||
.WithMessage("Last name cannot exceed 128 characters");
|
||||
|
||||
RuleFor(x => x.Email)
|
||||
.NotEmpty()
|
||||
.WithMessage("Email is required")
|
||||
.EmailAddress()
|
||||
.WithMessage("Invalid email format")
|
||||
.MaximumLength(128)
|
||||
.WithMessage("Email cannot exceed 128 characters");
|
||||
|
||||
RuleFor(x => x.DateOfBirth)
|
||||
.NotEmpty()
|
||||
.WithMessage("Date of birth is required")
|
||||
.LessThan(DateTime.Today.AddYears(-19))
|
||||
.WithMessage("You must be at least 19 years old to register");
|
||||
|
||||
RuleFor(x => x.Password)
|
||||
.NotEmpty()
|
||||
.WithMessage("Password is required")
|
||||
.MinimumLength(8)
|
||||
.WithMessage("Password must be at least 8 characters")
|
||||
.Matches("[A-Z]")
|
||||
.WithMessage("Password must contain at least one uppercase letter")
|
||||
.Matches("[a-z]")
|
||||
.WithMessage("Password must contain at least one lowercase letter")
|
||||
.Matches("[0-9]")
|
||||
.WithMessage("Password must contain at least one number")
|
||||
.Matches("[^a-zA-Z0-9]")
|
||||
.WithMessage(
|
||||
"Password must contain at least one special character"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,173 +0,0 @@
|
||||
using API.Core.Contracts.Auth;
|
||||
using Shared.Contracts;
|
||||
using Domain.Entities;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Service.Auth;
|
||||
|
||||
namespace API.Core.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// Handles user authentication concerns: registration, login, email confirmation, and token refresh.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The controller is decorated with <c>[Authorize(AuthenticationSchemes = "JWT")]</c> by default, but most
|
||||
/// actions opt out via <c>[AllowAnonymous]</c> since they are entry points used before a caller holds a token.
|
||||
/// </remarks>
|
||||
/// <param name="registerService">Service used to register new user accounts.</param>
|
||||
/// <param name="loginService">Service used to authenticate existing user accounts.</param>
|
||||
/// <param name="confirmationService">Service used to confirm user accounts and resend confirmation emails.</param>
|
||||
/// <param name="tokenService">Service used to refresh JWT access/refresh token pairs.</param>
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
[Authorize(AuthenticationSchemes = "JWT")]
|
||||
public class AuthController(
|
||||
IRegisterService registerService,
|
||||
ILoginService loginService,
|
||||
IConfirmationService confirmationService,
|
||||
ITokenService tokenService
|
||||
) : ControllerBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Registers a new user account.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Allows anonymous access. On success, responds with <c>201 Created</c> containing the new user's ID,
|
||||
/// username, issued refresh/access tokens, and whether a confirmation email was sent.
|
||||
/// </remarks>
|
||||
/// <param name="req">The registration details, including username, name, email, date of birth, and password.</param>
|
||||
/// <returns>A <c>201 Created</c> result wrapping a <see cref="ResponseBody{T}"/> of <see cref="RegistrationPayload"/>.</returns>
|
||||
[AllowAnonymous]
|
||||
[HttpPost("register")]
|
||||
public async Task<ActionResult<UserAccount>> Register(
|
||||
[FromBody] RegisterRequest req
|
||||
)
|
||||
{
|
||||
var rtn = await registerService.RegisterAsync(
|
||||
new UserAccount
|
||||
{
|
||||
UserAccountId = Guid.Empty,
|
||||
Username = req.Username,
|
||||
FirstName = req.FirstName,
|
||||
LastName = req.LastName,
|
||||
Email = req.Email,
|
||||
DateOfBirth = req.DateOfBirth,
|
||||
},
|
||||
req.Password
|
||||
);
|
||||
|
||||
var response = new ResponseBody<RegistrationPayload>
|
||||
{
|
||||
Message = "User registered successfully.",
|
||||
Payload = new RegistrationPayload(
|
||||
rtn.UserAccount.UserAccountId,
|
||||
rtn.UserAccount.Username,
|
||||
rtn.RefreshToken,
|
||||
rtn.AccessToken,
|
||||
rtn.EmailSent
|
||||
),
|
||||
};
|
||||
return Created("/", response);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Authenticates a user with a username and password.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Allows anonymous access. On success, responds with <c>200 OK</c> containing the user's ID,
|
||||
/// username, and a newly issued refresh/access token pair.
|
||||
/// </remarks>
|
||||
/// <param name="req">The login credentials (username and password).</param>
|
||||
/// <returns>An <c>200 OK</c> result wrapping a <see cref="ResponseBody{T}"/> of <see cref="LoginPayload"/>.</returns>
|
||||
[AllowAnonymous]
|
||||
[HttpPost("login")]
|
||||
public async Task<ActionResult> Login([FromBody] LoginRequest req)
|
||||
{
|
||||
var rtn = await loginService.LoginAsync(req.Username, req.Password);
|
||||
|
||||
return Ok(
|
||||
new ResponseBody<LoginPayload>
|
||||
{
|
||||
Message = "Logged in successfully.",
|
||||
Payload = new LoginPayload(
|
||||
rtn.UserAccount.UserAccountId,
|
||||
rtn.UserAccount.Username,
|
||||
rtn.RefreshToken,
|
||||
rtn.AccessToken
|
||||
),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Confirms a user account using a confirmation token.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Requires JWT authentication. On success, responds with <c>200 OK</c> containing the confirmed
|
||||
/// user's ID and the confirmation timestamp.
|
||||
/// </remarks>
|
||||
/// <param name="token">The confirmation token supplied via the confirmation email link.</param>
|
||||
/// <returns>A <c>200 OK</c> result wrapping a <see cref="ResponseBody{T}"/> of <see cref="ConfirmationPayload"/>.</returns>
|
||||
[HttpPost("confirm")]
|
||||
public async Task<ActionResult> Confirm([FromQuery] string token)
|
||||
{
|
||||
var rtn = await confirmationService.ConfirmUserAsync(token);
|
||||
return Ok(
|
||||
new ResponseBody<ConfirmationPayload>
|
||||
{
|
||||
Message = "User with ID " + rtn.UserId + " is confirmed.",
|
||||
Payload = new ConfirmationPayload(
|
||||
rtn.UserId,
|
||||
rtn.ConfirmedAt
|
||||
),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resends the account confirmation email for the specified user.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Requires JWT authentication. On success, responds with <c>200 OK</c>.
|
||||
/// </remarks>
|
||||
/// <param name="userId">The unique identifier of the user account to resend the confirmation email for.</param>
|
||||
/// <returns>A <c>200 OK</c> result wrapping a <see cref="ResponseBody"/> confirming the email was resent.</returns>
|
||||
[HttpPost("confirm/resend")]
|
||||
public async Task<ActionResult> ResendConfirmation([FromQuery] Guid userId)
|
||||
{
|
||||
await confirmationService.ResendConfirmationEmailAsync(userId);
|
||||
return Ok(new ResponseBody { Message = "confirmation email has been resent" });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Exchanges a valid refresh token for a new access/refresh token pair.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Allows anonymous access. On success, responds with <c>200 OK</c> containing the user's ID,
|
||||
/// username, and the newly issued refresh/access token pair.
|
||||
/// </remarks>
|
||||
/// <param name="req">The request containing the refresh token to exchange.</param>
|
||||
/// <returns>A <c>200 OK</c> result wrapping a <see cref="ResponseBody{T}"/> of <see cref="LoginPayload"/>.</returns>
|
||||
[AllowAnonymous]
|
||||
[HttpPost("refresh")]
|
||||
public async Task<ActionResult> Refresh(
|
||||
[FromBody] RefreshTokenRequest req
|
||||
)
|
||||
{
|
||||
var rtn = await tokenService.RefreshTokenAsync(req.RefreshToken);
|
||||
|
||||
return Ok(
|
||||
new ResponseBody<LoginPayload>
|
||||
{
|
||||
Message = "Token refreshed successfully.",
|
||||
Payload = new LoginPayload(
|
||||
rtn.UserAccount.UserAccountId,
|
||||
rtn.UserAccount.Username,
|
||||
rtn.RefreshToken,
|
||||
rtn.AccessToken
|
||||
),
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,20 +21,19 @@ COPY ["Database/Database.Migrations/Database.Migrations.csproj", "Database/Datab
|
||||
COPY ["Database/Database.Seed/Database.Seed.csproj", "Database/Database.Seed/"]
|
||||
COPY ["Domain/Domain.Entities/Domain.Entities.csproj", "Domain/Domain.Entities/"]
|
||||
COPY ["Domain/Domain.Exceptions/Domain.Exceptions.csproj", "Domain/Domain.Exceptions/"]
|
||||
COPY ["Features/Features.Auth/Features.Auth.csproj", "Features/Features.Auth/"]
|
||||
COPY ["Features/Features.Auth.Tests/Features.Auth.Tests.csproj", "Features/Features.Auth.Tests/"]
|
||||
COPY ["Features/Features.Breweries/Features.Breweries.csproj", "Features/Features.Breweries/"]
|
||||
COPY ["Features/Features.Breweries.Tests/Features.Breweries.Tests.csproj", "Features/Features.Breweries.Tests/"]
|
||||
COPY ["Features/Features.Emails/Features.Emails.csproj", "Features/Features.Emails/"]
|
||||
COPY ["Features/Features.Emails.Tests/Features.Emails.Tests.csproj", "Features/Features.Emails.Tests/"]
|
||||
COPY ["Features/Features.UserManagement/Features.UserManagement.csproj", "Features/Features.UserManagement/"]
|
||||
COPY ["Features/Features.UserManagement.Tests/Features.UserManagement.Tests.csproj", "Features/Features.UserManagement.Tests/"]
|
||||
COPY ["Infrastructure/Infrastructure.Email/Infrastructure.Email.csproj", "Infrastructure/Infrastructure.Email/"]
|
||||
COPY ["Infrastructure/Infrastructure.Email.Templates/Infrastructure.Email.Templates.csproj", "Infrastructure/Infrastructure.Email.Templates/"]
|
||||
COPY ["Infrastructure/Infrastructure.Jwt/Infrastructure.Jwt.csproj", "Infrastructure/Infrastructure.Jwt/"]
|
||||
COPY ["Infrastructure/Infrastructure.PasswordHashing/Infrastructure.PasswordHashing.csproj", "Infrastructure/Infrastructure.PasswordHashing/"]
|
||||
COPY ["Infrastructure/Infrastructure.Repository/Infrastructure.Repository.csproj", "Infrastructure/Infrastructure.Repository/"]
|
||||
COPY ["Infrastructure/Infrastructure.Repository.Tests/Infrastructure.Repository.Tests.csproj", "Infrastructure/Infrastructure.Repository.Tests/"]
|
||||
COPY ["Infrastructure/Infrastructure.Sql/Infrastructure.Sql.csproj", "Infrastructure/Infrastructure.Sql/"]
|
||||
COPY ["Service/Service.Auth/Service.Auth.csproj", "Service/Service.Auth/"]
|
||||
COPY ["Service/Service.Auth.Tests/Service.Auth.Tests.csproj", "Service/Service.Auth.Tests/"]
|
||||
COPY ["Service/Service.Emails/Service.Emails.csproj", "Service/Service.Emails/"]
|
||||
COPY ["Shared/Shared.Application/Shared.Application.csproj", "Shared/Shared.Application/"]
|
||||
COPY ["Shared/Shared.Contracts/Shared.Contracts.csproj", "Shared/Shared.Contracts/"]
|
||||
RUN dotnet restore "Core.slnx"
|
||||
|
||||
@@ -1,19 +1,17 @@
|
||||
using API.Core;
|
||||
using API.Core.Authentication;
|
||||
using Features.Auth.Controllers;
|
||||
using Features.Auth.DependencyInjection;
|
||||
using Features.Breweries.Controllers;
|
||||
using Features.Breweries.DependencyInjection;
|
||||
using Features.Emails.DependencyInjection;
|
||||
using Features.Emails.Services;
|
||||
using Features.UserManagement.Controllers;
|
||||
using Features.UserManagement.DependencyInjection;
|
||||
using FluentValidation;
|
||||
using FluentValidation.AspNetCore;
|
||||
using Infrastructure.Email;
|
||||
using Infrastructure.Email.Templates.Rendering;
|
||||
using Infrastructure.Jwt;
|
||||
using Infrastructure.PasswordHashing;
|
||||
using Infrastructure.Repository.Auth;
|
||||
using Infrastructure.Sql;
|
||||
using Service.Auth;
|
||||
using Service.Emails;
|
||||
using Shared.Application.Behaviors;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
@@ -24,7 +22,8 @@ builder.Services.AddControllers(options =>
|
||||
options.Filters.Add<GlobalExceptionFilter>();
|
||||
})
|
||||
.AddApplicationPart(typeof(BreweryController).Assembly)
|
||||
.AddApplicationPart(typeof(UserController).Assembly);
|
||||
.AddApplicationPart(typeof(UserController).Assembly)
|
||||
.AddApplicationPart(typeof(AuthController).Assembly);
|
||||
|
||||
builder.Services.AddEndpointsApiExplorer();
|
||||
builder.Services.AddSwaggerGen();
|
||||
@@ -34,6 +33,7 @@ builder.Services.AddOpenApi();
|
||||
builder.Services.AddValidatorsFromAssemblyContaining<Program>();
|
||||
builder.Services.AddValidatorsFromAssembly(typeof(BreweryController).Assembly);
|
||||
builder.Services.AddValidatorsFromAssembly(typeof(UserController).Assembly);
|
||||
builder.Services.AddValidatorsFromAssembly(typeof(AuthController).Assembly);
|
||||
builder.Services.AddFluentValidationAutoValidation();
|
||||
|
||||
// Add MediatR. Each Features.* slice's assembly is registered here as it's introduced;
|
||||
@@ -43,6 +43,8 @@ builder.Services.AddMediatR(cfg =>
|
||||
cfg.RegisterServicesFromAssemblyContaining<Program>();
|
||||
cfg.RegisterServicesFromAssembly(typeof(BreweryController).Assembly);
|
||||
cfg.RegisterServicesFromAssembly(typeof(UserController).Assembly);
|
||||
cfg.RegisterServicesFromAssembly(typeof(AuthController).Assembly);
|
||||
cfg.RegisterServicesFromAssembly(typeof(IEmailDispatcher).Assembly);
|
||||
cfg.AddOpenBehavior(typeof(ValidationBehavior<,>));
|
||||
});
|
||||
|
||||
@@ -64,20 +66,14 @@ builder.Services.AddSingleton<
|
||||
DefaultSqlConnectionFactory
|
||||
>();
|
||||
|
||||
builder.Services.AddScoped<IAuthRepository, AuthRepository>();
|
||||
builder.Services.AddFeaturesBreweries();
|
||||
builder.Services.AddFeaturesUserManagement();
|
||||
builder.Services.AddFeaturesAuth();
|
||||
builder.Services.AddFeaturesEmails();
|
||||
|
||||
builder.Services.AddScoped<ILoginService, LoginService>();
|
||||
builder.Services.AddScoped<IRegisterService, RegisterService>();
|
||||
builder.Services.AddScoped<ITokenService, TokenService>();
|
||||
|
||||
// ITokenInfrastructure is registered here (not inside Features.Auth's own DI extension) because
|
||||
// JwtAuthenticationHandler, a host-level auth middleware concern, also depends on it directly.
|
||||
builder.Services.AddScoped<ITokenInfrastructure, JwtInfrastructure>();
|
||||
builder.Services.AddScoped<IPasswordInfrastructure, Argon2Infrastructure>();
|
||||
builder.Services.AddScoped<IEmailProvider, SmtpEmailProvider>();
|
||||
builder.Services.AddScoped<IEmailTemplateProvider, EmailTemplateProvider>();
|
||||
builder.Services.AddScoped<IEmailService, EmailService>();
|
||||
builder.Services.AddScoped<IConfirmationService, ConfirmationService>();
|
||||
|
||||
// Register the exception filter
|
||||
builder.Services.AddScoped<GlobalExceptionFilter>();
|
||||
|
||||
Reference in New Issue
Block a user