aboutsummaryrefslogtreecommitdiff
path: root/lib/Emails.Transactional.Plugin/src/Api Endpoints
diff options
context:
space:
mode:
authorLibravatar vnugent <public@vaughnnugent.com>2023-08-28 21:54:26 -0400
committerLibravatar vnugent <public@vaughnnugent.com>2023-08-28 21:54:26 -0400
commitb153adbd86e226ad805c2edbb90e4032d386a1b0 (patch)
tree9d3e5d7f2966c66e0264001cb38c67f74d6cf707 /lib/Emails.Transactional.Plugin/src/Api Endpoints
parent964e81b81cdb430ecee8f67a68e3c616b3f339aa (diff)
Refactor overhaul, data extensions & Resend.com support
Diffstat (limited to 'lib/Emails.Transactional.Plugin/src/Api Endpoints')
-rw-r--r--lib/Emails.Transactional.Plugin/src/Api Endpoints/SendEndpoint.cs289
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;
- }
- }
-}