From be28a61d9ca1bb2375626d34c99ccb72097228ba Mon Sep 17 00:00:00 2001 From: TekH Date: Wed, 12 Aug 2026 16:08:29 +0200 Subject: [PATCH] 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. --- ...DigitalData.MessagingService.Domain.csproj | 4 + .../Entities/EmailAccount.cs | 23 +++ .../Entities/EmailAttachment.cs | 83 +++++++++++ .../Entities/ReceivedEmail.cs | 136 ++++++++++++++++++ 4 files changed, 246 insertions(+) create mode 100644 src/core/DigitalData.MessagingService.Domain/Entities/EmailAttachment.cs create mode 100644 src/core/DigitalData.MessagingService.Domain/Entities/ReceivedEmail.cs diff --git a/src/core/DigitalData.MessagingService.Domain/DigitalData.MessagingService.Domain.csproj b/src/core/DigitalData.MessagingService.Domain/DigitalData.MessagingService.Domain.csproj index 23957d0..de95276 100644 --- a/src/core/DigitalData.MessagingService.Domain/DigitalData.MessagingService.Domain.csproj +++ b/src/core/DigitalData.MessagingService.Domain/DigitalData.MessagingService.Domain.csproj @@ -11,4 +11,8 @@ + + + + diff --git a/src/core/DigitalData.MessagingService.Domain/Entities/EmailAccount.cs b/src/core/DigitalData.MessagingService.Domain/Entities/EmailAccount.cs index 5530c92..f8bab17 100644 --- a/src/core/DigitalData.MessagingService.Domain/Entities/EmailAccount.cs +++ b/src/core/DigitalData.MessagingService.Domain/Entities/EmailAccount.cs @@ -1,52 +1,75 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + namespace DigitalData.MessagingService.Domain.Entities; /// /// DTO for a single email account configuration. /// +[Table("EMAIL_ACCOUNT")] public class EmailAccount { /// /// Logical name to identify this account (e.g. "default", "support"). /// + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + [Column("ID", TypeName = "int")] public int Id { get; set; } + [Required] + [MaxLength(256)] + [Column("USERNAME", TypeName = "nvarchar(256)")] #if NET public required string Username { get; set; } #else public string Username { get; set; } = null!; #endif + [Required] + [MaxLength(512)] + [Column("PASSWORD", TypeName = "nvarchar(512)")] #if NET public required string Password { get; set; } #else public string Password { get; set; } = null!; #endif + [Required] + [MaxLength(256)] + [Column("SMTP_SERVER", TypeName = "nvarchar(256)")] #if NET public required string SmtpServer { get; set; } #else public string SmtpServer { get; set; } = null!; #endif + [Column("SMTP_PORT", TypeName = "int")] public int SmtpPort { get; set; } + [Column("SMTP_USE_SSL", TypeName = "bit")] public bool SmtpUseSsl { get; set; } + [Column("USE_OAUTH2", TypeName = "bit")] public bool UseOAuth2 { get; set; } /// /// IMAP server hostname (e.g. "imap.example.com"). /// Leave empty when this account is send-only. /// + [MaxLength(256)] + [Column("IMAP_SERVER", TypeName = "nvarchar(256)")] public string? ImapServer { get; set; } /// /// IMAP server port (993 for SSL, 143 for plain/STARTTLS). /// + [Column("IMAP_PORT", TypeName = "int")] public int ImapPort { get; set; } = 993; /// /// Use SSL/TLS when connecting to the IMAP server. /// + [Column("IMAP_USE_SSL", TypeName = "bit")] public bool ImapUseSsl { get; set; } = true; } \ No newline at end of file diff --git a/src/core/DigitalData.MessagingService.Domain/Entities/EmailAttachment.cs b/src/core/DigitalData.MessagingService.Domain/Entities/EmailAttachment.cs new file mode 100644 index 0000000..f7b4c61 --- /dev/null +++ b/src/core/DigitalData.MessagingService.Domain/Entities/EmailAttachment.cs @@ -0,0 +1,83 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace DigitalData.MessagingService.Domain.Entities; + +/// +/// Represents a single email attachment. +/// +[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 + + /// + /// Display name of the attachment (e.g. "invoice.pdf"). + /// + [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 + + /// + /// Raw content of the attachment. + /// + [Required] + [Column("CONTENT", TypeName = "varbinary(max)")] +#if NETFRAMEWORK + public byte[] Content { get; set; } = null!; +#else + public required byte[] Content { get; init; } +#endif + + /// + /// MIME content-type (e.g. "application/pdf", "image/png"). + /// Defaults to "application/octet-stream" when not specified. + /// + [Required] + [MaxLength(256)] + [Column("CONTENT_TYPE", TypeName = "nvarchar(256)")] + public string ContentType { get; set; } = "application/octet-stream"; + + /// + /// When the attachment is embedded inline and displayed + /// directly inside the email body via a CID reference (e.g. <img src="cid:logo">). + /// When (default) it appears as a regular downloadable attachment. + /// + [Column("IS_INLINE", TypeName = "bit")] + public bool IsInline { get; set; } = false; + + /// + /// Content-ID used when is . + /// Reference it in HTML body as cid:{ContentId}. + /// Auto-generated from when left empty. + /// + [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 +} diff --git a/src/core/DigitalData.MessagingService.Domain/Entities/ReceivedEmail.cs b/src/core/DigitalData.MessagingService.Domain/Entities/ReceivedEmail.cs new file mode 100644 index 0000000..4b1eb4d --- /dev/null +++ b/src/core/DigitalData.MessagingService.Domain/Entities/ReceivedEmail.cs @@ -0,0 +1,136 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace DigitalData.MessagingService.Domain.Entities; + +/// +/// Represents an email message received via IMAP. +/// +[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 + + /// + /// Unique identifier of the message on the IMAP server (UID). + /// + [Column("UID", TypeName = "bigint")] +#if NET + public long Uid { get; init; } +#else + public long Uid { get; set; } +#endif + + /// + /// Sender address (From header). + /// + [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 + + /// + /// Recipient addresses (To header). + /// + [Column("TO", TypeName = "nvarchar(max)")] +#if NET + public IEnumerable To { get; init; } = []; +#else + public IEnumerable To { get; set; } = []; +#endif + + /// + /// CC addresses. + /// + [Column("CC", TypeName = "nvarchar(max)")] +#if NET + public IEnumerable Cc { get; init; } = []; +#else + public IEnumerable Cc { get; set; } = []; +#endif + + /// + /// Email subject. + /// + [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 + + /// + /// Plain-text body (may be empty when only HTML is present). + /// + [Column("TEXT_BODY", TypeName = "nvarchar(max)")] +#if NET + public string TextBody { get; init; } = string.Empty; +#else + public string TextBody { get; set; } = string.Empty; +#endif + + /// + /// HTML body (may be empty when only plain-text is present). + /// + [Column("HTML_BODY", TypeName = "nvarchar(max)")] +#if NET + public string HtmlBody { get; init; } = string.Empty; +#else + public string HtmlBody { get; set; } = string.Empty; +#endif + + /// + /// Date/time the message was sent (Date header). + /// + [Column("DATE", TypeName = "datetime2")] +#if NET + public DateTime Date { get; init; } +#else + public DateTime Date { get; set; } +#endif + + /// + /// Whether the message has been marked as seen/read on the server. + /// + [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 + + /// + /// Attachments included with this message. + /// +#if NET + public IEnumerable? Attachments { get; init; } +#else + public IEnumerable? Attachments { get; set; } +#endif + +#if NET + public EmailAccount? Account { get; init; } +#else + public EmailAccount? Account { get; set; } +#endif +}