feat(application): add MediatR commands and handlers with exception improvements

MediatR Commands (CQRS Pattern):
- CreateEmailProfileCommand + Handler
- UpdateEmailProfileCommand + Handler
- DeleteEmailProfileCommand + Handler
- CreateEmailAccountCommand + Handler
- ProcessEmailCommand + Handler (core email processing logic)

Command Handlers:
- Create/Update/Delete operations for EmailProfile
- Create operation for EmailAccount
- ProcessEmail: Complete email processing workflow including:
  * Duplicate detection using MessageId hash
  * Email history creation
  * PDF attachment validation
  * windream DMS archiving support (placeholder)
  * Domain event publishing (EmailProcessedEvent)
  * Error handling and status tracking

Exception Improvements:
- Added ErrorCode property to DomainException
- Added ErrorCode overload to ValidationException
- Simplified AttachmentProcessingException to use base ErrorCode

Field Mappings Fixed:
- EmailProfile: ProcessId (not EmailProcessId), ValidationSql (not SenderFilter/SubjectFilter)
- EmailAccount: Username, EncryptedPassword, UseOAuth2, EncryptedClientSecret
- EmailHistory: SenderAddress, EmailDate, OriginalMessageId, EmailBodyText/Html
- EmailAttachment: OriginalFileName, SavedFileName, FilePath, FileSize
- Audit fields: AddedWhen/AddedWho, ChangedWhen/ChangedWho (not CreatedDate/By, ModifiedDate/By)

All commands follow Clean Architecture and use UnitOfWork pattern.
Build successful with 1 minor warning (dmsService marked for future implementation).
This commit is contained in:
2026-07-08 15:51:51 +02:00
parent 3778c0b338
commit 45654796b7
13 changed files with 348 additions and 6 deletions

View File

@@ -0,0 +1,16 @@
using MediatR;
namespace DigitalData.EmailProfiler.Application.Features.EmailProfiles.Commands;
/// <summary>
/// Command to create a new email profile.
/// </summary>
public record CreateEmailProfileCommand : IRequest<int>
{
public string ProfileName { get; init; } = string.Empty;
public int EmailAccountId { get; init; }
public int? ProcessId { get; init; }
public string? ValidationSql { get; init; }
public int PollIntervalMinutes { get; init; } = 15;
public bool IsActive { get; init; } = true;
}

View File

@@ -0,0 +1,29 @@
using DigitalData.EmailProfiler.Application.Interfaces.Repositories;
using DigitalData.EmailProfiler.Domain.Entities;
using MediatR;
namespace DigitalData.EmailProfiler.Application.Features.EmailProfiles.Commands;
public class CreateEmailProfileCommandHandler(IUnitOfWork unitOfWork)
: IRequestHandler<CreateEmailProfileCommand, int>
{
public async Task<int> Handle(CreateEmailProfileCommand request, CancellationToken cancellationToken)
{
var profile = new EmailProfile
{
ProfileName = request.ProfileName,
EmailAccountId = request.EmailAccountId,
ProcessId = request.ProcessId,
ValidationSql = request.ValidationSql,
PollIntervalMinutes = request.PollIntervalMinutes,
IsActive = request.IsActive,
AddedWhen = DateTime.Now,
AddedWho = "System" // TODO: Get from current user context
};
var createdProfile = await unitOfWork.EmailProfiles.AddAsync(profile, cancellationToken);
await unitOfWork.SaveChangesAsync(cancellationToken);
return createdProfile.Id;
}
}

View File

@@ -0,0 +1,8 @@
using MediatR;
namespace DigitalData.EmailProfiler.Application.Features.EmailProfiles.Commands;
/// <summary>
/// Command to delete an email profile.
/// </summary>
public record DeleteEmailProfileCommand(int Id) : IRequest<Unit>;

View File

@@ -0,0 +1,20 @@
using DigitalData.EmailProfiler.Application.Interfaces.Repositories;
using DigitalData.EmailProfiler.Domain.Exceptions;
using MediatR;
namespace DigitalData.EmailProfiler.Application.Features.EmailProfiles.Commands;
public class DeleteEmailProfileCommandHandler(IUnitOfWork unitOfWork)
: IRequestHandler<DeleteEmailProfileCommand, Unit>
{
public async Task<Unit> Handle(DeleteEmailProfileCommand request, CancellationToken cancellationToken)
{
var profile = await unitOfWork.EmailProfiles.GetByIdAsync(request.Id, cancellationToken)
?? throw new DomainException($"Email profile with ID {request.Id} not found");
await unitOfWork.EmailProfiles.DeleteAsync(profile, cancellationToken);
await unitOfWork.SaveChangesAsync(cancellationToken);
return Unit.Value;
}
}

View File

@@ -0,0 +1,15 @@
using MediatR;
namespace DigitalData.EmailProfiler.Application.Features.EmailProfiles.Commands;
/// <summary>
/// Command to update an existing email profile.
/// </summary>
public record UpdateEmailProfileCommand : IRequest<Unit>
{
public int Id { get; init; }
public string ProfileName { get; init; } = string.Empty;
public string? ValidationSql { get; init; }
public int PollIntervalMinutes { get; init; }
public bool IsActive { get; init; }
}

View File

@@ -0,0 +1,27 @@
using DigitalData.EmailProfiler.Application.Interfaces.Repositories;
using DigitalData.EmailProfiler.Domain.Exceptions;
using MediatR;
namespace DigitalData.EmailProfiler.Application.Features.EmailProfiles.Commands;
public class UpdateEmailProfileCommandHandler(IUnitOfWork unitOfWork)
: IRequestHandler<UpdateEmailProfileCommand, Unit>
{
public async Task<Unit> Handle(UpdateEmailProfileCommand request, CancellationToken cancellationToken)
{
var profile = await unitOfWork.EmailProfiles.GetByIdAsync(request.Id, cancellationToken)
?? throw new DomainException($"Email profile with ID {request.Id} not found");
profile.ProfileName = request.ProfileName;
profile.ValidationSql = request.ValidationSql;
profile.PollIntervalMinutes = request.PollIntervalMinutes;
profile.IsActive = request.IsActive;
profile.ChangedWhen = DateTime.Now;
profile.ChangedWho = "System"; // TODO: Get from current user context
await unitOfWork.EmailProfiles.UpdateAsync(profile, cancellationToken);
await unitOfWork.SaveChangesAsync(cancellationToken);
return Unit.Value;
}
}