nodeJS agenda start job on a specific date - node.js

Is there any way to start a job on a specific date using the agenda library?
This is how I'm creating the jobs:
const someData = {
name: 'Name',
value: 'Value'
}
const job = await agenda.create('daily alert', someData);
await job.repeatEvery('1 day').save() // specify when to start
But at this moment the job starts right after it was created, and I want to somehow specify the date when it should start, like tomorrow.

Use agenda.schedule(when, job, data)
Schedules a job to run name once at a given time. when can be a Date
or a String such as tomorrow at 5pm.
https://github.com/agenda/agenda#schedulewhen-name-data-cb
agenda.define('send email report', {priority: 'high', concurrency: 10}, (job, done) => {
const {to} = job.attrs.data;
emailClient.send({
to,
from: 'example#example.com',
subject: 'Email Report',
body: '...'
}, done);
});
(async function() {
await agenda.start();
await agenda.schedule('tomorrow at 5pm', 'send email report', {to: 'admin#example.com'});
})();

This will work for you.
const scheduleAJobOnlyOnce = (options) => {
let job = this.Agenda.create(options.jobName, options);
job.schedule(options.start); // run at this time
job.save();
};

Define a "job", an arbitrary function that agenda can execute
const dt = new Date();
dt.setMinutes(dt.getMinutes()+60); // run after an hour
agenda.schedule(dt, 'hello'); // this will do it
agenda.start();

Related

Google Cloud Tasks: How to update a tasks time to live?

Description:
I have created a Firebase app where a user can insert a Firestore document. When this document is created a timestamp is added so that it can be automatically deleted after x amount of time, by a cloud function.
After the document is created, a http/onCreate cloud function is triggered successfully, and it creates a cloud task. Which then deletes the document on the scheduled time.
export const onCreatePost = functions
.region(region)
.firestore.document('/boxes/{id}')
.onCreate(async (snapshot) => {
const data = snapshot.data() as ExpirationDocData;
// Box creation timestamp.
const { timestamp } = data;
// The path of the firebase document('/myCollection/{docId}').
const docPath = snapshot.ref.path;
await scheduleCloudTask(timestamp, docPath)
.then(() => {
console.log('onCreate: cloud task created successfully.');
})
.catch((error) => {
console.error(error);
});
});
export const scheduleCloudTask = async (timestamp: number, docPath: string) => {
// Convert timestamp to seconds.
const timestampToSeconds = timestamp / 1000;
// Doc time to live in seconds
const documentLifeTime = 20;
const expirationAtSeconds = timestampToSeconds + documentLifeTime;
// The Firebase project ID.
const project = 'my-project';
// Cloud Tasks -> firestore time to life queue.
const queue = 'my-queue';
const queuePath: string = tasksClient.queuePath(project, region, queue);
// The url to the callback function.
// That gets envoked by Google Cloud tasks when the deadline is reached.
const url = `https://${region}-${project}.cloudfunctions.net/callbackFn`;
const payload: ExpirationTaskPayload = { docPath };
// Google cloud IAM & ADMIN principle account.
const serviceAccountEmail = 'myServiceAccount#appspot.gserviceaccount.com';
// Configuration for the Cloud Task
const task = {
httpRequest: {
httpMethod: 'POST',
url,
oidcToken: {
serviceAccountEmail,
},
body: Buffer.from(JSON.stringify(payload)).toString('base64'),
headers: {
'Content-Type': 'application/json',
},
},
scheduleTime: {
seconds: expirationAtSeconds,
},
};
await tasksClient.createTask({
parent: queuePath,
task,
});
};
export const callbackFn = functions
.region(region)
.https.onRequest(async (req, res) => {
const payload = req.body as ExpirationTaskPayload;
try {
await admin.firestore().doc(payload.docPath).delete();
res.sendStatus(200);
} catch (error) {
console.error(error);
res.status(500).send(error);
}
});
Problem:
The user can also extend the time to live for the document. When that happens the timestamp is successfully updated in the Firestore document, and a http/onUpdate cloud function runs like expected.
Like shown below I tried to update the cloud tasks "time to live", by calling again
the scheduleCloudTask function. Which obviously does not work and I guess just creates another task for the document.
export const onDocTimestampUpdate = functions
.region(region)
.firestore.document('/myCollection/{docId}')
.onUpdate(async (change, context) => {
const before = change.before.data() as ExpirationDocData;
const after = change.after.data() as ExpirationDocData;
if (before.timestamp < after.timestamp) {
const docPath = change.before.ref.path;
await scheduleCloudTask(after.timestamp, docPath)
.then((res) => {
console.log('onUpdate: cloud task created successfully.');
return;
})
.catch((error) => {
console.error(error);
});
} else return;
});
I have not been able to find documentation or examples where an updateTask() or a similar method is used to update an existing task.
Should I use the deleteTask() method and then use the createTask() method and create a new task after the documents timestamp is updated?
Thanks in advance,
Cheers!
Yes, that's how you have to do it. There is no API to update a task.

