mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-17 20:50:44 +00:00
CE-1068: changes from review: allow multiple email address entry, fixed download urls, fixed localhost to use inbucket/filesystem
This commit is contained in:
@ -57,4 +57,13 @@ public interface QStorageInterface
|
||||
//////////
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
default String getDownloadURL(StorageInput storageInput) throws QException
|
||||
{
|
||||
return (null);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -105,4 +105,16 @@ public class StorageAction
|
||||
QStorageInterface storageInterface = qBackendModuleInterface.getStorageInterface();
|
||||
storageInterface.makePublic(storageInput);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String getDownloadURL(StorageInput storageInput) throws QException
|
||||
{
|
||||
QBackendModuleInterface qBackendModuleInterface = preAction(storageInput);
|
||||
QStorageInterface storageInterface = qBackendModuleInterface.getStorageInterface();
|
||||
return (storageInterface.getDownloadURL(storageInput));
|
||||
}
|
||||
}
|
||||
|
@ -28,20 +28,25 @@ import java.util.List;
|
||||
import java.util.Properties;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.messaging.Content;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.messaging.MultiParty;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.messaging.Party;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.messaging.PartyRole;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.messaging.SendMessageInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.messaging.SendMessageOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.messaging.email.EmailContentRole;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.messaging.email.EmailPartyRole;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import jakarta.mail.Address;
|
||||
import jakarta.mail.Message;
|
||||
import jakarta.mail.Multipart;
|
||||
import jakarta.mail.Session;
|
||||
import jakarta.mail.Transport;
|
||||
import jakarta.mail.internet.AddressException;
|
||||
import jakarta.mail.internet.InternetAddress;
|
||||
import jakarta.mail.internet.MimeBodyPart;
|
||||
import jakarta.mail.internet.MimeMessage;
|
||||
import jakarta.mail.internet.MimeMultipart;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -60,10 +65,10 @@ public class SendEmailAction
|
||||
/////////////////////////////////////////
|
||||
// set up properties to make a session //
|
||||
/////////////////////////////////////////
|
||||
Properties properties = System.getProperties();
|
||||
Properties properties = new Properties();
|
||||
properties.setProperty("mail.smtp.host", messagingProvider.getSmtpServer());
|
||||
properties.setProperty("mail.smtp.port", messagingProvider.getSmtpPort());
|
||||
Session session = Session.getDefaultInstance(properties);
|
||||
Session session = Session.getInstance(properties);
|
||||
|
||||
try
|
||||
{
|
||||
@ -72,7 +77,6 @@ public class SendEmailAction
|
||||
////////////////////////////////////////////
|
||||
MimeMessage emailMessage = new MimeMessage(session);
|
||||
emailMessage.setSubject(sendMessageInput.getSubject());
|
||||
emailMessage.setText(sendMessageInput.getContentList().get(0).getBody());
|
||||
|
||||
Party to = sendMessageInput.getTo();
|
||||
if(to instanceof MultiParty toMultiParty)
|
||||
@ -100,6 +104,25 @@ public class SendEmailAction
|
||||
addSender(emailMessage, from);
|
||||
}
|
||||
|
||||
Multipart multipart = new MimeMultipart();
|
||||
for(Content content : sendMessageInput.getContentList())
|
||||
{
|
||||
if(EmailContentRole.HTML.equals(content.getContentRole()))
|
||||
{
|
||||
MimeBodyPart mimeBodyPart = new MimeBodyPart();
|
||||
mimeBodyPart.setContent(content.getBody(), "text/html; charset=utf-8");
|
||||
multipart.addBodyPart(mimeBodyPart);
|
||||
}
|
||||
else if(EmailContentRole.TEXT.equals(content.getContentRole()))
|
||||
{
|
||||
MimeBodyPart mimeBodyPart = new MimeBodyPart();
|
||||
mimeBodyPart.setContent(content.getBody(), "text/plain; charset=utf-8");
|
||||
multipart.addBodyPart(mimeBodyPart);
|
||||
}
|
||||
}
|
||||
|
||||
emailMessage.setContent(multipart);
|
||||
|
||||
/////////////
|
||||
// send it //
|
||||
/////////////
|
||||
@ -132,7 +155,6 @@ public class SendEmailAction
|
||||
else
|
||||
{
|
||||
List<Address> replyToList = Arrays.asList(replyTo);
|
||||
replyToList.add(internetAddress);
|
||||
emailMessage.setReplyTo(replyToList.toArray(new Address[0]));
|
||||
}
|
||||
}
|
||||
|
@ -29,6 +29,9 @@ import java.time.LocalDate;
|
||||
import java.time.LocalTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import com.kingsrook.qqq.backend.core.actions.messaging.SendMessageAction;
|
||||
@ -39,6 +42,7 @@ import com.kingsrook.qqq.backend.core.actions.tables.StorageAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.UpdateAction;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QUserFacingException;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.messaging.Content;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.messaging.MultiParty;
|
||||
@ -64,6 +68,7 @@ import com.kingsrook.qqq.backend.core.model.savedreports.SavedReport;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ExceptionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import org.apache.commons.validator.EmailValidator;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
|
||||
|
||||
@ -89,6 +94,9 @@ public class RenderSavedReportExecuteStep implements BackendStep
|
||||
////////////////////////////////
|
||||
// read inputs, set up params //
|
||||
////////////////////////////////
|
||||
String sesProviderName = runBackendStepInput.getValueString(RenderSavedReportMetaDataProducer.SES_PROVIDER_NAME);
|
||||
String fromEmailAddress = runBackendStepInput.getValueString(RenderSavedReportMetaDataProducer.FROM_EMAIL_ADDRESS);
|
||||
String replyToEmailAddress = runBackendStepInput.getValueString(RenderSavedReportMetaDataProducer.REPLY_TO_EMAIL_ADDRESS);
|
||||
String storageTableName = runBackendStepInput.getValueString(RenderSavedReportMetaDataProducer.FIELD_NAME_STORAGE_TABLE_NAME);
|
||||
ReportFormat reportFormat = ReportFormat.fromString(runBackendStepInput.getValueString(RenderSavedReportMetaDataProducer.FIELD_NAME_REPORT_FORMAT));
|
||||
String sendToEmailAddress = runBackendStepInput.getValueString(RenderSavedReportMetaDataProducer.FIELD_NAME_EMAIL_ADDRESS);
|
||||
@ -96,6 +104,15 @@ public class RenderSavedReportExecuteStep implements BackendStep
|
||||
String downloadFileBaseName = getDownloadFileBaseName(runBackendStepInput, savedReport);
|
||||
String storageReference = LocalDate.now() + "/" + LocalTime.now().toString().replaceAll(":", "").replaceFirst("\\..*", "") + "/" + UUID.randomUUID() + "/" + downloadFileBaseName + "." + reportFormat.getExtension();
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if sending an email (or emails), validate the addresses before doing anything so user gets error and can fix //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
List<String> toEmailAddressList = new ArrayList<>();
|
||||
if(sendToEmailAddress != null)
|
||||
{
|
||||
toEmailAddressList = validateEmailAddresses(sendToEmailAddress);
|
||||
}
|
||||
|
||||
StorageAction storageAction = new StorageAction();
|
||||
StorageInput storageInput = new StorageInput(storageTableName).withReference(storageReference);
|
||||
OutputStream outputStream = storageAction.createOutputStream(storageInput);
|
||||
@ -133,7 +150,6 @@ public class RenderSavedReportExecuteStep implements BackendStep
|
||||
reportInput.setInputValues(values);
|
||||
|
||||
ReportOutput reportOutput = new GenerateReportAction().execute(reportInput);
|
||||
storageAction.makePublic(storageInput);
|
||||
|
||||
///////////////////////////////////
|
||||
// update record to show success //
|
||||
@ -152,20 +168,33 @@ public class RenderSavedReportExecuteStep implements BackendStep
|
||||
runBackendStepOutput.addValue("storageReference", storageReference);
|
||||
LOG.info("Completed rendering a report", logPair("savedReportId", savedReport.getId()), logPair("tableName", savedReport.getTableName()), logPair("storageReference", storageReference), logPair("rowCount", reportOutput.getTotalRecordCount()));
|
||||
|
||||
if(sendToEmailAddress != null && CollectionUtils.nullSafeHasContents(QContext.getQInstance().getMessagingProviders()))
|
||||
if(!toEmailAddressList.isEmpty() && CollectionUtils.nullSafeHasContents(QContext.getQInstance().getMessagingProviders()))
|
||||
{
|
||||
String s3Url = "https://bucket-ctlive-reports-dev.s3.us-east-2.amazonaws.com/saved-reports/" + storageReference; // TODO: derp
|
||||
///////////////////////////////////////////////////////////
|
||||
// since sending email, make s3 file publicly accessible //
|
||||
///////////////////////////////////////////////////////////
|
||||
storageAction.makePublic(storageInput);
|
||||
|
||||
////////////////////////////////////////////////
|
||||
// add multiparty in case multiple recipients //
|
||||
////////////////////////////////////////////////
|
||||
MultiParty recipients = new MultiParty();
|
||||
for(String toAddress : toEmailAddressList)
|
||||
{
|
||||
recipients.addParty(new Party().withAddress(toAddress).withRole(EmailPartyRole.TO));
|
||||
}
|
||||
|
||||
String downloadURL = storageAction.getDownloadURL(storageInput);
|
||||
new SendMessageAction().execute(new SendMessageInput()
|
||||
.withMessagingProviderName("defaultMessagingProvider") // TODO: derp
|
||||
.withTo(new Party().withAddress(sendToEmailAddress).withRole(EmailPartyRole.TO))
|
||||
.withMessagingProviderName(sesProviderName)
|
||||
.withTo(recipients)
|
||||
.withFrom(new MultiParty()
|
||||
.withParty(new Party().withAddress("reports@coldtrack-dev.com").withRole(EmailPartyRole.FROM)) // TODO: derp
|
||||
.withParty(new Party().withAddress("noreply@coldtrack-dev.com").withRole(EmailPartyRole.REPLY_TO)) // TODO: derp
|
||||
.withParty(new Party().withAddress(fromEmailAddress).withRole(EmailPartyRole.FROM))
|
||||
.withParty(new Party().withAddress(replyToEmailAddress).withRole(EmailPartyRole.REPLY_TO))
|
||||
)
|
||||
.withSubject(downloadFileBaseName)
|
||||
.withContent(new Content().withContentRole(EmailContentRole.TEXT).withBody("To download your report, open this URL in your browser: " + s3Url))
|
||||
.withContent(new Content().withContentRole(EmailContentRole.HTML).withBody("Link: <a target=\"_blank\" href=\"" + s3Url + "\">" + downloadFileName + "</a>"))
|
||||
.withContent(new Content().withContentRole(EmailContentRole.TEXT).withBody("To download your report, open this URL in your browser: " + downloadURL))
|
||||
.withContent(new Content().withContentRole(EmailContentRole.HTML).withBody("Link: <a target=\"_blank\" href=\"" + downloadURL + "\" download>" + downloadFileName + "</a>"))
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -188,6 +217,42 @@ public class RenderSavedReportExecuteStep implements BackendStep
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private List<String> validateEmailAddresses(String sendToEmailAddress) throws QUserFacingException
|
||||
{
|
||||
////////////////////////////////////////////////////////////////
|
||||
// split email address string on spaces, comma, and semicolon //
|
||||
////////////////////////////////////////////////////////////////
|
||||
List<String> toEmailAddressList = Arrays.asList(sendToEmailAddress.split("[\\s,;]+"));
|
||||
|
||||
//////////////////////////////////////////////////////
|
||||
// check each address keeping track of any bad ones //
|
||||
//////////////////////////////////////////////////////
|
||||
List<String> invalidEmails = new ArrayList<>();
|
||||
EmailValidator validator = EmailValidator.getInstance();
|
||||
for(String emailAddress : toEmailAddressList)
|
||||
{
|
||||
if(!validator.isValid(emailAddress))
|
||||
{
|
||||
invalidEmails.add(emailAddress);
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////
|
||||
// if bad one found, throw exception //
|
||||
///////////////////////////////////////
|
||||
if(!invalidEmails.isEmpty())
|
||||
{
|
||||
throw (new QUserFacingException("The following email addresses were invalid: " + StringUtils.join(",", invalidEmails)));
|
||||
}
|
||||
|
||||
return (toEmailAddressList);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
|
@ -47,6 +47,9 @@ public class RenderSavedReportMetaDataProducer implements MetaDataProducerInterf
|
||||
{
|
||||
public static final String NAME = "renderSavedReport";
|
||||
|
||||
public static final String SES_PROVIDER_NAME = "sesProviderName";
|
||||
public static final String FROM_EMAIL_ADDRESS = "fromEmailAddress";
|
||||
public static final String REPLY_TO_EMAIL_ADDRESS = "replyToEmailAddress";
|
||||
public static final String FIELD_NAME_STORAGE_TABLE_NAME = "storageTableName";
|
||||
public static final String FIELD_NAME_REPORT_FORMAT = "reportFormat";
|
||||
public static final String FIELD_NAME_EMAIL_ADDRESS = "reportDestinationEmailAddress";
|
||||
@ -67,6 +70,9 @@ public class RenderSavedReportMetaDataProducer implements MetaDataProducerInterf
|
||||
.addStep(new QBackendStepMetaData()
|
||||
.withName("pre")
|
||||
.withInputData(new QFunctionInputMetaData()
|
||||
.withField(new QFieldMetaData(SES_PROVIDER_NAME, QFieldType.STRING))
|
||||
.withField(new QFieldMetaData(FROM_EMAIL_ADDRESS, QFieldType.STRING))
|
||||
.withField(new QFieldMetaData(REPLY_TO_EMAIL_ADDRESS, QFieldType.STRING))
|
||||
.withField(new QFieldMetaData(FIELD_NAME_STORAGE_TABLE_NAME, QFieldType.STRING))
|
||||
.withRecordListMetaData(new QRecordListMetaData().withTableName(SavedReport.TABLE_NAME)))
|
||||
.withCode(new QCodeReference(RenderSavedReportPreStep.class)))
|
||||
|
@ -57,8 +57,7 @@ class EmailMessagingProviderTest extends BaseTest
|
||||
.withParty(new Party().withAddress("james.maes@kingsrook.com").withLabel("Mames Maes").withRole(EmailPartyRole.CC))
|
||||
.withParty(new Party().withAddress("tyler.samples@kingsrook.com").withLabel("Tylers Ample").withRole(EmailPartyRole.BCC))
|
||||
)
|
||||
// .withFrom(new Party().withAddress("darin.kelkhoff@gmail.com").withLabel("Darin Kelkhoff"))
|
||||
.withFrom(new Party().withAddress("tim.chamberlain@kingsrook.com").withLabel("Tim Chamberlain"))
|
||||
.withFrom(new Party().withAddress("darin.kelkhoff@gmail.com").withLabel("Darin Kelkhoff"))
|
||||
.withSubject("This is another qqq test message.")
|
||||
.withContent(new Content().withContentRole(EmailContentRole.TEXT).withBody("This is a text body"))
|
||||
.withContent(new Content().withContentRole(EmailContentRole.HTML).withBody("This <u>is</u> an <b>HTML</b> body!"))
|
||||
|
@ -51,7 +51,7 @@ public class FilesystemStorageAction extends AbstractFilesystemAction implements
|
||||
try
|
||||
{
|
||||
String fullPath = getFullPath(storageInput);
|
||||
File file = new File(fullPath);
|
||||
File file = new File(fullPath);
|
||||
if(!file.getParentFile().exists())
|
||||
{
|
||||
if(!file.getParentFile().mkdirs())
|
||||
@ -100,4 +100,15 @@ public class FilesystemStorageAction extends AbstractFilesystemAction implements
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public String getDownloadURL(StorageInput storageInput) throws QException
|
||||
{
|
||||
return ("file://" + getFullPath(storageInput));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -112,7 +112,30 @@ public class S3StorageAction extends AbstractS3Action implements QStorageInterfa
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
throw (new QException("Exception getting s3 input stream for file", e));
|
||||
throw (new QException("Exception getting s3 input stream for file.", e));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public String getDownloadURL(StorageInput storageInput) throws QException
|
||||
{
|
||||
try
|
||||
{
|
||||
S3BackendMetaData backend = (S3BackendMetaData) storageInput.getBackend();
|
||||
preAction(backend);
|
||||
|
||||
AmazonS3 amazonS3 = getS3Utils().getAmazonS3();
|
||||
String fullPath = getFullPath(storageInput);
|
||||
return (amazonS3.getUrl(backend.getBucketName(), fullPath).toString());
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
throw (new QException("Exception getting the S3 download URL.", e));
|
||||
}
|
||||
}
|
||||
|
||||
@ -135,7 +158,7 @@ public class S3StorageAction extends AbstractS3Action implements QStorageInterfa
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
throw (new QException("Exception making s3 file publicly available", e));
|
||||
throw (new QException("Exception making s3 file publicly available.", e));
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user