refactor: Rename EmailPorifler to MessagingService

This commit is contained in:
2026-07-24 13:59:43 +02:00
parent 77d3b52d16
commit 5e587da957
58 changed files with 202 additions and 202 deletions

View File

@@ -0,0 +1,9 @@
namespace DigitalData.MessagingService.Domain.Common;
public abstract class BaseEntity
{
public DateTime? CreatedDate { get; set; }
public string? CreatedBy { get; set; }
public DateTime? ModifiedDate { get; set; }
public string? ModifiedBy { get; set; }
}

View File

@@ -0,0 +1,19 @@
namespace DigitalData.MessagingService.Domain.Common;
/// <summary>
/// Domain-wide constants
/// </summary>
public static class DomainConstants
{
/// <summary>
/// Email processing constants
/// </summary>
public static class Email
{
/// <summary>
/// Maximum number of retry attempts for failed email sending
/// After this limit, email will be moved to Dead Letter Queue (DLQ)
/// </summary>
public const int MaxRetryCount = 3;
}
}

View File

@@ -0,0 +1,8 @@
namespace DigitalData.MessagingService.Domain.Common;
/// <summary>
/// Marker interface for aggregate roots in DDD
/// </summary>
public interface IAggregateRoot
{
}

View File

@@ -0,0 +1,43 @@
namespace DigitalData.MessagingService.Domain.Common;
/// <summary>
/// Base class for value objects that implement equality by value
/// </summary>
public abstract class ValueObject
{
protected abstract IEnumerable<object> GetEqualityComponents();
public override bool Equals(object? obj)
{
if (obj == null || obj.GetType() != GetType())
{
return false;
}
var other = (ValueObject)obj;
return GetEqualityComponents().SequenceEqual(other.GetEqualityComponents());
}
public override int GetHashCode()
{
return GetEqualityComponents()
.Select(x => x?.GetHashCode() ?? 0)
.Aggregate((x, y) => x ^ y);
}
public static bool operator ==(ValueObject? left, ValueObject? right)
{
if (left is null && right is null)
return true;
if (left is null || right is null)
return false;
return left.Equals(right);
}
public static bool operator !=(ValueObject? left, ValueObject? right)
{
return !(left == right);
}
}

View File

@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MediatR" Version="12.2.0" />
</ItemGroup>
<ItemGroup>
<Folder Include="Events\" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,9 @@
namespace DigitalData.MessagingService.Domain.Enums;
public enum AttachmentStatus
{
Pending = 1,
Valid = 2,
Corrupt = 3,
Skipped = 4
}

View File

@@ -0,0 +1,7 @@
namespace DigitalData.MessagingService.Domain.Enums;
public enum AuthenticationType
{
UsernamePassword = 1,
OAuth2 = 2
}

View File

@@ -0,0 +1,11 @@
namespace DigitalData.MessagingService.Domain.Enums;
public enum EmailStatus
{
Pending = 1,
Processing = 2,
Processed = 3,
Failed = 4,
PartiallyProcessed = 5,
Rejected = 6
}

View File

@@ -0,0 +1,16 @@
namespace DigitalData.MessagingService.Domain.Enums;
public enum ErrorCode
{
None = 0,
NoAttachments = 10001,
SenderValidationFailed = 10002,
EmbeddedFileAttachmentCorrupt = 10003,
NormalFileAttachmentCorrupt = 10004,
PdfStructureInvalid = 10005,
ImapConnectionFailed = 10006,
WindreamImportFailed = 10007,
DiskSpaceInsufficient = 10008,
DuplicateMessageId = 10009,
AttachmentExtractionFailed = 10010
}

View File

@@ -0,0 +1,8 @@
namespace DigitalData.MessagingService.Domain.Enums;
public enum ProcessType
{
ProcessManager = 1, // Easy Approval workflow
AttachmentSniffer = 2, // General attachment extraction
ZugFeRDParser = 3 // Electronic invoice processing
}

View File

@@ -0,0 +1,15 @@
using DigitalData.MessagingService.Domain.Enums;
namespace DigitalData.MessagingService.Domain.Exceptions;
public class AttachmentProcessingException : DomainException
{
public AttachmentProcessingException(ErrorCode errorCode, string message) : base(message, errorCode)
{
}
public AttachmentProcessingException(ErrorCode errorCode, string message, Exception innerException)
: base(message, errorCode)
{
}
}

View File

@@ -0,0 +1,17 @@
namespace DigitalData.MessagingService.Domain.Exceptions;
/// <summary>
/// Exception thrown when OAuth2 authentication fails.
/// </summary>
public class AuthenticationFailedException : Exception
{
public AuthenticationFailedException(string message)
: base(message)
{
}
public AuthenticationFailedException(string message, Exception innerException)
: base(message, innerException)
{
}
}

View File

@@ -0,0 +1,22 @@
namespace DigitalData.MessagingService.Domain.Exceptions;
/// <summary>
/// Exception thrown when DMS (windream) is not available or not configured properly.
/// </summary>
public class DmsNotAvailableException : Exception
{
public DmsNotAvailableException()
: base("DMS service is not available. windream COM components may not be registered.")
{
}
public DmsNotAvailableException(string message)
: base(message)
{
}
public DmsNotAvailableException(string message, Exception innerException)
: base(message, innerException)
{
}
}

