diff options
Diffstat (limited to 'lib/Emails.Transactional.Plugin/src/Api Endpoints')
-rw-r--r-- | lib/Emails.Transactional.Plugin/src/Api Endpoints/SendEndpoint.cs | 289 |
1 files changed, 0 insertions, 289 deletions
diff --git a/lib/Emails.Transactional.Plugin/src/Api Endpoints/SendEndpoint.cs b/lib/Emails.Transactional.Plugin/src/Api Endpoints/SendEndpoint.cs deleted file mode 100644 index 1ce6acf..0000000 --- a/lib/Emails.Transactional.Plugin/src/Api Endpoints/SendEndpoint.cs +++ /dev/null @@ -1,289 +0,0 @@ -/* -* Copyright (c) 2023 Vaughn Nugent -* -* Library: VNLib -* Package: Transactional Emails -* File: SendEndpoint.cs -* -* SendEndpoint.cs is part of Transactional Emails which is part of the larger -* VNLib collection of libraries and utilities. -* -* Transactional Emails 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. -* -* Transactional Emails 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 Transactional Emails. If not, see http://www.gnu.org/licenses/. -*/ - -using System; -using System.IO; -using System.Net; -using System.Text; -using System.Threading.Tasks; -using System.Collections.Generic; - -using Fluid; - -using Minio; -using Minio.DataModel; -using Minio.Exceptions; - -using MimeKit.Text; - -using VNLib.Utils.Extensions; -using VNLib.Utils.Logging; -using VNLib.Utils.Memory.Caching; -using VNLib.Plugins; -using VNLib.Plugins.Essentials; -using VNLib.Plugins.Essentials.Extensions; -using VNLib.Plugins.Essentials.Oauth; -using VNLib.Plugins.Extensions.Validation; -using VNLib.Plugins.Extensions.Loading; -using VNLib.Plugins.Extensions.Loading.Sql; - -using Emails.Transactional.Transactions; -using static Emails.Transactional.TEmailEntryPoint; - -namespace Emails.Transactional.Endpoints -{ - [ConfigurationName("transaction_endpoint")] - internal class SendEndpoint : O2EndpointBase - { - public const string OAUTH2_USER_SCOPE_PERMISSION = "email:send"; - - private class FluidCache : ICacheable - { - public FluidCache(IFluidTemplate temp) - { - this.Template = temp; - } - - public IFluidTemplate Template { get; } - - DateTime ICacheable.Expires { get; set; } - - bool IEquatable<ICacheable>.Equals(ICacheable? other) - { - return ReferenceEquals(Template, (other as FluidCache)?.Template); - } - - void ICacheable.Evicted() - {} - } - - private static readonly EmailTransactionValidator Validator = new(); - private static readonly FluidParser Parser = new(); - - private readonly string FromAddress; - private readonly string? BaseObjectPath; - - private readonly TransactionStore Transactions; - - private readonly Dictionary<string, FluidCache> TemplateCache; - private readonly TimeSpan TemplateCacheTimeout; - private readonly MinioClient Client; - private readonly S3Config S3Config; - - private SmtpProvider? EmailService; - - public SendEndpoint(PluginBase plugin, IConfigScope config) - { - string? path = config["path"].GetString(); - FromAddress = config["from_address"].GetString() ?? throw new KeyNotFoundException("Missing required key 'from_address' in 'transaction_endpoint'"); - TemplateCacheTimeout = config["cache_valid_sec"].GetTimeSpan(TimeParseType.Seconds); - BaseObjectPath = config["base_object_path"].GetString(); - - InitPathAndLog(path, plugin.Log); - - //Smtp setup - { - IConfigScope smtp = plugin.GetConfig("smtp"); - Uri serverUri = new(smtp["server_address"].GetString()!); - string username = smtp["username"].GetString() ?? throw new KeyNotFoundException("Missing required key 'usename' in 'smtp' config"); - TimeSpan timeout = smtp["timeout_sec"].GetTimeSpan(TimeParseType.Seconds); - - //Load SMTP - _ = plugin.ObserveWork(async () => - { - //Get the password from the secret store - string password = await plugin.GetSecretAsync("smtp_password").ToLazy(static r => r.Result.ToString()); - - //Copy the secre to the network credential - NetworkCredential cred = new(username, password); - - //Init email service - EmailService = new(serverUri, cred, timeout); - }); - } - - //S3 minio setup - { - //Init minio s3 client - S3Config = plugin.TryGetS3Config() ?? throw new KeyNotFoundException("Missing required 's3_config' configuration variable"); - //Init minio from config - Client = new(); - - //Load the client when the secret finishes loading - _ = plugin.ObserveWork(async () => - { - string s3Secret = await plugin.GetSecretAsync("s3_secret").ToLazy(static r => r.Result.ToString()); - - Client.WithEndpoint(S3Config.ServerAddress) - .WithCredentials(S3Config.ClientId, s3Secret) - .WithSSL(S3Config.UseSsl.HasValue && S3Config.UseSsl.Value); - - //Accept optional region - if (!string.IsNullOrWhiteSpace(S3Config.Region)) - { - Client.WithRegion(S3Config.Region); - } - - //Build client - Client.Build(); - - //If the plugin is in debug mode, log requests to logger - if (plugin.IsDebug()) - { - Client.SetTraceOn(new ReqLogger(Log)); - } - }); - } - - //Load transactions - Transactions = new(plugin.GetContextOptions()); - - TemplateCache = new(20, StringComparer.OrdinalIgnoreCase); - } - - - protected override async ValueTask<VfReturnType> PostAsync(HttpEntity entity) - { - //Make sure the user has the required scope to send an email - if (!entity.Session.HasScope(OAUTH2_USER_SCOPE_PERMISSION)) - { - entity.CloseResponseError(HttpStatusCode.Forbidden, ErrorType.InvalidScope, "Your account does not have the required permissions scope"); - return VfReturnType.VirtualSkip; - } - - //Get the transaction request from the client - EmailTransaction? transaction = await entity.GetJsonFromFileAsync<EmailTransaction>(); - if(transaction == null) - { - entity.CloseResponseError(HttpStatusCode.BadRequest, ErrorType.InvalidRequest, "No request body was received"); - return VfReturnType.VirtualSkip; - } - - TransactionResult webm = new() - { - Success = false - }; - - //Set a default from name/address if not set by user - transaction.From ??= FromAddress; - transaction.FromName ??= FromAddress; - //Specify user-id - transaction.UserId = entity.Session.UserID; - //validate the form - if (!Validator.Validate(transaction, webm)) - { - //return errors - entity.CloseResponseJson(HttpStatusCode.UnprocessableEntity, webm); - return VfReturnType.VirtualSkip; - } - - IFluidTemplate fTemp; - - //See if the template is in cache - if (TemplateCache.TryGetOrEvictRecord(transaction.TemplateId!, out FluidCache? cache) == 1) - { - fTemp = cache.Template; - } - //Record was evicted or not found - else - { - string? templateData = null; - try - { - //Combine base obj path - string objPath = string.Concat(BaseObjectPath, transaction.TemplateId); - //Recover the template from the store - GetObjectArgs args = new(); - args.WithBucket(S3Config.BaseBucket) - .WithObject(objPath) - .WithCallbackStream((stream) => - { - //Read template from stream - using StreamReader reader = new(stream, Encoding.UTF8, detectEncodingFromByteOrderMarks: false, leaveOpen: true); - templateData = reader.ReadToEnd(); - }); - //get the template object - ObjectStat status = await Client.GetObjectAsync(args, entity.EventCancellation); - - } - catch(ObjectNotFoundException) - { - entity.CloseResponseError(HttpStatusCode.NotFound, ErrorType.InvalidRequest, "The requested template does not exist"); - return VfReturnType.VirtualSkip; - } - catch(MinioException me) - { - Log.Error(me); - return VfReturnType.Error; - } - - //Make sure template data was found - if (string.IsNullOrWhiteSpace(templateData)) - { - entity.CloseResponseError(HttpStatusCode.NotFound, ErrorType.InvalidRequest, "The requested template does not exist"); - return VfReturnType.VirtualSkip; - } - - //Try to parse the template - if (!Parser.TryParse(templateData, out fTemp, out string error)) - { - Log.Error(error, "Liquid template parsing error"); - return VfReturnType.Error; - } - - //Cache the new template - TemplateCache.StoreRecord(transaction.TemplateId!, new FluidCache(fTemp), TemplateCacheTimeout); - } - - //Allways add the template name to the model - transaction.Variables!["template_name"] = transaction.TemplateId!; - - //Create a template model - TemplateContext ctx = new(transaction.Variables); - //render the template - string rendered = fTemp.Render(ctx); - try - { - //Send the template - _ = await EmailService.SendAsync(transaction, rendered, TextFormat.Html, entity.EventCancellation); - //Set success flag - webm.Success = true; - } - catch (Exception ex) - { - Log.Error(ex); - //Write an error status message to the transaction store - transaction.Result = ex.Message; - } - //Write transaction to db (we need to return the transaction id) - _ = await Transactions.AddOrUpdateAsync(transaction, entity.EventCancellation); - //Store the results object - webm.SmtpStatus = transaction.Result; - webm.TransactionId = transaction.Id; - //Return the response object - entity.CloseResponse(webm); - return VfReturnType.VirtualSkip; - } - } -} |