remove Features-directory and move the files to the root directory

This commit is contained in:
2026-07-15 15:03:12 +02:00
parent bfe24eba06
commit 8f2365d048
21 changed files with 27 additions and 27 deletions

View File

@@ -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;
}
}
}