Skip to content

Commit

Permalink
Merge pull request #1 from Lombiq/issue/OFFI-134
Browse files Browse the repository at this point in the history
OFFI-134: Add email client with IMAP email download, background sync and content types
  • Loading branch information
sarahelsaig authored Feb 18, 2025
2 parents b7690f1 + 0119da2 commit 3d4f6d2
Show file tree
Hide file tree
Showing 35 changed files with 1,056 additions and 12 deletions.
21 changes: 11 additions & 10 deletions .github/workflows/validate-nuget-publish.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
name: Validate NuGet Publish

on:
pull_request:
push:
branches:
- dev

jobs:
validate-nuget-publish:
name: Validate NuGet Publish
uses: Lombiq/GitHub-Actions/.github/workflows/validate-nuget-publish.yml@dev
# Temporarily commented out until making it a fully compliant OSS module.
## on:
## pull_request:
## push:
## branches:
## - dev
##
## jobs:
## validate-nuget-publish:
## name: Validate NuGet Publish
## uses: Lombiq/GitHub-Actions/.github/workflows/validate-nuget-publish.yml@dev
10 changes: 10 additions & 0 deletions Lombiq.EmailClient/Constants/FeatureIds.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace Lombiq.EmailClient.Constants;

public static class FeatureIds
{
public const string Area = "Lombiq.EmailClient";

public const string Default = Area;
public const string Imap = Area + "." + nameof(Imap);
public const string EmailSync = Area + "." + nameof(EmailSync);
}
6 changes: 6 additions & 0 deletions Lombiq.EmailClient/Constants/Protocols.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Lombiq.EmailClient.Constants;

public static class Protocols
{
public const string Imap = "IMAP";
}
53 changes: 53 additions & 0 deletions Lombiq.EmailClient/Drivers/EmailSyncSettingsDisplayDriver.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
using Lombiq.EmailClient.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using OrchardCore.DisplayManagement.Entities;
using OrchardCore.DisplayManagement.Handlers;
using OrchardCore.DisplayManagement.Views;
using OrchardCore.Settings;
using System;
using System.Threading.Tasks;

namespace Lombiq.EmailClient.Drivers;

public class EmailSyncSettingsDisplayDriver : SiteDisplayDriver<EmailSyncSettings>
{
public const string GroupId = nameof(EmailSyncSettings);

private readonly IAuthorizationService _authorizationService;
private readonly IHttpContextAccessor _hca;

protected override string SettingsGroupId => GroupId;

public EmailSyncSettingsDisplayDriver(IAuthorizationService authorizationService, IHttpContextAccessor hca)
{
_authorizationService = authorizationService;
_hca = hca;
}

public override async Task<IDisplayResult> EditAsync(ISite model, EmailSyncSettings section, BuildEditorContext context)
{
if (!await AuthorizeAsync(context)) return null;

return Initialize<EmailSyncSettings>($"{nameof(EmailSyncSettings)}_Edit", section.CopyTo)
.PlaceInContent()
.OnGroup(GroupId);
}

public override async Task<IDisplayResult> UpdateAsync(ISite model, EmailSyncSettings section, UpdateEditorContext context)
{
if (await AuthorizeAsync(context) &&
await context.CreateModelAsync<EmailSyncSettings>(Prefix) is { } viewModel)
{
viewModel.CopyTo(section);
}

return await EditAsync(model, section, context);
}

private Task<bool> AuthorizeAsync(BuildEditorContext context) =>
_hca.HttpContext?.User is { } user &&
context.GroupId.EqualsOrdinalIgnoreCase(GroupId)
? _authorizationService.AuthorizeAsync(user, Permissions.EmailSyncPermissions.ManageEmailSyncSettings)
: Task.FromResult(false);
}
53 changes: 53 additions & 0 deletions Lombiq.EmailClient/Drivers/ImapSettingsDisplayDriver.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
using Lombiq.EmailClient.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using OrchardCore.DisplayManagement.Entities;
using OrchardCore.DisplayManagement.Handlers;
using OrchardCore.DisplayManagement.Views;
using OrchardCore.Settings;
using System;
using System.Threading.Tasks;

namespace Lombiq.EmailClient.Drivers;