View File

@@ -0,0 +1,21 @@
using DigitalData.MessagingService.Domain.Enums;
namespace DigitalData.MessagingService.Domain.Exceptions;
public class DomainException : Exception
{
public ErrorCode? ErrorCode { get; }
public DomainException(string message) : base(message)
{
}
public DomainException(string message, ErrorCode errorCode) : base(message)
{
ErrorCode = errorCode;
}
public DomainException(string message, Exception innerException) : base(message, innerException)
{
}
}

View File

@@ -0,0 +1,17 @@
namespace DigitalData.MessagingService.Domain.Exceptions;
/// <summary>
/// Exception thrown when a PDF file is invalid or corrupted.
/// </summary>
public class InvalidPdfException : Exception
{
public InvalidPdfException(string message)
: base(message)
{
}
public InvalidPdfException(string message, Exception innerException)
: base(message, innerException)
{
}
}

View File

@@ -0,0 +1,22 @@
namespace DigitalData.MessagingService.Domain.Exceptions;
/// <summary>
/// Exception thrown when a requested entity is not found.
/// </summary>
public class NotFoundException : Exception
{
public NotFoundException(string entityName, object key)
: base($"{entityName} with key '{key}' was not found.")
{
}
public NotFoundException(string message)
: base(message)
{
}
public NotFoundException(string message, Exception innerException)
: base(message, innerException)
{
}
}

View File

@@ -0,0 +1,18 @@
using DigitalData.MessagingService.Domain.Enums;
namespace DigitalData.MessagingService.Domain.Exceptions;
public class ValidationException : DomainException
{
public ValidationException(string message) : base(message)
{
}
public ValidationException(string message, ErrorCode errorCode) : base(message, errorCode)
{
}
public ValidationException(string message, Exception innerException) : base(message, innerException)
{
}
}

View File

@@ -0,0 +1,28 @@
using DigitalData.MessagingService.Domain.ValueObjects;
namespace DigitalData.MessagingService.Domain.Services;
public interface IMessageIdGenerator
{
MessageId Generate(string originalMessageId, string sender, DateTime date, string subject);
List<string> GenerateFallbackHashes(MessageId messageId);
}
public class MessageIdGenerator : IMessageIdGenerator
{
public MessageId Generate(string originalMessageId, string sender, DateTime date, string subject)
{
return MessageId.Create(originalMessageId, sender, date, subject);
}
public List<string> GenerateFallbackHashes(MessageId messageId)
{
// Legacy behavior: 10 variations for duplicate detection
var hashes = new List<string> { messageId.Hash };
// Add variations (this is simplified - legacy had 10 variations)
// TODO: Implement exact legacy fallback algorithm if needed
return hashes;
}
}

View File

@@ -0,0 +1,45 @@
using System.ComponentModel.DataAnnotations;
using DigitalData.MessagingService.Domain.Common;
using DigitalData.MessagingService.Domain.Exceptions;
namespace DigitalData.MessagingService.Domain.ValueObjects;
/// <summary>
/// Value object representing an email address with validation
/// </summary>
public class EmailAddress : ValueObject
{
public string Value { get; private set; }
public string Domain { get; private set; }
public string LocalPart { get; private set; }
private EmailAddress(string value)
{
Value = value;
var parts = value.Split('@');
LocalPart = parts[0];
Domain = parts[1];
}
public static EmailAddress Create(string email)
{
if (!IsValid(email))
throw new DomainException($"Invalid email address: {email}");
return new EmailAddress(email.ToLowerInvariant());
}
private static bool IsValid(string email)
{
return !string.IsNullOrWhiteSpace(email) &&
email.Contains('@') &&
new EmailAddressAttribute().IsValid(email);
}
protected override IEnumerable<object> GetEqualityComponents()
{
yield return Value;
}
public override string ToString() => Value;
}

View File

@@ -0,0 +1,41 @@
using System.Security.Cryptography;
using System.Text;
using DigitalData.MessagingService.Domain.Common;
namespace DigitalData.MessagingService.Domain.ValueObjects;
/// <summary>
/// Value object representing a unique message identifier with hash generation
/// Uses the same algorithm as legacy system for compatibility
/// </summary>
public class MessageId : ValueObject
{
public string Value { get; private set; }
public string Hash { get; private set; }
private MessageId(string value)
{
Value = value;
Hash = GenerateHash(value);
}
public static MessageId Create(string originalMessageId, string sender, DateTime date, string subject)
{
var combined = $"{originalMessageId}|{sender}|{date:yyyyMMddHHmmss}|{subject}";
return new MessageId(combined);
}
private static string GenerateHash(string input)
{
// Same algorithm as legacy: SHA256 hash
using var sha256 = SHA256.Create();
var bytes = Encoding.UTF8.GetBytes(input);
var hash = sha256.ComputeHash(bytes);
return Convert.ToBase64String(hash);
}
protected override IEnumerable<object> GetEqualityComponents()
{
yield return Value;
}
}