How do I properly send emails to users exactly by 12am in their own time zone

I am working on a nodejs project that involves sending emails to users exactly by 12 am in their own timezone. I tried collecting the user's timezone during registration and storing it in the database. Then I tried looping through the users collection and mailing the user based on his/her timezone but it ended up mailing all users with the same time zone alongside users with no timezone and skipping those with a different timezone entirely.
I'm using expressjs, MongoDB for database management, and node-cron to schedule the email sending.
I will really appreciate your help guys.
email service file
const nodemailer = require("nodemailer");
const mg = require("nodemailer-mailgun-transport");
const handlebars = require("handlebars");
const fs = require("fs");
const path = require("path");
let cron = require("node-cron");
const { User } = require("../models/user");
const verses = require("kjv/json/verses-1769.json");
const store = require("store2");
const { bible_books } = require("./data");
const _ = require("lodash");
async function getUsersEmail() {
const users = await User.find().select(["email","timezone"]);
return users;
}
module.exports = async function () {
const emailTemplateSource = fs.readFileSync(
path.join(__dirname, "../views/template.hbs"),
"utf8"
);
const mailgunAuth = {
auth: {
api_key: process.env.mailgun_apikey,
domain: process.env.mailgun_domain,
},
};
const smtpTransport = nodemailer.createTransport(mg(mailgunAuth));
const template = handlebars.compile(emailTemplateSource);
(await getUsersEmail()).map(async function (value) {
cron.schedule("0 0 * * *", async function () {
console.log("running task every day at 12:00 am");
const bible_book = _.sampleSize(bible_books, 1);
store("bible", bible_book[0]);
let verse = store("bible");
let bible_passage = verse;
let bible_text = verses[bible_passage];
await User.findByIdAndUpdate(
value?._id,
{
bible: {
verse: bible_passage,
text: bible_text,
},
},
{ new: true }
);
const htmlToSend = template({
verse: bible_passage,
text: bible_text,
imageUrl:"https://world.png",
redirectUrl: "https://redirecturl.com/home",
year: new Date().getFullYear(),
});
const mailOptions = {
from: "from#domain.org",
to: value?.email,
subject: "Send",
html: htmlToSend,
};
smtpTransport.sendMail(mailOptions, function (error, response) {
if (error) {
console.log(error);
} else {
console.log(`Successfully sent email to ${mailOptions.to}.`);
}
});
}, {
scheduled: true,
timezone:value?.timezone
});
console.log(value?.timezone)
});
};
calling my service file in my index.js
require("./service/emailServiceFile")();
There are many approaches to do this but let's discuss your solution
The time is midnight[12:00] somewhere around the globe now, so the schedule should run every hour not day.
cron.schedule("0 * * * *", ...
You need to check if it is after 12:00 am in the user's timezone with something like this.
if (new Date(new Date().toLocaleString('en-us', { timeZone: value?.timezone })).getHours() == 0) { //Send Emails }

BullMQ - Group/Job based completion

I am using BullMQ, Redis and MySQL in a producer-consumer model to process jobs.
I have a producer that looks like this:
const jobOptions = {
removeOnComplete: true, // remove job if complete
delay: 60000,
attempts: 3
};
const sasWorkerProducerService = ({redisConnection}) => {
const queueLogId = async (jobId, logIds) => {
for(const logId of logIds) {
redisConnection.add({
jobId: jobId,
scraperPriceLogId: logId
}, jobOptions);
}
}
return {
queueLogId
}
}
module.exports = sasWorkerProducerService;
And I have a worker service that handles the jobs:
const Bull = require('bull');
const connectQueue = (name) => new Bull(name, {
redis: {
port: 6379, host: 'xxxx', password: 'xxxx'
},
settings: {
maxStalledCount: 5
}
})
module.exports = { connectQueue }
const nameQueue = 'sas-worker'
const cases = await connectQueue(nameQueue)
const initJob = () => {
console.info('job is working!');
cases.process('__default__', 300, processJob);
cases.on('failed', handlerFailure);
cases.on('completed', handlerCompleted);
cases.on('stalled', handlerStalled);
}
initJob()
Notice that the producer sends a jobId as part of the payload. This ID is an identifier that I have generated and stored in a MySQL database. A job represents a batch of items that need to be processed. I don't care about what order the jobs for a batch get completed in.
However, how can I determine once all of the jobs for a given jobId have been completed? I need to do some work after all the jobs have been processed.
I understand the nature of a a producer-consumer model is to do work on an item and forget about it, but how can I do some final, post-processing work for a job after all the items have indeed been processed?

Cron job scheduling on heroku . Please go thru my code below and tell me if there is a solution to this

I am making an appointment system so every time an appointment is booked i want to run a cron job so the code goes as follows
const Appointment = new appointment({
p_id: req.user._id,
d_id: req.params.id,
date,
time,
type,
relative,
phoneNo1,
phoneNo2,
payment,
});
await Appointment.save();
await checkStatus(req.params.id, Appointment.time, date);
// the above function schedules a cron job
res.status(200).json({
status: "success",
message: "Appointment placed successfully",
Appointment,
});
CheckStatus Function is as below
export const checkStatus = async (id, time, date) => {
if (todayDate === date) {
const job = Cron("*/5 * * * *", async () => {
const Appointment = await appointment
.find({
$and: [
{ d_id: mongoose.Types.ObjectId(id) },
{ date: date },
{ time: time },
{ Status: "incoming" },
],
})
.populate("p_id d_id", "name email cardCollections.doctorName phoneNumber");
console.log("Appointment", Appointment);
console.log("diff", diff);
if (Appointment.length === 0) {
job.stop();
}
if (diff <= 4 && diff > 3) {
SendEmail(
Appointment[0].p_id.email,
`Reminder to accept appointment 1nd cond at ${now} `,
"Appointments waiting for you"
);
} else if (diff <= 3 && diff > 1) {
// send sms to doctor
// SendSms(
// Appointment[0].d_id.phoneNumber,
// `Reminder to accept appointment 2nd cond at ${now} `
// );
SendEmail(
Appointment[0].p_id.email,
`Reminder to accept appointment 2nd cond at ${now} `,
"Appointments waiting for you"
);
} else if (diff <= 1) {
// send sms to doctor
Appointment.forEach(async (apt) => {
apt.Status = "dismissed";
apt.cancellationStatus.reason = `Dismissed by the doctor`;
apt.cancellationStatus.cancelledAt = Date.now();
await apt.save();
});
SendEmail(
Appointment[0].p_id.email,
`All appointment are cancelled `,
"Cancellation due to no response"
);
job.stop();
}
});
}
}
So the code works perfectly during localhost but i cant find a way to do the same in heroku as i m confused how do i send parameters and especially i want to run the code for when a appointment is booked .I tried looking various blogs but all of them they show cron scheduling independently without any params .
So basically i want to chedule a cron job (checkStatus) everytime an appointment is booked Can anyone help me with this???

Agenda not running jobs on schedule

I am trying to use agenda to schedule jobs at a certain date and time, but the jobs are not running when the specified date is reached.
This is how I'm creating the job:
agenda.create(type, data)
.schedule(new Date(startDate))
.repeatEvery('11 21 * * *', {timezone: 'Europe/Bucharest'})
.save();
This is how I start agenda:
const Agenda = require('agenda');
const mongoDB = process.env.DB_PATH;
const mongoConnectionString = mongoDB;
let agenda = new Agenda({db: {address: mongoConnectionString, collection: 'jobs'}});
let jobTypes = process.env.JOB_TYPES ? process.env.JOB_TYPES.split(',') : [];
jobTypes.forEach(function(type) {
require('./jobs/' + type)(agenda);
});
if(jobTypes.length) {
agenda.on('ready', function() {
console.log('agenda start')
agenda.start();
});
}
function graceful() {
agenda.stop(function() {
process.exit(0);
});
}
process.on('SIGTERM', graceful);
process.on('SIGINT' , graceful);
export default agenda;
This is an example with a job that didn't start on the scheduled date:
Is there something I'm doing wrong?
EDIT:
The job is triggered if I do schedule(new Date()), but it won't using a defined date.

Resources