public class ImapSettingsDisplayDriver : SiteDisplayDriver<ImapSettings>
{
public const string GroupId = nameof(ImapSettings);

private readonly IAuthorizationService _authorizationService;
private readonly IHttpContextAccessor _hca;

protected override string SettingsGroupId => GroupId;

public ImapSettingsDisplayDriver(IAuthorizationService authorizationService, IHttpContextAccessor hca)
{
_authorizationService = authorizationService;
_hca = hca;
}

public override async Task<IDisplayResult> EditAsync(ISite model, ImapSettings section, BuildEditorContext context)
{
if (!await AuthorizeAsync(context)) return null;

return Initialize<ImapSettings>($"{nameof(ImapSettings)}_Edit", section.CopyTo)
.PlaceInContent()
.OnGroup(GroupId);
}

public override async Task<IDisplayResult> UpdateAsync(ISite model, ImapSettings section, UpdateEditorContext context)
{
if (await AuthorizeAsync(context) &&
await context.CreateModelAsync<ImapSettings>(Prefix) is { } viewModel)
{
viewModel.CopyTo(section);
}

return await EditAsync(model, section, context);
}

private Task<bool> AuthorizeAsync(BuildEditorContext context) =>
_hca.HttpContext?.User is { } user &&
context.GroupId.EqualsOrdinalIgnoreCase(GroupId)
? _authorizationService.AuthorizeAsync(user, Permissions.ImapPermissions.ManageImapSettings)
: Task.FromResult(false);
}
44 changes: 44 additions & 0 deletions Lombiq.EmailClient/Lombiq.EmailClient.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<Project Sdk="Microsoft.NET.Sdk.Razor">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<AddRazorSupportForMvc>true</AddRazorSupportForMvc>
<DefaultItemExcludes>$(DefaultItemExcludes);.git*;node_modules\**</DefaultItemExcludes>
</PropertyGroup>

<PropertyGroup>
<Title>Lombiq Email Client for Orchard Core</Title>
<Authors>Lombiq Technologies</Authors>
<Copyright>Copyright © 2025, Lombiq Technologies Ltd.</Copyright>
<Description>Lombiq Email Client for Orchard Core: With the help of this module, you can do advanced email operations such as downloading them using IMAP.</Description>
<PackageIcon>NuGetIcon.png</PackageIcon>
<PackageTags>OrchardCore;Lombiq;AspNetCore;Email;IMAP</PackageTags>
<RepositoryUrl>https://github.com/Lombiq/Orchard-Email-Client</RepositoryUrl>
<PackageProjectUrl>https://github.com/Lombiq/Orchard-Email-Client/blob/dev/Readme.md</PackageProjectUrl>
<PackageLicenseExpression>BSD-3-Clause</PackageLicenseExpression>
</PropertyGroup>

<ItemGroup>
<None Remove="node_modules\**" />
<None Include="../Readme.md" Link="Readme.md" />
<None Include="NuGetIcon.png" Pack="true" PackagePath="" />
</ItemGroup>

<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="MailKit" Version="4.9.0" />
<PackageReference Include="OrchardCore.Module.Targets" Version="2.1.0" />
</ItemGroup>

<ItemGroup Condition="'$(NuGetBuild)' != 'true'">
<ProjectReference Include="..\..\..\Libraries\Lombiq.HelpfulLibraries\Lombiq.HelpfulLibraries.OrchardCore\Lombiq.HelpfulLibraries.OrchardCore.csproj" />
</ItemGroup>

<ItemGroup Condition="'$(NuGetBuild)' == 'true'">
<PackageReference Include="Lombiq.HelpfulLibraries.OrchardCore" Version="12.0.0" />
</ItemGroup>

</Project>
40 changes: 40 additions & 0 deletions Lombiq.EmailClient/Manifest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using OrchardCore.Modules.Manifest;
using static Lombiq.EmailClient.Constants.FeatureIds;

[assembly: Module(
Name = "Lombiq Email Client",
Author = "Lombiq Technologies",
Website = "https://github.com/Lombiq/Orchard-Email-Client",
Version = "0.0.1"
)]

[assembly: Feature(
Id = Default,
Name = "Lombiq Email Client - Base",
Category = "Email",
Description = "Base functionality for the email client such as content types. Should be used along with a " +
"specific email provider feature (e.g., IMAP).",
EnabledByDependencyOnly = true
)]

[assembly: Feature(
Id = Imap,
Name = "Lombiq Email Client - IMAP",
Category = "Email",
Description = "IMAP email provider for the email client.",
Dependencies =
[
Default,
]
)]

[assembly: Feature(
Id = EmailSync,
Name = "Lombiq Email Client - Email Sync",
Category = "Email",
Description = "Syncs emails from an email provider periodically.",
Dependencies =
[
Default,
]
)]
24 changes: 24 additions & 0 deletions Lombiq.EmailClient/Models/AttachmentMetadata.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System.Net.Mime;

namespace Lombiq.EmailClient.Models;

