diff options
author | vnugent <public@vaughnnugent.com> | 2023-08-28 21:54:26 -0400 |
---|---|---|
committer | vnugent <public@vaughnnugent.com> | 2023-08-28 21:54:26 -0400 |
commit | b153adbd86e226ad805c2edbb90e4032d386a1b0 (patch) | |
tree | 9d3e5d7f2966c66e0264001cb38c67f74d6cf707 /lib/Emails.Transactional.Plugin/src/Templates | |
parent | 964e81b81cdb430ecee8f67a68e3c616b3f339aa (diff) |
Refactor overhaul, data extensions & Resend.com support
Diffstat (limited to 'lib/Emails.Transactional.Plugin/src/Templates')
4 files changed, 278 insertions, 0 deletions
diff --git a/lib/Emails.Transactional.Plugin/src/Templates/EmailTemplateStore.cs b/lib/Emails.Transactional.Plugin/src/Templates/EmailTemplateStore.cs new file mode 100644 index 0000000..d589262 --- /dev/null +++ b/lib/Emails.Transactional.Plugin/src/Templates/EmailTemplateStore.cs @@ -0,0 +1,148 @@ +/* +* Copyright (c) 2023 Vaughn Nugent +* +* Library: VNLib +* Package: Emails.Transactional +* File: EmailTemplateStore.cs +* +* EmailTemplateStore.cs is part of Emails.Transactional which is part of the larger +* VNLib collection of libraries and utilities. +* +* Emails.Transactional is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published +* by the Free Software Foundation, either version 2 of the License, +* or (at your option) any later version. +* +* Emails.Transactional is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with Emails.Transactional. If not, see http://www.gnu.org/licenses/. +*/ + + +using System; +using System.IO; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Collections.Generic; + +using Fluid; + +using VNLib.Utils.IO; +using VNLib.Utils.Extensions; +using VNLib.Utils.Memory.Caching; +using VNLib.Plugins; +using VNLib.Plugins.Extensions.Loading; +using VNLib.Plugins.Extensions.Data.Storage; + +using Emails.Transactional.Mta; + +namespace Emails.Transactional.Templates +{ + [ConfigurationName("templates")] + internal sealed class EmailTemplateStore : ITemplateStorage + { + private readonly FluidParser _parser; + private readonly Dictionary<string, EmailTemplate> _templateCache; + private readonly ISimpleFilesystem _filesystem; + private readonly TimeSpan _cacheValidFor; + + public EmailTemplateStore(PluginBase plugin, IConfigScope config) + { + _parser = new(); + _templateCache = new(StringComparer.OrdinalIgnoreCase); + + _cacheValidFor = config["cache_valid_for_sec"].GetTimeSpan(TimeParseType.Seconds); + string fsPath = config.GetRequiredProperty("template_path", e => e.GetString()!); + + //Get the filesystem + ISimpleFilesystem baseFs = plugin.GetOrCreateSingleton<MinioStorage>(); + + //Create a new scope for the base path + _filesystem = baseFs.CreateNewScope(fsPath); + } + + public Task<IEmailTemplate> GetTemplateAsync(string templateId, CancellationToken cancellation) + { + //try to get the template from the cache + if (_templateCache.TryGetOrEvictRecord(templateId, out EmailTemplate? template) > 0) + { + return Task.FromResult<IEmailTemplate>(template!); + } + + //Load the template from the store + return GetTemplateFromStoreAsync(templateId, cancellation); + } + + private async Task<IEmailTemplate> GetTemplateFromStoreAsync(string templateId, CancellationToken cancellation) + { + //memory stream for template data + using VnMemoryStream templateData = new(); + + //remove leading slash + if (templateId.StartsWith('/')) + { + templateId = templateId[1..]; + } + + //Recover template data + long read = await _filesystem.ReadFileAsync(templateId, templateData, cancellation); + + if(read <= 0) + { + throw new TemplateLookupFailedException($"Template {templateId} not found"); + } + + //Rewind the stream + templateData.Seek(0, SeekOrigin.Begin); + + //To string + string templateString = Encoding.UTF8.GetString(templateData.AsSpan()); + + //Try to parse the template and raise exception if it fails + if (!_parser.TryParse(templateString, out IFluidTemplate template, out string error)) + { + throw new TemplateLookupFailedException($"A template parse error occured: {error}"); + } + + //Create new email template + EmailTemplate et = new(template); + + //Store template in cache + _templateCache.StoreRecord(templateId, et, _cacheValidFor); + + return et; + } + + private sealed record class EmailTemplate(IFluidTemplate Template) : IEmailTemplate, ICacheable + { + ///<inheritdoc/> + public DateTime Expires { get; set; } + + ///<inheritdoc/> + public bool Equals(ICacheable? other) => ReferenceEquals(this, other); + + ///<inheritdoc/> + public void Evicted() + { } + + ///<inheritdoc/> + public IEmailMessageData RenderTemplate(object variables) + { + //Create a template model + TemplateContext ctx = new(variables); + string html = Template.Render(ctx); + return new EmailMessageData(html); + } + + private sealed record class EmailMessageData(string Rendered) : IEmailMessageData + { + public string GetHtml() => Rendered; + } + } + } +} diff --git a/lib/Emails.Transactional.Plugin/src/Templates/IEmailTemplate.cs b/lib/Emails.Transactional.Plugin/src/Templates/IEmailTemplate.cs new file mode 100644 index 0000000..3ee12cb --- /dev/null +++ b/lib/Emails.Transactional.Plugin/src/Templates/IEmailTemplate.cs @@ -0,0 +1,42 @@ +/* +* Copyright (c) 2023 Vaughn Nugent +* +* Library: VNLib +* Package: Emails.Transactional +* File: IEmailTemplate.cs +* +* IEmailTemplate.cs is part of Emails.Transactional which is part of the larger +* VNLib collection of libraries and utilities. +* +* Emails.Transactional is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published +* by the Free Software Foundation, either version 2 of the License, +* or (at your option) any later version. +* +* Emails.Transactional is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with Emails.Transactional. If not, see http://www.gnu.org/licenses/. +*/ + + +using Emails.Transactional.Mta; + +namespace Emails.Transactional.Templates +{ + /// <summary> + /// An abstraction that defines a template for an email + /// </summary> + internal interface IEmailTemplate + { + /// <summary> + /// Renders a new copy of the template with the specified variables + /// </summary> + /// <param name="variables">The variable model to compile with the template</param> + /// <returns>A new <see cref="IEmailMessageData"/> instance to capture html data from</returns> + IEmailMessageData RenderTemplate(object variables); + } +} diff --git a/lib/Emails.Transactional.Plugin/src/Templates/ITemplateStorage.cs b/lib/Emails.Transactional.Plugin/src/Templates/ITemplateStorage.cs new file mode 100644 index 0000000..e17e24a --- /dev/null +++ b/lib/Emails.Transactional.Plugin/src/Templates/ITemplateStorage.cs @@ -0,0 +1,45 @@ +/* +* Copyright (c) 2023 Vaughn Nugent +* +* Library: VNLib +* Package: Emails.Transactional +* File: ITemplateStorage.cs +* +* ITemplateStorage.cs is part of Emails.Transactional which is part of the larger +* VNLib collection of libraries and utilities. +* +* Emails.Transactional is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published +* by the Free Software Foundation, either version 2 of the License, +* or (at your option) any later version. +* +* Emails.Transactional is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with Emails.Transactional. If not, see http://www.gnu.org/licenses/. +*/ + + +using System.Threading; +using System.Threading.Tasks; + +namespace Emails.Transactional.Templates +{ + /// <summary> + /// An abstraction that defines a storage for email templates + /// </summary> + internal interface ITemplateStorage + { + /// <summary> + /// Gets an email template from its id + /// </summary> + /// <param name="templateId">The id of the template to fetch</param> + /// <param name="cancellation">A token to cancel the fetch operation</param> + /// <returns>A task that completes with the template model instance</returns> + /// <exception cref="TemplateLookupFailedException"></exception> + Task<IEmailTemplate> GetTemplateAsync(string templateId, CancellationToken cancellation); + } +} diff --git a/lib/Emails.Transactional.Plugin/src/Templates/TemplateLookupFailedException.cs b/lib/Emails.Transactional.Plugin/src/Templates/TemplateLookupFailedException.cs new file mode 100644 index 0000000..5e607f3 --- /dev/null +++ b/lib/Emails.Transactional.Plugin/src/Templates/TemplateLookupFailedException.cs @@ -0,0 +1,43 @@ +/* +* Copyright (c) 2023 Vaughn Nugent +* +* Library: VNLib +* Package: Emails.Transactional +* File: TemplateLookupFailedException.cs +* +* TemplateLookupFailedException.cs is part of Emails.Transactional which is +* part of the larger VNLib collection of libraries and utilities. +* +* Emails.Transactional is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published +* by the Free Software Foundation, either version 2 of the License, +* or (at your option) any later version. +* +* Emails.Transactional is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with Emails.Transactional. If not, see http://www.gnu.org/licenses/. +*/ + + +using System; + +namespace Emails.Transactional.Templates +{ + [Serializable] + public class TemplateLookupFailedException : Exception + { + public TemplateLookupFailedException() { } + + public TemplateLookupFailedException(string message) : base(message) { } + + public TemplateLookupFailedException(string message, Exception inner) : base(message, inner) { } + + protected TemplateLookupFailedException( + System.Runtime.Serialization.SerializationInfo info, + System.Runtime.Serialization.StreamingContext context) : base(info, context) { } + } +} |