Skip to content

Commit

Permalink
add cron job checking for late notifications
Browse files Browse the repository at this point in the history
  • Loading branch information
dbarkowsky committed Dec 18, 2024
1 parent be1581d commit 5cb5166
Show file tree
Hide file tree
Showing 4 changed files with 112 additions and 0 deletions.
2 changes: 2 additions & 0 deletions express-api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"express-rate-limit": "7.4.0",
"morgan": "1.10.0",
"multer": "1.4.5-lts.1",
"node-cron": "3.0.3",
"node-sql-reader": "0.1.3",
"nunjucks": "3.2.4",
"pg": "8.13.0",
Expand All @@ -54,6 +55,7 @@
"@types/morgan": "1.9.9",
"@types/multer": "1.4.11",
"@types/node": "22.10.1",
"@types/node-cron": "3.0.11",
"@types/nunjucks": "3.2.6",
"@types/supertest": "6.0.2",
"@types/swagger-jsdoc": "6.0.4",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<html>
<head>
<title>PIMS Notifications Warning</title>
</head>
<body>
<h2>Some PIMS notifications may not have been sent.</h2>
<p>PIMS has detected that some projects have notifications in a pending state that have passed their intended send date.</p>
<p>Please review the following projects for notification issues:</p>
<ul>
{% for project in Projects %}
<li><p><b><a href={{project.Link}}>{{project.ProjectNumber}}</a></b> {{project.Name}}</p></li>
{% endfor %}
</ul>
</body>
</html>
9 changes: 9 additions & 0 deletions express-api/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import app from '@/express';
import { AppDataSource } from '@/appDataSource';
import { Application } from 'express';
import { IncomingMessage, Server, ServerResponse } from 'http';
import cron from 'node-cron';
import failedEmailCheck from '@/utilities/failedEmailCheck';

const { API_PORT } = constants;

Expand All @@ -18,6 +20,13 @@ const startApp = (app: Application) => {
if (err) logger.error(err);
logger.info(`Server started on port ${API_PORT}.`);

// 0 2 * * * == Triggers every 2:00AM
cron.schedule('0 2 * * *', async () => {
logger.info('Failed email check: Starting');
await failedEmailCheck();
logger.info('Failed email check: Completed');
});

// creating connection to database
await AppDataSource.initialize()
.then(() => {
Expand Down
86 changes: 86 additions & 0 deletions express-api/src/utilities/failedEmailCheck.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { AppDataSource } from '@/appDataSource';
import notificationServices, {
NotificationStatus,
} from '@/services/notifications/notificationServices';
import { NotificationQueue } from '@/typeorm/Entities/NotificationQueue';
import { User } from '@/typeorm/Entities/User';
import logger from '@/utilities/winstonLogger';
import { IsNull, LessThan, Not } from 'typeorm';
import nunjucks from 'nunjucks';
import getConfig from '@/constants/config';
import chesServices, { EmailBody, IEmail } from '@/services/ches/chesServices';
import { PimsRequestUser } from '@/middleware/userAuthCheck';
import { Project } from '@/typeorm/Entities/Project';
import networking from '@/constants/networking';

// Retrieves overdue notifications belonging to projects
const getOverdueNotifications = async (joinProjects: boolean = false) =>
await AppDataSource.getRepository(NotificationQueue).find({
where: {
Status: NotificationStatus.Pending,
SendOn: LessThan(new Date()),
ProjectId: Not(IsNull()),
},
relations: {
Project: joinProjects,
},
});

/**
* Used to check for notifications in the Pending state that have past their send date.
* If any notifications are found, an attempt to update their status is made.
* If any are still Pending after update, an email is created and sent to the business contact email.
*/
const failedEmailCheck = async () => {
try {
// Get all the notification records that are still pending and past their send date.
const overdueNotifications = await getOverdueNotifications();
// Get system user
const system = await AppDataSource.getRepository(User).findOneOrFail({
where: { Username: 'system' },
});
// Update these records
await Promise.all(
overdueNotifications.map((notif) =>
notificationServices.updateNotificationStatus(notif.Id, system),
),
);
// Temporary interface to allow for URL to project
interface ProjectWithLink extends Project {
Link?: string;
}
// If any of those records are still Pending, send an email to business with project names.
const stillOverdueNotifications = await getOverdueNotifications(true);
if (stillOverdueNotifications.length > 0) {
const uniqueProjects: Record<string, ProjectWithLink> = {};
stillOverdueNotifications.forEach(
(notif) =>
(uniqueProjects[notif.ProjectId] = {
...notif.Project,
Link: `${networking.FRONTEND_URL}/projects/${notif.ProjectId}`,
}),
);
const emailBody = nunjucks.render('FailedProjectNotifications.njk', {
Projects: Object.values(uniqueProjects) ?? [],
});
const config = getConfig();
const email: IEmail = {
to: [config.contact.toEmail],
cc: [],
from: 'PIMS System <[email protected]>', // Made up for this purpose.
bodyType: EmailBody.Html,
subject: 'PIMS Notifications Warning',
body: emailBody,
};
const sendResult = await chesServices.sendEmailAsync(email, system as PimsRequestUser);
// If the email fails to send, throw an error for logging purposes.
if (sendResult == null) {
throw new Error('Email was attempted but not sent. This feature could be disabled.');
}
}
} catch (e) {
logger.error(`Error in failedEmailCheck: ${e}`);
}
};

export default failedEmailCheck;

0 comments on commit 5cb5166

Please sign in to comment.