/* * Copyright (c) 2023 Vaughn Nugent * * Library: VNLib * Package: Emails.Transactional * File: SendEndpoint.cs * * SendEndpoint.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.Net; using System.Threading.Tasks; using VNLib.Utils.Logging; 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 VNLib.Plugins.Extensions.Data.Extensions; using Emails.Transactional.Mta; using Emails.Transactional.Templates; using Emails.Transactional.Transactions; namespace Emails.Transactional.Endpoints { [ConfigurationName("send_endpoint")] internal class SendEndpoint : O2EndpointBase { public const string OAUTH2_USER_SCOPE_PERMISSION = "email:send"; private static readonly EmailTransactionValidator Validator = new(); private readonly string FromAddress; private readonly TransactionStore Transactions; private readonly IMailTransferAgent EmailService; private readonly ITemplateStorage Templates; public SendEndpoint(PluginBase plugin, IConfigScope config) { string? path = config["path"].GetString(); FromAddress = config.GetRequiredProperty("from_address", e => e.GetString()!); InitPathAndLog(path, plugin.Log); //Load transactions Transactions = new(plugin.GetContextOptionsAsync()); //init ail transfer agent EmailService = plugin.GetOrCreateSingleton(); //Load templates Templates = plugin.GetOrCreateSingleton(); } protected override async ValueTask 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(); 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; } //Always add the template name to the model transaction.Variables!["template_name"] = transaction.TemplateId!; try { //Get the template IEmailTemplate template = await Templates.GetTemplateAsync(transaction.TemplateId!, entity.EventCancellation); //Render the template with the model IEmailMessageData message = template.RenderTemplate(transaction.Variables!); //Send the template MtaResult result = await EmailService.SendEmailAsync(transaction, message, entity.EventCancellation); //Set success flag webm.Success = result.Success; webm.SmtpStatus = result.Message; } //Handle a template lookup failure catch(TemplateLookupFailedException ex) { Log.Debug("Failed to fetch template: {err}", transaction.TemplateId, ex.Message); entity.CloseResponseError(HttpStatusCode.NotFound, ErrorType.InvalidRequest, "The requested template does not exist"); return VfReturnType.VirtualSkip; } catch (Exception ex) { Log.Error(ex); //Write an error status message to the transaction store webm.SmtpStatus = 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.TransactionId = transaction.Id; //Return the response object entity.CloseResponse(webm); return VfReturnType.VirtualSkip; } } }