/// <summary>
/// Represents metadata for an attachment, such as its file name, MIME type, and size.
/// </summary>
public class AttachmentMetadata
{
/// <summary>
/// Gets or sets the file name of the attachment.
/// </summary>
public string FileName { get; set; }

/// <summary>
/// Gets or sets the MIME type of the attachment (e.g., <see cref="MediaTypeNames.Application.Pdf"/>).
/// </summary>
public string MimeType { get; set; }

/// <summary>
/// Gets or sets the size of the attachment in bytes.
/// </summary>
public long? Size { get; set; }
}
17 changes: 17 additions & 0 deletions Lombiq.EmailClient/Models/EmailAddress.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
namespace Lombiq.EmailClient.Models;

/// <summary>
/// Represents an email address with a display name and the actual address.
/// </summary>
public class EmailAddress
{
/// <summary>
/// Gets or sets the display name of the email address (e.g., "John Doe").
/// </summary>
public string DisplayName { get; set; }

/// <summary>
/// Gets or sets the actual email address (e.g., "[email protected]").
/// </summary>
public string Address { get; set; }
}
17 changes: 17 additions & 0 deletions Lombiq.EmailClient/Models/EmailBody.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
namespace Lombiq.EmailClient.Models;

/// <summary>
/// Represents the body of an email, including its content and format.
/// </summary>
public class EmailBody
{
/// <summary>
/// Gets or sets the main body of the email as plain text or HTML.
/// </summary>
public string Body { get; set; }

/// <summary>
/// Gets or sets a value indicating whether the body contains HTML content.
/// </summary>
public bool IsHtml { get; set; }
}
19 changes: 19 additions & 0 deletions Lombiq.EmailClient/Models/EmailContent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System.Collections.Generic;

namespace Lombiq.EmailClient.Models;

/// <summary>
/// Represents the content of an email, including its body and attachments.
/// </summary>
public class EmailContent
{
/// <summary>
/// Gets or sets the body of the email, including its content and format information.
/// </summary>
public EmailBody Body { get; set; }

/// <summary>
/// Gets the metadata of the attachments associated with this email.
/// </summary>
public IList<AttachmentMetadata> Attachments { get; private set; } = [];
}
7 changes: 7 additions & 0 deletions Lombiq.EmailClient/Models/EmailFilterParameters.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Lombiq.EmailClient.Models;

public class EmailFilterParameters
{
public string Subject { get; set; }
public uint AfterImapUniqueId { get; set; }
}
41 changes: 41 additions & 0 deletions Lombiq.EmailClient/Models/EmailHeader.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using System;
using System.Collections.Generic;

namespace Lombiq.EmailClient.Models;

/// <summary>
/// Represents the headers of an email, including sender, recipients, subject, and dates.
/// </summary>
public class EmailHeader
{
/// <summary>
/// Gets or sets the subject line of the email.
/// </summary>
public string Subject { get; set; }

/// <summary>
/// Gets or sets the sender of the email, including their display name and email address.
/// </summary>
public EmailAddress Sender { get; set; }

/// <summary>
/// Gets the primary recipients of the email (To field).
/// </summary>
public IList<EmailAddress> To { get; private set; } = [];

/// <summary>
/// Gets the carbon copy recipients of the email (Cc field).
/// </summary>
public IList<EmailAddress> Cc { get; private set; } = [];

/// <summary>
/// Gets the blind carbon copy recipients of the email (Bcc field).
/// </summary>
public IList<EmailAddress> Bcc { get; private set; } = [];

/// <summary>
/// Gets or sets the date and time when the email was sent by the sender's client.
/// This value is in UTC but reflects the sender's system clock and timezone.
/// </summary>
public DateTime? SentDateUtc { get; set; }
}
24 changes: 24 additions & 0 deletions Lombiq.EmailClient/Models/EmailMessage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
namespace Lombiq.EmailClient.Models;

/// <summary>
/// Represents the metadata, headers, and content of an email, including protocol-specific details,
/// sender and recipient information, and message body.
/// </summary>
public class EmailMessage
{
/// <summary>
/// Gets or sets the protocol-specific metadata of the email (e.g., Message-ID, protocol name, and folder information).
/// </summary>
public EmailMetadata Metadata { get; set; }

/// <summary>
/// Gets or sets the headers of the email, including sender, recipients, and subject.
/// </summary>
public EmailHeader Header { get; set; }

/// <summary>
/// Gets or sets the content of the email, including its body and format details.
/// This is populated only after the email is downloaded.
/// </summary>
public EmailContent Content { get; set; }
}
Loading

0 comments on commit 3d4f6d2

Please sign in to comment.