aboutsummaryrefslogtreecommitdiff
path: root/lib/Emails.Transactional.Extensions/src
diff options
context:
space:
mode:
Diffstat (limited to 'lib/Emails.Transactional.Extensions/src')
-rw-r--r--lib/Emails.Transactional.Extensions/src/EmailSystemConfig.cs54
-rw-r--r--lib/Emails.Transactional.Extensions/src/TEmailConfig.cs204
-rw-r--r--lib/Emails.Transactional.Extensions/src/TransactionalEmailExtensions.cs150
3 files changed, 213 insertions, 195 deletions
diff --git a/lib/Emails.Transactional.Extensions/src/EmailSystemConfig.cs b/lib/Emails.Transactional.Extensions/src/EmailSystemConfig.cs
deleted file mode 100644
index fdce935..0000000
--- a/lib/Emails.Transactional.Extensions/src/EmailSystemConfig.cs
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
-* Copyright (c) 2022 Vaughn Nugent
-*
-* Library: VNLib
-* Package: VNLib.Plugins.Extentions.TransactionalEmail
-* File: EmailSystemConfig.cs
-*
-* EmailSystemConfig.cs is part of VNLib.Plugins.Extentions.TransactionalEmail which is part of the larger
-* VNLib collection of libraries and utilities.
-*
-* VNLib.Plugins.Extentions.TransactionalEmail is free software: you can redistribute it and/or modify
-* it under the terms of the GNU Affero General Public License as
-* published by the Free Software Foundation, either version 3 of the
-* License, or (at your option) any later version.
-*
-* VNLib.Plugins.Extentions.TransactionalEmail 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 Affero General Public License for more details.
-*
-* You should have received a copy of the GNU Affero General Public License
-* along with this program. If not, see https://www.gnu.org/licenses/.
-*/
-
-using RestSharp;
-
-using Emails.Transactional.Client;
-
-using VNLib.Net.Rest.Client;
-
-namespace VNLib.Plugins.Extentions.TransactionalEmail
-{
- /// <summary>
- /// An extended <see cref="TransactionalEmailConfig"/> configuration
- /// object that contains a <see cref="Net.Rest.Client.RestClientPool"/> pool for making
- /// transactions
- /// </summary>
- internal sealed class EmailSystemConfig : TransactionalEmailConfig
- {
- /// <summary>
- /// A shared <see cref="Net.Rest.Client.RestClientPool"/> for renting configuraed
- /// <see cref="RestClient"/>
- /// </summary>
- public RestClientPool RestClientPool { get; init; }
- /// <summary>
- /// A global from email address name
- /// </summary>
- public string EmailFromName { get; init; }
- /// <summary>
- /// A global from email address
- /// </summary>
- public string EmailFromAddress { get; init; }
- }
-} \ No newline at end of file
diff --git a/lib/Emails.Transactional.Extensions/src/TEmailConfig.cs b/lib/Emails.Transactional.Extensions/src/TEmailConfig.cs
new file mode 100644
index 0000000..7905982
--- /dev/null
+++ b/lib/Emails.Transactional.Extensions/src/TEmailConfig.cs
@@ -0,0 +1,204 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: Emails.Transactional.Client.Extensions
+* File: TEmailConfig.cs
+*
+* TEmailConfig.cs is part of Emails.Transactional.Client.Extensions which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* Emails.Transactional.Client.Extensions is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* Emails.Transactional.Client.Extensions 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 Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System.Text;
+using System.Text.Json.Serialization;
+
+using RestSharp;
+
+using VNLib.Utils.Logging;
+using VNLib.Plugins;
+using VNLib.Net.Rest.Client;
+using VNLib.Net.Rest.Client.OAuth2;
+using VNLib.Plugins.Extensions.Loading;
+
+namespace Emails.Transactional.Client.Extensions
+{
+ /// <summary>
+ /// Provides transactional emails based on templates and variable complilation. Should be loaded as a singleton for a plugin
+ /// </summary>
+ [ConfigurationName(EMAIL_CONFIG_KEY)]
+ public class TEmailConfig : TransactionalEmailConfig, IAsyncConfigurable, IDisposable
+ {
+ const string DEFAULT_USER_AGENT = "VN Transactional Email System";
+
+ public const string EMAIL_CONFIG_KEY = "emails";
+ public const string REQUIRED_EMAIL_TEMPALTE_CONFIG_KEY = "required_email_templates";
+
+ public const int DEFAULT_MAX_CLIENTS = 5;
+ public const int DEFAULT_CLIENT_TIMEOUT_MS = 10000;
+
+ private bool disposedValue;
+
+ private readonly OAuth2Authenticator _authenticator;
+
+ /// <summary>
+ /// A shared <see cref="VNLib.Net.Rest.Client.RestClientPool"/> for renting configuraed
+ /// <see cref="RestClient"/>
+ /// </summary>
+ public RestClientPool RestClientPool { get; }
+ /// <summary>
+ /// A global from email address name
+ /// </summary>
+ public string EmailFromName { get; }
+ /// <summary>
+ /// A global from email address
+ /// </summary>
+ public string EmailFromAddress { get; }
+
+ private SecretResult? _clientId;
+ private SecretResult? _clientSecret;
+
+
+ /// <summary>
+ /// Intialzies the singleton using the <see cref="LoadingExtensions"/>
+ /// loading system
+ /// </summary>
+ /// <param name="plugin">The plugin loading the instance</param>
+ /// <param name="config">The configuration scope for the instance</param>
+ public TEmailConfig(PluginBase plugin, IConfigScope config)
+ {
+ //The current config scope should be the required data, so we can deserialze it
+ JsonConfig mailConfig = config.DeserialzeAndValidate<JsonConfig>();
+
+ //Get required templates
+ Dictionary<string, string> templates = plugin.GetConfig(REQUIRED_EMAIL_TEMPALTE_CONFIG_KEY).Deserialze<Dictionary<string, string>>();
+
+ EmailFromName = mailConfig.EmailFromName!;
+ EmailFromAddress = mailConfig.EmailFromAddress!;
+
+ if (plugin.IsDebug())
+ {
+ plugin.Log.Debug("Required email templates {t}", templates);
+ }
+
+ //Options for auth token endpoint
+ RestClientOptions clientOptions = new()
+ {
+ AllowMultipleDefaultParametersWithSameName = true,
+ //Server supports compression
+ AutomaticDecompression = System.Net.DecompressionMethods.All,
+ PreAuthenticate = false,
+ Encoding = Encoding.UTF8,
+ MaxTimeout = mailConfig.TimeoutMs,
+ UserAgent = mailConfig.UserAgent,
+ //Server should not redirect
+ FollowRedirects = false
+ };
+
+ //Init Oauth authenticator
+ _authenticator = new(clientOptions, OAuthCredentialGet, mailConfig.TokenServerUri.ToString());
+
+ //Create client pool
+ RestClientPool = new(mailConfig.MaxClients, clientOptions, authenticator: _authenticator);
+
+ //Init templates and service url
+ WithTemplates(templates)
+ .WithUrl(mailConfig.ServiceUri);
+ }
+
+ private Credential OAuthCredentialGet()
+ {
+ _ = _clientId ?? throw new InvalidOperationException("Oauth2 client id was not loaded");
+ _ = _clientSecret ?? throw new InvalidOperationException("Oauth2 client secret was not loaded");
+
+ //Create new credential
+ return Credential.Create(_clientId.Result, _clientSecret.Result);
+ }
+
+ async Task IAsyncConfigurable.ConfigureServiceAsync(PluginBase plugin)
+ {
+ //Load oauth secrets
+ _clientId = await plugin.TryGetSecretAsync("email_client_id");
+ _clientSecret = await plugin.TryGetSecretAsync("email_client_secret");
+ }
+
+ ///<inheritdoc/>
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!disposedValue)
+ {
+ if (disposing)
+ {
+ _clientId?.Dispose();
+ _clientSecret?.Dispose();
+
+ _authenticator.Dispose();
+ RestClientPool.Dispose();
+ }
+ disposedValue = true;
+ }
+ }
+
+ void IDisposable.Dispose()
+ {
+ // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
+ Dispose(disposing: true);
+ GC.SuppressFinalize(this);
+ }
+
+ private sealed class JsonConfig : IOnConfigValidation
+ {
+ [JsonPropertyName("from_name")]
+ public string? EmailFromName { get; set; }
+ [JsonPropertyName("from_address")]
+ public string? EmailFromAddress { get; set; }
+ [JsonPropertyName("email_url")]
+ public string? ServiceUrl { get; set; }
+ [JsonPropertyName("auth_url")]
+ public string? AuthUrl { get; set; }
+ [JsonPropertyName("user_agent")]
+ public string UserAgent { get; set; } = DEFAULT_USER_AGENT;
+ [JsonPropertyName("timeout_ms")]
+ public int TimeoutMs { get; set; } = DEFAULT_CLIENT_TIMEOUT_MS;
+ [JsonPropertyName("max_clients")]
+ public int MaxClients { get; set; } = DEFAULT_MAX_CLIENTS;
+
+
+ [JsonIgnore]
+ public Uri ServiceUri => new(ServiceUrl!);
+
+ [JsonIgnore]
+ public Uri TokenServerUri => new(AuthUrl!);
+
+ void IOnConfigValidation.Validate()
+ {
+ _ = EmailFromName ?? throw new KeyNotFoundException("Missing required mail configuration key 'from_name'");
+ _ = EmailFromAddress ?? throw new KeyNotFoundException("Missing required mail configuration key 'from_address'");
+ _ = ServiceUri ?? throw new KeyNotFoundException("Missing required mail configuration key 'email_url'");
+ _ = AuthUrl ?? throw new KeyNotFoundException("Missing required mail configuration key 'auth_url'");
+
+ if (MaxClients < 1)
+ {
+ throw new ArgumentOutOfRangeException("max_client", "Max clients config variable must be a non-zero positive integer");
+ }
+
+ if (TimeoutMs < 100)
+ {
+ throw new ArgumentOutOfRangeException("timeout_ms", "Client timeout should be a value greater than 100ms");
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/lib/Emails.Transactional.Extensions/src/TransactionalEmailExtensions.cs b/lib/Emails.Transactional.Extensions/src/TransactionalEmailExtensions.cs
index 2345f7e..e6cf3ed 100644
--- a/lib/Emails.Transactional.Extensions/src/TransactionalEmailExtensions.cs
+++ b/lib/Emails.Transactional.Extensions/src/TransactionalEmailExtensions.cs
@@ -1,19 +1,19 @@
/*
-* Copyright (c) 2022 Vaughn Nugent
+* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
-* Package: VNLib.Plugins.Extentions.TransactionalEmail
+* Package: Emails.Transactional.Client.Extensions
* File: TransactionalEmailExtensions.cs
*
-* TransactionalEmailExtensions.cs is part of VNLib.Plugins.Extentions.TransactionalEmail which is part of the larger
-* VNLib collection of libraries and utilities.
+* TransactionalEmailExtensions.cs is part of Emails.Transactional.Client.Extensions which
+* is part of the larger VNLib collection of libraries and utilities.
*
-* VNLib.Plugins.Extentions.TransactionalEmail is free software: you can redistribute it and/or modify
+* Emails.Transactional.Client.Extensions is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
-* VNLib.Plugins.Extentions.TransactionalEmail is distributed in the hope that it will be useful,
+* Emails.Transactional.Client.Extensions 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 Affero General Public License for more details.
@@ -22,41 +22,19 @@
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
-using System.Text;
-using System.Text.Json;
-using RestSharp;
-
-using Emails.Transactional.Client;
-
-using VNLib.Utils.Logging;
-using VNLib.Utils.Extensions;
using VNLib.Net.Rest.Client;
-using VNLib.Net.Rest.Client.OAuth2;
-using VNLib.Plugins.Extensions.Loading;
-namespace VNLib.Plugins.Extentions.TransactionalEmail
+namespace Emails.Transactional.Client.Extensions
{
+
/// <summary>
/// Contains extension methods for implementing templated
/// transactional emails
/// </summary>
public static class TransactionalEmailExtensions
{
- public const string EMAIL_CONFIG_KEY = "emails";
- public const string REQUIRED_EMAIL_TEMPALTE_CONFIG_KEY = "required_email_templates";
-
- public const uint DEFAULT_MAX_CLIENTS = 5;
- public const uint DEFAULT_CLIENT_TIMEOUT_MS = 10000;
-
- /// <summary>
- /// Gets (or loads) the ambient <see cref="TransactionalEmailConfig"/> configuration object
- /// to send transactional emails against
- /// </summary>
- /// <param name="pbase"></param>
- /// <returns>The <see cref="TransactionalEmailConfig"/> from the current plugins config</returns>
- public static TransactionalEmailConfig GetEmailConfig(this PluginBase pbase) => LoadingExtensions.GetOrCreateSingleton(pbase, LoadConfig);
/// <summary>
/// Sends an <see cref="EmailTransactionRequest"/> on the current configuration resource pool
@@ -67,119 +45,9 @@ namespace VNLib.Plugins.Extentions.TransactionalEmail
public static async Task<TransactionResult> SendEmailAsync(this TransactionalEmailConfig config, EmailTransactionRequest request)
{
//Get a new client contract from the configuration's pool assuming its a EmailSystemConfig class
- using ClientContract client = ((EmailSystemConfig)config).RestClientPool.Lease();
+ using ClientContract client = ((TEmailConfig)config).RestClientPool.Lease();
//Send the email and await the result before releasing the client
return await client.Resource.SendEmailAsync(request);
}
-
- private static TransactionalEmailConfig LoadConfig(PluginBase pbase)
- {
- //Get the required email config
- IReadOnlyDictionary<string, JsonElement> conf = pbase.GetConfig(EMAIL_CONFIG_KEY);
-
- string emailFromName = conf["from_name"].GetString() ?? throw new KeyNotFoundException("Missing required configuration key 'from_name'");
- string emailFromAddress = conf["from_address"].GetString() ?? throw new KeyNotFoundException("Missing required configuration key 'from_address'");
- Uri baseServerPath = new(conf["base_url"].GetString()!, UriKind.RelativeOrAbsolute);
-
- //Get the token server url or use the base path if no set
- Uri tokenServerBase = conf.TryGetValue("token_server_url", out JsonElement tksEl) && tksEl.GetString() != null ?
- new(tksEl.GetString()!, UriKind.RelativeOrAbsolute)
- : baseServerPath;
-
- //Get the transaction endpoint path, should be a realative path
- Uri transactionEndpoint = new(conf["transaction_path"].GetString()!, UriKind.Relative);
-
- //Load credentials
- string authEndpoint = conf["token_path"].GetString() ?? throw new KeyNotFoundException("Missing required configuration key 'token_path'");
-
- //Optional user-agent
- string? userAgent = conf.GetPropString("user_agent");
-
- //Get optional timeout ms
- int timeoutMs = (int)(conf.TryGetValue("request_timeout_ms", out JsonElement timeoutEl) ? timeoutEl.GetUInt32() : DEFAULT_CLIENT_TIMEOUT_MS);
-
- //Get maximum client limit
- int maxClients = (int)(conf.TryGetValue("max_clients", out JsonElement mxcEl) ? mxcEl.GetUInt32() : DEFAULT_MAX_CLIENTS);
-
- //Load all templates from the plugin config
- Dictionary<string, string> templates = pbase.PluginConfig.GetProperty(REQUIRED_EMAIL_TEMPALTE_CONFIG_KEY)
- .EnumerateObject()
- .ToDictionary(static jp => jp.Name, static jp => jp.Value.GetString()!);
-
- pbase.Log.Verbose("Required email templates {t}", templates);
-
- //Load oauth secrets from vault
- Task<SecretResult?> oauth2ClientID = pbase.TryGetSecretAsync("email_client_id");
- Task<SecretResult?> oauth2Password = pbase.TryGetSecretAsync("email_client_secret");
-
- //Lazy cred loaded, tasks should be loaded before this method will ever get called
- Credential lazyCredentialGet()
- {
- //Load the results
- SecretResult cliendId = oauth2ClientID.GetAwaiter().GetResult() ?? throw new KeyNotFoundException("Missing required oauth2 client id");
- SecretResult password = oauth2Password.GetAwaiter().GetResult() ?? throw new KeyNotFoundException("Missing required oauth2 client secret");
-
- //Creat credential
- return Credential.Create(cliendId.Result, password.Result);
- }
-
- //Init client creation options
- RestClientOptions poolOptions = new(baseServerPath)
- {
- AllowMultipleDefaultParametersWithSameName = true,
- AutomaticDecompression = System.Net.DecompressionMethods.All,
- PreAuthenticate = true,
- Encoding = Encoding.UTF8,
- MaxTimeout = timeoutMs,
- UserAgent = userAgent,
- //Server should not redirect
- FollowRedirects = false,
- };
-
- //Options for auth token endpoint
- RestClientOptions oAuth2ClientOptions = new(tokenServerBase)
- {
- AllowMultipleDefaultParametersWithSameName = true,
- //Server supports compression
- AutomaticDecompression = System.Net.DecompressionMethods.All,
- PreAuthenticate = false,
- Encoding = Encoding.UTF8,
- MaxTimeout = timeoutMs,
- UserAgent = userAgent,
- //Server should not redirect
- FollowRedirects = false
- };
-
- //Init Oauth authenticator
- OAuth2Authenticator authenticator = new(oAuth2ClientOptions, lazyCredentialGet, authEndpoint);
-
- //Create client pool
- RestClientPool pool = new(maxClients, poolOptions, authenticator: authenticator);
-
- void Cleanup()
- {
- authenticator.Dispose();
- pool.Dispose();
- oauth2ClientID.Dispose();
- oauth2Password.Dispose();
- }
-
- //register password cleanup
- _ = pbase.RegisterForUnload(Cleanup);
-
- //Create config
- EmailSystemConfig config = new ()
- {
- EmailFromName = emailFromName,
- EmailFromAddress = emailFromAddress,
- RestClientPool = pool,
- };
-
- //Store templates and set service url
- config.WithTemplates(templates)
- .WithUrl(transactionEndpoint);
-
- return config;
- }
}
} \ No newline at end of file