Skip to content

Commit

Permalink
Jobs: templating for email action (#1326)
Browse files Browse the repository at this point in the history
* email action wip

* fix email templating

* remove unnecessary file

* add optional auth field

* rename field to bodyTemplateFile

* fix lint errors
  • Loading branch information
despadam authored Sep 9, 2024
1 parent d179e4b commit 6b56931
Show file tree
Hide file tree
Showing 4 changed files with 165 additions and 21 deletions.
85 changes: 85 additions & 0 deletions src/common/email-templates/job-template-simplified.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
<html>
<head>
<style type="text/css">
body {
font-family: Helvetica, sans-serif;
background: #f0f0f0;
margin: 1em;
}

.container {
margin: auto;
max-width: 50em;
background: white;
padding: 2em;
box-shadow: 0px 3px 1px -2px rgba(0, 0, 0, 0.2),
0px 2px 2px 0px rgba(0, 0, 0, 0.14),
0px 1px 5px 0px rgba(0, 0, 0, 0.12);
}
.job-id-container {
padding: 1em 0;
}
.footer {
font-size: 0.8em;
color: #666;
font-style: italic;
padding-top: 2em;
}
.link {
text-decoration: none;
color: #de9300;
}
.link:hover {
color: #fea901;
}

table {
border-collapse: collapse;
border-spacing: 10px;

tr {
border-top: 1px solid #eee;
}
td {
padding: 0.5em;
}

.key{
font-weight: bold;
color: #666;
vertical-align: top;
margin-top: 0;
}
}
</style>
</head>
<body>
<div class="container">
<div>
Your {{type}} job has been submitted and will be processed as soon as possible.<br>
You will be notified by email as soon as the job is completed.
</div>
<div class="job-id-container">
<b>Job id:</b> {{id}}
</div>
{{#if jobParams.datasetIds}}
<p><b>Job will be perfomed on the following dataset(s):</b></p>
<table>
{{#each jobParams.datasetIds}}
<tr style="background-color: lightblue;">
<td class="key">
{{this}}
</td>
</tr>
{{/each}}
</table>
{{/if}}

<div class="footer">
This email was automatically generated by
<a class="link" href="https://scicatproject.github.io/">SciCat</a>.
Please do not reply.
</div>
</div>
</body>
</html>
86 changes: 65 additions & 21 deletions src/jobs/actions/emailaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,27 @@
* This is intended as an example of the JobAction interface
*
*/
import { readFileSync } from "fs";
import { compile, TemplateDelegate } from "handlebars";
import { createTransport, Transporter } from "nodemailer";
import { Logger, NotFoundException } from "@nestjs/common";
import { JobAction } from "../config/jobconfig";
import { JobClass } from "../schemas/job.schema";
import { createTransport, Transporter } from "nodemailer";
import { compile, TemplateDelegate } from "handlebars";
import configuration from "src/config/configuration";

// Handlebar options for JobClass templates
const jobTemplateOptions = {
allowedProtoProperties: {
id: true,
type: true,
statusCode: true,
statusMessage: true,
createdBy: true,
jobParams: true,
contactEmail: true,
},
allowProtoPropertiesByDefault: false, // limit accessible fields for security
};

type MailOptions = {
to: string;
Expand All @@ -16,17 +32,23 @@ type MailOptions = {
text?: string;
};

type Auth = {
user: string;
password: string;
};

/**
* Send an email following a job
*/
export class EmailJobAction<T> implements JobAction<T> {
public static readonly actionType = "email";

private mailService: Transporter;
private toTemplate: TemplateDelegate<JobClass>;
private from: string;
private auth: Auth | object = {};
private subjectTemplate: TemplateDelegate<JobClass>;
private bodyTemplate?: TemplateDelegate<JobClass>;

public static readonly actionType = "email";
private bodyTemplate: TemplateDelegate<JobClass>;

getActionType(): string {
return EmailJobAction.actionType;
Expand All @@ -38,31 +60,52 @@ export class EmailJobAction<T> implements JobAction<T> {
"EmailJobAction",
);

if (!data["mailer"]) {
throw new NotFoundException("Param 'mailer' is undefined");
if (data["auth"]) {
// check optional auth field
function CheckAuthDefinition(obj: object): obj is Auth {
return (
Object.keys(obj).length == 2 && "user" in obj && "password" in obj
);
}

if (!CheckAuthDefinition(data["auth"])) {
throw new NotFoundException(
"Param 'auth' should contain fields 'user' and 'password' only.",
);
}
this.auth = data["auth"] as Auth;
}
if (!data["to"]) {
throw new NotFoundException("Param 'to' is undefined");
}
if (!data["from"]) {
throw new NotFoundException("Param 'from' is undefined");
}
if (typeof data["from"] !== "string") {
throw new TypeError("from should be a string");
}
if (!data["subject"]) {
throw new NotFoundException("Param 'subject' is undefined");
}
if (!data["body"]) {
throw new NotFoundException("Param 'body' is undefined");
if (!data["bodyTemplateFile"]) {
throw new NotFoundException("Param 'bodyTemplateFile' is undefined");
}
Logger.log("EmailJobAction parameters are valid.", "EmailJobAction");

this.mailService = createTransport(data["mailer"]);
// const mailerConfig = configuration().smtp;
// this.mailService = createTransport({
// host: mailerConfig.host,
// port: mailerConfig.port,
// secure: mailerConfig.secure,
// auth: this.auth
// } as any);

this.from = data["from"] as string;
this.toTemplate = compile(data["to"]);
this.from = data["from"];
this.subjectTemplate = compile(data["subject"]);
this.bodyTemplate = compile(data["body"]);

const templateFile = readFileSync(
data["bodyTemplateFile"] as string,
"utf8",
);
this.bodyTemplate = compile(templateFile);
}

async performJob(job: JobClass) {
Expand All @@ -73,13 +116,14 @@ export class EmailJobAction<T> implements JobAction<T> {

// Fill templates
const mail: MailOptions = {
to: this.toTemplate(job),
to: this.toTemplate(job, jobTemplateOptions),
from: this.from,
subject: this.subjectTemplate(job),
subject: this.subjectTemplate(job, jobTemplateOptions),
};
if (this.bodyTemplate) {
mail.text = this.bodyTemplate(job);
}
await this.mailService.sendMail(mail);
mail.text = this.bodyTemplate(job, jobTemplateOptions);
Logger.log(mail);

// Send the email
// await this.mailService.sendMail(mail);
}
}
11 changes: 11 additions & 0 deletions src/jobs/config/jobConfig.example.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,17 @@
"exchange": "jobs.write",
"queue": "client.jobs.write",
"key": "jobqueue"
},
{
"actionType": "email",
"auth": {
"user": "user",
"password": "password"
},
"to": "{{contactEmail}}",
"from": "from",
"subject": "[SciCat] Your {{type}} job was submitted successfully",
"bodyTemplateFile": "src/common/email-templates/job-template-simplified.html"
}
]
},
Expand Down
4 changes: 4 additions & 0 deletions src/jobs/jobs.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,10 @@ export class JobsController {
let datasetIds: string[] = [];
if (JobsConfigSchema.DatasetIds in jobCreateDto.jobParams) {
datasetIds = await this.checkDatasetIds(jobCreateDto.jobParams);
jobInstance.jobParams = {
...jobInstance.jobParams,
[JobsConfigSchema.DatasetIds]: datasetIds,
};
}
if (user) {
// the request comes from a user who is logged in.
Expand Down

0 comments on commit 6b56931

Please sign in to comment.