Add email domain entities and .NET Framework support

Introduced `EmailAccount`, `EmailAttachment`, and `ReceivedEmail`
entities with database mappings using data annotations. These
entities represent email account configurations, attachments,
and received emails, respectively.

Added conditional compilation to ensure compatibility between
.NET and .NET Framework. Updated `DigitalData.MessagingService.Domain.csproj`
to include `System.ComponentModel.DataAnnotations` for `net462`.

Defined relationships between entities, including navigation
properties and foreign key constraints.
This commit is contained in:
2026-08-12 16:08:29 +02:00
parent b2857c558f
commit be28a61d9c
4 changed files with 246 additions and 0 deletions

View File

@@ -11,4 +11,8 @@
<PackageReference Include="MediatR" Version="12.2.0" /> <PackageReference Include="MediatR" Version="12.2.0" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net462'">
<Reference Include="System.ComponentModel.DataAnnotations" />
</ItemGroup>
</Project> </Project>

View File

@@ -1,52 +1,75 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace DigitalData.MessagingService.Domain.Entities; namespace DigitalData.MessagingService.Domain.Entities;
/// <summary> /// <summary>
/// DTO for a single email account configuration. /// DTO for a single email account configuration.
/// </summary> /// </summary>
[Table("EMAIL_ACCOUNT")]
public class EmailAccount public class EmailAccount
{ {
/// <summary> /// <summary>
/// Logical name to identify this account (e.g. "default", "support"). /// Logical name to identify this account (e.g. "default", "support").
/// </summary> /// </summary>
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Column("ID", TypeName = "int")]
public int Id { get; set; } public int Id { get; set; }
[Required]
[MaxLength(256)]
[Column("USERNAME", TypeName = "nvarchar(256)")]
#if NET #if NET
public required string Username { get; set; } public required string Username { get; set; }
#else #else
public string Username { get; set; } = null!; public string Username { get; set; } = null!;
#endif #endif
[Required]
[MaxLength(512)]
[Column("PASSWORD", TypeName = "nvarchar(512)")]
#if NET #if NET
public required string Password { get; set; } public required string Password { get; set; }
#else #else
public string Password { get; set; } = null!; public string Password { get; set; } = null!;
#endif #endif
[Required]
[MaxLength(256)]
[Column("SMTP_SERVER", TypeName = "nvarchar(256)")]
#if NET #if NET
public required string SmtpServer { get; set; } public required string SmtpServer { get; set; }
#else #else
public string SmtpServer { get; set; } = null!; public string SmtpServer { get; set; } = null!;
#endif #endif
[Column("SMTP_PORT", TypeName = "int")]
public int SmtpPort { get; set; } public int SmtpPort { get; set; }
[Column("SMTP_USE_SSL", TypeName = "bit")]
public bool SmtpUseSsl { get; set; } public bool SmtpUseSsl { get; set; }
[Column("USE_OAUTH2", TypeName = "bit")]
public bool UseOAuth2 { get; set; } public bool UseOAuth2 { get; set; }
/// <summary> /// <summary>
/// IMAP server hostname (e.g. "imap.example.com"). /// IMAP server hostname (e.g. "imap.example.com").
/// Leave empty when this account is send-only. /// Leave empty when this account is send-only.
/// </summary> /// </summary>
[MaxLength(256)]
[Column("IMAP_SERVER", TypeName = "nvarchar(256)")]
public string? ImapServer { get; set; } public string? ImapServer { get; set; }
/// <summary> /// <summary>
/// IMAP server port (993 for SSL, 143 for plain/STARTTLS). /// IMAP server port (993 for SSL, 143 for plain/STARTTLS).
/// </summary> /// </summary>
[Column("IMAP_PORT", TypeName = "int")]
public int ImapPort { get; set; } = 993; public int ImapPort { get; set; } = 993;
/// <summary> /// <summary>
/// Use SSL/TLS when connecting to the IMAP server. /// Use SSL/TLS when connecting to the IMAP server.
/// </summary> /// </summary>
[Column("IMAP_USE_SSL", TypeName = "bit")]
public bool ImapUseSsl { get; set; } = true; public bool ImapUseSsl { get; set; } = true;
} }

View File

