refactor(application): replace GetSenderQuery with GetEmailAccountQuery and add EmailAccountModificationCommand

- Rename GetSenderQuery to GetEmailAccountQuery with updated return type (IEnumerable<EmailAccountDto>)
- Handler now queries IRepository<EmailAccount> directly instead of chaining MediatR dispatches
- Add EmailAccountModificationCommand for create/update/delete operations on email accounts
- Update GetSenderQueryValidator to reference renamed query type
- Remove exclusive Id/Username validation rule (now handled by repo query logic)
This commit is contained in:
2026-08-13 11:57:39 +02:00
parent 84da5cb646
commit 50eaefb868
4 changed files with 76 additions and 48 deletions

View File

@@ -0,0 +1,36 @@
#if NET
using DigitalData.MessagingService.Application.Common.Dto.EmailAccounts;
using DigitalData.MessagingService.Application.Common.Interfaces.Repositories;
using DigitalData.MessagingService.Application.Common.ValueObjects;
using DigitalData.MessagingService.Domain.Entities;
using MediatR;
using System.Security.Principal;
namespace DigitalData.MessagingService.Application.EmailAccounts.Commands;
/// <summary>
/// DTO for a single email account configuration.
/// </summary>
public record EmailAccountModificationCommand : IRequest<(EmailAccount, bool)>
{
public required EmailAccountModificationDto ModifiedAccount { get; init; }
public required Modification Modification { get; init; }
}
/// <summary>
///
/// </summary>
/// <param name="Repo"></param>
public class EmailAccountModificationCommandHandler(IRepository<EmailAccount> Repo) : IRequestHandler<EmailAccountModificationCommand, (EmailAccount, bool)>
{
public async Task<(EmailAccount, bool)> Handle(EmailAccountModificationCommand request, CancellationToken cancellationToken)
{
return request.Modification switch
{
Modification.Upsert => await Repo.UpsertAsync(a => a.Username == request.ModifiedAccount.Username, request.ModifiedAccount, cancellationToken),
_ => throw new NotSupportedException($"Modification type '{request.Modification}' is not supported in email account modification."),
};
}
}
#endif

View File

@@ -0,0 +1,37 @@
#if NET
using MediatR;
using DigitalData.MessagingService.Domain.Entities;
using DigitalData.MessagingService.Application.Common.Interfaces.Repositories;
using AutoMapper;
using DigitalData.MessagingService.Domain.Exceptions;
using DigitalData.MessagingService.Application.Common.Dto.EmailAccounts;
namespace DigitalData.MessagingService.Application.EmailAccounts.Queries;
public record GetEmailAccountQuery : IRequest<IEnumerable<EmailAccountDto>>
{
public int? Id { get; init; }
public string? Username { get; init; }
}
/// <summary>
/// Handles queries for retrieving email accounts.
/// </summary>
/// <param name="Repo"></param>
/// <param name="Mapper"></param>
public class GetEmailAccountQueryHandler(IRepository<EmailAccount> Repo, IMapper Mapper) : IRequestHandler<GetEmailAccountQuery, IEnumerable<EmailAccountDto>>
{
public async Task<IEnumerable<EmailAccountDto>> Handle(GetEmailAccountQuery request, CancellationToken cancellationToken)
{
var accounts = request.Id is null && request.Username is null
? await Repo.GetAllAsync(cancellationToken)
: await Repo.FindAsync(request.Id is int id ? x => x.Id == id : x => x.Username == request.Username, cancellationToken: cancellationToken);
if (accounts.Any())
return Mapper.Map<IEnumerable<EmailAccountDto>>(accounts);
else
throw new NotFoundException($"No email account found for the given criteria ({(request.Id is not null ? $"Id: {request.Id}" : $"Username: {request.Username}")}).");
}
}
#endif

View File

@@ -1,41 +0,0 @@
#if NET
using DigitalData.MessagingService.Application.Common.Options;
using MediatR;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using DigitalData.MessagingService.Domain.Entities;
namespace DigitalData.MessagingService.Application.EmailAccounts.Queries;
public record GetSenderQuery : IRequest<EmailAccount?>
{
public int? Id { get; init; }
public string? Username { get; init; }
}
/// <summary>
///
/// </summary>
/// <param name="Options"></param>
/// <param name="Logger"></param>
public class GetSenderQueryHandler(IOptions<EmailAccountsOptions> Options, ILogger<GetSenderQueryHandler> Logger) : IRequestHandler<GetSenderQuery, EmailAccount?>
{
public Task<EmailAccount?> Handle(GetSenderQuery request, CancellationToken cancellationToken)
{
var accounts = request.Id is not null
? Options.Value.Accounts.Where(a => a.Id == request.Id)
: Options.Value.Accounts.Where(a => a.Username == request.Username);
if(accounts.Count() > 1)
{
Logger.LogWarning(
"Multiple email accounts found for the given criteria ({Criteria}). Returning the first one.",
request.Id is not null ? $"Id: {request.Id}" : $"Username: {request.Username}"
);
}
return Task.FromResult(accounts.FirstOrDefault());
}
}
#endif

View File

@@ -5,17 +5,13 @@ using FluentValidation;
namespace DigitalData.MessagingService.Application.EmailAccounts.Validators;
/// <summary>
/// Validator for <see cref="GetSenderQuery"/>.
/// Either <see cref="GetSenderQuery.Id"/> or <see cref="GetSenderQuery.Username"/> must be provided, but not both.
/// Validator for <see cref="GetEmailAccountQuery"/>.
/// Either <see cref="GetEmailAccountQuery.Id"/> or <see cref="GetEmailAccountQuery.Username"/> must be provided, but not both.
/// </summary>
public class GetSenderQueryValidator : AbstractValidator<GetSenderQuery>
public class GetSenderQueryValidator : AbstractValidator<GetEmailAccountQuery>
{
public GetSenderQueryValidator()
{
RuleFor(x => x)
.Must(x => (x.Id is not null) ^ (x.Username is not null))
.WithMessage("Either Id or Username must be provided, but not both.");
When(x => x.Username is not null, () =>
{
RuleFor(x => x.Username)