Wave/Wave/Services/EmailFactory.cs

113 lines
5.9 KiB
C#

using System.Collections.Frozen;
using System.Globalization;
using System.Net;
using System.Text;
using Microsoft.Extensions.Options;
using MimeKit;
using StackExchange.Redis;
using Wave.Data;
using Wave.Utilities;
namespace Wave.Services;
public class EmailFactory(IOptions<Customization> customizations, EmailTemplateService templateService) {
private Customization Customizations { get; } = customizations.Value;
private EmailTemplateService TemplateService { get; } = templateService;
public async ValueTask<IEmail> CreateDefaultEmail(string receiverMail, string? receiverName, string subject, string title, string bodyHtml, string bodyPlain = "") {
(string host, string logo) = GetStaticData();
string body = await TemplateService.DefaultAsync(host, logo, title, bodyHtml);
return new StaticEmail(receiverMail, receiverName, subject, title, body, $"{title}\n\n{bodyPlain}", FrozenDictionary<string, string>.Empty);
}
public async ValueTask<IEmail> CreateConfirmationEmail(EmailSubscriber subscriber, string subject, string title, string bodyHtml, string bodyPlain = "", string confirmLabel = "confirm") {
(string user, string token) = await TemplateService.CreateConfirmTokensAsync(subscriber.Id);
(string? host, string _) = GetStaticData();
string confirmLink = new Uri(
new Uri(host, UriKind.Absolute),
new Uri($"/Email/Confirm?user={WebUtility.UrlEncode(user)}&token={WebUtility.UrlEncode(token)}", UriKind.Relative))
.AbsoluteUri;
return await CreateDefaultEmail(subscriber.Email, subscriber.Name, subject, title,
string.Format(bodyHtml, Customizations.AppName)
+ $"""<p style="text-align: center"><a href="{confirmLink}">{confirmLabel}</a></p>""",
string.Format(bodyPlain, Customizations.AppName) + $"\n\n{confirmLabel}: {confirmLink}");
}
public async ValueTask<IEmail> CreateSubscribedEmail(EmailSubscriber subscriber, string browserLink, string subject, string title, string bodyHtml, string bodyPlain = "", string role = "unknown", string? replyTo = null) {
(string host, string logo) = GetStaticData();
string unsubscribeLink = await GetUnsubscribeLink(host, subscriber.Id, role);
string body = await TemplateService.NewsletterAsync(host, browserLink, logo, title, bodyHtml, unsubscribeLink);
string footer = await TemplateService.GetPartialAsync("email-plain-footer");
bodyPlain += "\n\n" + footer.Replace(
$"[[{EmailTemplateService.Constants.EmailUnsubscribeLink}]]",
unsubscribeLink, true, CultureInfo.InvariantCulture);
var headers = new Dictionary<string, string>{
{HeaderId.ListUnsubscribe.ToHeaderName(), $"<{unsubscribeLink}>"},
{HeaderId.ListUnsubscribePost.ToHeaderName(), "One-Click"}
};
if (!string.IsNullOrWhiteSpace(replyTo)) headers.Add(HeaderId.ReplyTo.ToHeaderName(), replyTo);
return new StaticEmail(subscriber.Email, subscriber.Name, subject, title, body, $"{title}\n\n{bodyPlain}", headers.ToFrozenDictionary());
}
public async ValueTask<IEmail> CreateWelcomeEmail(EmailSubscriber subscriber, IEnumerable<EmailNewsletter> articles, string subject, string title, string bodyHtml, string bodyPlain = "") {
(string host, string logo) = GetStaticData();
string articlePartial = await TemplateService.GetPartialAsync("email-article");
string footer = await TemplateService.GetPartialAsync("email-plain-footer");
var articlesHtml = new StringBuilder("");
var articlesPlain = new StringBuilder("");
foreach (var n in articles) {
string articleLink = ArticleUtilities.GenerateArticleLink(n.Article, new Uri(Customizations.AppUrl, UriKind.Absolute));
articlesHtml.AppendFormat(
articlePartial,
n.Article.Title, n.Article.Author.Name, n.Article.Body[..Math.Min(250, n.Article.Body.Length)], articleLink);
articlesPlain.AppendFormat("{0}\n\n{1}\n{2}\n{3}",
n.Article.Title, n.Article.Author.Name, n.Article.Body[..Math.Min(250, n.Article.Body.Length)], articleLink);
}
string unsubscribeLink = await GetUnsubscribeLink(host, subscriber.Id, "welcome");
string body = TemplateService.Welcome(host, logo, title, bodyHtml, unsubscribeLink, articlesHtml.ToString());
bodyPlain += "\n\n\n" + articlesPlain;
bodyPlain += "\n\n" + footer.Replace(
$"[[{EmailTemplateService.Constants.EmailUnsubscribeLink}]]",
unsubscribeLink, true, CultureInfo.InvariantCulture);
return new StaticEmail(subscriber.Email, subscriber.Name, subject, title, body, $"{title}\n\n{bodyPlain}", new Dictionary<string, string>{
{HeaderId.ListUnsubscribe.ToHeaderName(), $"<{unsubscribeLink}>"},
{HeaderId.ListUnsubscribePost.ToHeaderName(), "One-Click"}
}.ToFrozenDictionary());
}
public async ValueTask<string> CreateAuthorCard(ApplicationUser user, Uri host) {
var profileLink = new Uri(host, "/profile/" + user.Id);
var pfpLink = new Uri(host, "api/user/pfp/" + user.Id + "?size=100");
string partial = await TemplateService.GetPartialAsync("email-author");
return string.Format(partial, pfpLink, user.FullName, user.AboutTheAuthor, profileLink.AbsoluteUri);
}
public async ValueTask<Guid?> IsTokenValid(string id, string token) {
return await TemplateService.ValidateTokensAsync(id, token, deleteToken: false);
}
public async ValueTask ClearToken(string id, string token) {
await TemplateService.ValidateTokensAsync(id, token, deleteToken: true);
}
private (string host, string logo) GetStaticData() {
var host = new Uri(string.IsNullOrWhiteSpace(Customizations.AppUrl) ? "" : Customizations.AppUrl); // TODO get link
string logo = !string.IsNullOrWhiteSpace(Customizations.LogoLink)
? Customizations.LogoLink
: new Uri(host, "/img/logo.png").AbsoluteUri;
return (host.AbsoluteUri, logo);
}
private async ValueTask<string> GetUnsubscribeLink(string host, Guid subscriberId, string role) {
(string user, string token) = await TemplateService.CreateConfirmTokensAsync(subscriberId, "unsubscribe-"+role, TimeSpan.FromDays(30));
return new Uri(new Uri(host), $"/Email/Unsubscribe?newsletter={role}&user={WebUtility.UrlEncode(user)}&token={WebUtility.UrlEncode(token)}").AbsoluteUri;
}
}