remove Features-directory and move the files to the root directory
This commit is contained in:
@@ -0,0 +1,180 @@
|
||||
using DigitalData.EmailProfiler.Application.Common.Dtos.EmailAttachments;
|
||||
using DigitalData.EmailProfiler.Application.Common.Dtos.EmailHistories;
|
||||
using DigitalData.EmailProfiler.Application.Interfaces.Repositories;
|
||||
using DigitalData.EmailProfiler.Application.Interfaces.Services;
|
||||
using DigitalData.EmailProfiler.Domain.Entities;
|
||||
using DigitalData.EmailProfiler.Domain.Enums;
|
||||
using DigitalData.EmailProfiler.Domain.Events;
|
||||
using DigitalData.EmailProfiler.Domain.Exceptions;
|
||||
using DigitalData.EmailProfiler.Domain.Services;
|
||||
using MediatR;
|
||||
|
||||
namespace DigitalData.EmailProfiler.Application.EmailProcessing.Commands;
|
||||
|
||||
/// <summary>
|
||||
/// Command to process a single email from a profile.
|
||||
/// This is the core email processing logic.
|
||||
/// </summary>
|
||||
public record ProcessEmailCommand : IRequest<int>
|
||||
{
|
||||
public int ProfileId { get; init; }
|
||||
public string MessageId { get; init; } = string.Empty;
|
||||
public string Sender { get; init; } = string.Empty;
|
||||
public DateTime ReceivedDate { get; init; }
|
||||
public string Subject { get; init; } = string.Empty;
|
||||
public string? BodyText { get; init; }
|
||||
public string? BodyHtml { get; init; }
|
||||
public List<AttachmentData> Attachments { get; init; } = new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attachment data for email processing.
|
||||
/// </summary>
|
||||
public record AttachmentData(
|
||||
string FileName,
|
||||
byte[] Content,
|
||||
string ContentType,
|
||||
long SizeBytes);
|
||||
|
||||
public class ProcessEmailCommandHandler(
|
||||
IEmailProfileRepository profileRepository,
|
||||
IEmailHistoryRepository historyRepository,
|
||||
IRepository<EmailAttachment> attachmentRepository,
|
||||
IPublisher publisher,
|
||||
MessageIdGenerator messageIdGenerator,
|
||||
IPdfProcessingService pdfService,
|
||||
IDmsService dmsService)
|
||||
: IRequestHandler<ProcessEmailCommand, int>
|
||||
{
|
||||
public async Task<int> Handle(ProcessEmailCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
// 1. Get profile with related entities
|
||||
var profile = await profileRepository.GetWithRelatedEntitiesAsync(request.ProfileId, cancellationToken)
|
||||
?? throw new DomainException($"Profile with ID {request.ProfileId} not found");
|
||||
|
||||
// 2. Generate message ID hash for duplicate detection
|
||||
var messageId = messageIdGenerator.Generate(
|
||||
request.MessageId,
|
||||
request.Sender,
|
||||
request.ReceivedDate,
|
||||
request.Subject);
|
||||
|
||||
// 3. Check for duplicates
|
||||
var isDuplicate = await historyRepository.IsDuplicateAsync(messageId.Hash, cancellationToken);
|
||||
if (isDuplicate)
|
||||
{
|
||||
throw new ValidationException("Email already processed (duplicate detected)", ErrorCode.DuplicateMessageId);
|
||||
}
|
||||
|
||||
// 4. Create email history record using DTO approach
|
||||
var historyDto = new CreateEmailHistoryDto(
|
||||
ProfileId: profile.Id,
|
||||
MessageIdHash: messageId.Hash,
|
||||
OriginalMessageId: request.MessageId,
|
||||
SenderAddress: request.Sender,
|
||||
EmailDate: request.ReceivedDate,
|
||||
Subject: request.Subject,
|
||||
EmailBodyText: request.BodyText,
|
||||
EmailBodyHtml: request.BodyHtml);
|
||||
|
||||
var createdHistory = await historyRepository.CreateAsync(historyDto, cancellationToken);
|
||||
|
||||
try
|
||||
{
|
||||
// 5. Process attachments
|
||||
foreach (var attachmentData in request.Attachments)
|
||||
{
|
||||
var attachmentDto = new CreateEmailAttachmentDto(
|
||||
EmailHistoryId: createdHistory.Id,
|
||||
OriginalFileName: attachmentData.FileName,
|
||||
SavedFileName: attachmentData.FileName, // TODO: Generate unique name
|
||||
FilePath: string.Empty, // TODO: Save to disk and get path
|
||||
FileSize: attachmentData.SizeBytes,
|
||||
Extension: Path.GetExtension(attachmentData.FileName),
|
||||
ContentType: attachmentData.ContentType,
|
||||
Content: attachmentData.Content);
|
||||
|
||||
var attachment = await attachmentRepository.CreateAsync(attachmentDto, cancellationToken);
|
||||
|
||||
// Validate PDF attachments - need to retrieve and update the entity
|
||||
if (attachmentData.ContentType.Contains("pdf", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
using var stream = new MemoryStream(attachmentData.Content);
|
||||
var isValidPdf = await pdfService.IsValidPdfAsync(stream, cancellationToken);
|
||||
|
||||
// For domain methods like MarkAsValid/MarkAsCorrupt, we need to get the entity
|
||||
var attachmentEntity = await attachmentRepository.GetByIdAsync(attachment.Id, cancellationToken);
|
||||
if (attachmentEntity != null)
|
||||
{
|
||||
if (isValidPdf)
|
||||
{
|
||||
attachmentEntity.MarkAsValid();
|
||||
}
|
||||
else
|
||||
{
|
||||
attachmentEntity.MarkAsCorrupt(ErrorCode.PdfStructureInvalid, "Invalid PDF structure");
|
||||
}
|
||||
|
||||
// Update directly using UpdateSingleAsync for safety
|
||||
await attachmentRepository.UpdateSingleAsync(
|
||||
a => a.Id == attachmentEntity.Id,
|
||||
new UpdateEmailAttachmentStatusDto(
|
||||
Status: attachmentEntity.Status ?? string.Empty,
|
||||
ValidationErrorCode: attachmentEntity.ValidationErrorCode,
|
||||
ValidationErrorMessage: attachmentEntity.ValidationErrorMessage),
|
||||
cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 6. Archive to DMS if configured
|
||||
if (profile.EmailProcess != null && profile.EmailProcess.EnableWindreamImport)
|
||||
{
|
||||
// TODO: Implement DMS archiving with indexing steps
|
||||
// This will be implemented based on ProcessSteps and IndexingSteps
|
||||
}
|
||||
|
||||
// 7. Mark as processed - retrieve entity for domain method
|
||||
var historyEntity = await historyRepository.GetByIdAsync(createdHistory.Id, cancellationToken);
|
||||
if (historyEntity != null)
|
||||
{
|
||||
historyEntity.MarkAsProcessed();
|
||||
await historyRepository.UpdateSingleAsync(
|
||||
h => h.Id == historyEntity.Id,
|
||||
new UpdateEmailHistoryStatusDto(
|
||||
Status: historyEntity.Status ?? string.Empty,
|
||||
ProcessedDate: historyEntity.ProcessedDate),
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
// 8. Publish domain event
|
||||
await publisher.Publish(
|
||||
new EmailProcessedEvent(
|
||||
createdHistory.Id,
|
||||
profile.Id,
|
||||
messageId.Hash,
|
||||
EmailStatus.Processed),
|
||||
cancellationToken);
|
||||
|
||||
return createdHistory.Id;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Mark as failed - retrieve entity for domain method
|
||||
var historyEntity = await historyRepository.GetByIdAsync(createdHistory.Id, cancellationToken);
|
||||
if (historyEntity != null)
|
||||
{
|
||||
historyEntity.MarkAsFailed(ErrorCode.AttachmentExtractionFailed, ex.Message);
|
||||
await historyRepository.UpdateSingleAsync(
|
||||
h => h.Id == historyEntity.Id,
|
||||
new UpdateEmailHistoryStatusDto(
|
||||
Status: historyEntity.Status ?? string.Empty,
|
||||
ErrorCodeValue: historyEntity.ErrorCodeValue,
|
||||
ErrorMessage: historyEntity.ErrorMessage),
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user