@@ -0,0 +1,83 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace DigitalData.MessagingService.Domain.Entities;
/// <summary>
/// Represents a single email attachment.
/// </summary>
[Table("EMAIL_ATTACHMENT")]
public sealed class EmailAttachment
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Column("ID", TypeName = "bigint")]
#if NET
public long Id { get; init; }
#else
public long Id { get; set; }
#endif
/// <summary>
/// Display name of the attachment (e.g. "invoice.pdf").
/// </summary>
[Required]
[MaxLength(260)]
[Column("FILE_NAME", TypeName = "nvarchar(260)")]
#if NETFRAMEWORK
public string FileName { get; set; } = null!;
#else
public required string FileName { get; init; }
#endif
/// <summary>
/// Raw content of the attachment.
/// </summary>
[Required]
[Column("CONTENT", TypeName = "varbinary(max)")]
#if NETFRAMEWORK
public byte[] Content { get; set; } = null!;
#else
public required byte[] Content { get; init; }
#endif
/// <summary>
/// MIME content-type (e.g. "application/pdf", "image/png").
/// Defaults to "application/octet-stream" when not specified.
/// </summary>
[Required]
[MaxLength(256)]
[Column("CONTENT_TYPE", TypeName = "nvarchar(256)")]
public string ContentType { get; set; } = "application/octet-stream";
/// <summary>
/// When <see langword="true"/> the attachment is embedded inline and displayed
/// directly inside the email body via a CID reference (e.g. &lt;img src="cid:logo"&gt;).
/// When <see langword="false"/> (default) it appears as a regular downloadable attachment.
/// </summary>
[Column("IS_INLINE", TypeName = "bit")]
public bool IsInline { get; set; } = false;
/// <summary>
/// Content-ID used when <see cref="IsInline"/> is <see langword="true"/>.
/// Reference it in HTML body as <c>cid:{ContentId}</c>.
/// Auto-generated from <see cref="FileName"/> when left empty.
/// </summary>
[MaxLength(512)]
[Column("CONTENT_ID", TypeName = "nvarchar(512)")]
public string? ContentId { get; set; }
[ForeignKey(nameof(Email))]
[Column("EMAIL_ID", TypeName = "bigint")]
#if NET
public long EmailId { get; init; }
#else
public long EmailId { get; set; }
#endif
#if NET
public ReceivedEmail? Email { get; init; }
#else
public ReceivedEmail? Email { get; set; }
#endif
}

View File

@@ -0,0 +1,136 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace DigitalData.MessagingService.Domain.Entities;
/// <summary>
/// Represents an email message received via IMAP.
/// </summary>
[Table("RECEIVED_EMAIL")]
public sealed record ReceivedEmail
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Column("ID", TypeName = "bigint")]
#if NET
public long Id { get; init; }
#else
public long Id { get; set; }
#endif
/// <summary>
/// Unique identifier of the message on the IMAP server (UID).
/// </summary>
[Column("UID", TypeName = "bigint")]
#if NET
public long Uid { get; init; }
#else
public long Uid { get; set; }
#endif
/// <summary>
/// Sender address (From header).
/// </summary>
[Required]
[MaxLength(512)]
[Column("FROM", TypeName = "nvarchar(512)")]
#if NET
public string From { get; init; } = string.Empty;
#else
public string From { get; set; } = string.Empty;
#endif
/// <summary>
/// Recipient addresses (To header).
/// </summary>
[Column("TO", TypeName = "nvarchar(max)")]
#if NET
public IEnumerable<string> To { get; init; } = [];
#else
public IEnumerable<string> To { get; set; } = [];
#endif
/// <summary>
/// CC addresses.
/// </summary>
[Column("CC", TypeName = "nvarchar(max)")]
#if NET
public IEnumerable<string> Cc { get; init; } = [];
#else
public IEnumerable<string> Cc { get; set; } = [];
#endif
/// <summary>
/// Email subject.
/// </summary>
[MaxLength(998)]
[Column("SUBJECT", TypeName = "nvarchar(998)")]
#if NET
public string Subject { get; init; } = string.Empty;
#else
public string Subject { get; set; } = string.Empty;
#endif
/// <summary>
/// Plain-text body (may be empty when only HTML is present).
/// </summary>
[Column("TEXT_BODY", TypeName = "nvarchar(max)")]
#if NET
public string TextBody { get; init; } = string.Empty;
#else
public string TextBody { get; set; } = string.Empty;
#endif
/// <summary>
/// HTML body (may be empty when only plain-text is present).
/// </summary>
[Column("HTML_BODY", TypeName = "nvarchar(max)")]
#if NET
public string HtmlBody { get; init; } = string.Empty;
#else
public string HtmlBody { get; set; } = string.Empty;
#endif
/// <summary>
/// Date/time the message was sent (Date header).
/// </summary>
[Column("DATE", TypeName = "datetime2")]
#if NET
public DateTime Date { get; init; }
#else
public DateTime Date { get; set; }
#endif
/// <summary>
/// Whether the message has been marked as seen/read on the server.
/// </summary>
[Column("IS_SEEN", TypeName = "bit")]
#if NET
public bool IsSeen { get; init; }
#else
public bool IsSeen { get; set; }
#endif
[ForeignKey(nameof(Account))]
[Column("ACCOUNT_ID", TypeName = "int")]
#if NET
public int AccountId { get; init; }
#else
public int AccountId { get; set; }
#endif
/// <summary>
/// Attachments included with this message.
/// </summary>
#if NET
public IEnumerable<EmailAttachment>? Attachments { get; init; }
#else
public IEnumerable<EmailAttachment>? Attachments { get; set; }
#endif
#if NET
public EmailAccount? Account { get; init; }
#else
public EmailAccount? Account { get; set; }
#endif
}