scheduling task in nodejs periodically to send emails to the user - javascript

I'm trying to send an email to the user periodically(daily, weekly, monthly) based on their preference from my nodejs application using node-cron. Here, user can edit/change their mail preference(weekly, daily..etc). so when they do, the cron expression gets updated to match their preference from the backend.
However, the previous preference still getting triggered, I believe that we have to destroy the previous job. how to do it?
Is this the right approach for my problem? or Do I need to store their preferences in database and triggered the mails manually based on the date?

This is how you should do it with node-cron :
const cron = require('node-cron');
const task = cron.schedule('* * * * *', () => {
console.log('stopped task');
}, {
scheduled: true
});
task.start();
task.stop();
task.getStatus(); // get you the status, so for every user check it and then restart it.
task.destroy(); // it will kill the job
It doesn't give any ID, but you can check the processID and store it anywhere.
You can check here for documentation.

Related

Would giving response to client while letting asynchronous operation continue to run a good idea?

So I need to implement an "expensive" API endpoint. Basically, the user/client would need to be able to create a "group" of existing users.
So this "create group" API would need to check that each users fulfill the criteria, i.e. all users in the same group would need to be from the same region, same gender, within an age group etc. This operation can be quite expensive, especially since there are no limit on how many users in one group, so its possible that the client requests group of 1000 users for example.
My idea is that the endpoint will just create entry in database and mark the "group" as pending, while the checking process is still happening, then after its completed, it will update the group status to "completed" or "error" with error message, then the client would need to periodically fetch the status if its still pending.
My implementation idea is something along this line
const createGroup = async (req, res) => {
const { ownerUserId, userIds } = req.body;
// This will create database entry of group with "pending" status and return the primary key
const groupId = await insertGroup(ownerUserId, 'pending');
// This is an expensive function which will do checking over the network, and would take 0.5s per user id for example
// I would like this to keep running after this API endpoint send the response to client
checkUser(userIds)
.then((isUserIdsValid) => {
if (isUserIdsValid) {
updateGroup(groupId, 'success');
} else {
updateGroup(groupId, 'error');
}
})
.catch((err) => {
console.error(err);
updateGroup(groupId, 'error');
});
// The client will receive a groupId to check periodically whether its ready via separate API
res.status(200).json({ groupId });
};
My question is, is it a good idea to do this? Do I missing something important that I should consider?
Yes, this is the standard approach to long-running operations. Instead of offering a createGroup API that creates and returns a group, think of it as having an addGroupCreationJob API that creates and returns a job.
Instead of polling (periodically fetching the status to check whether it's still pending), you can use a notification API (events via websocket, SSE, webhooks etc) and even subscribe to the progress of processing. But sure, a check-status API (via GET request on the job identifier) is the lowest common denominator that all kinds of clients will be able to use.
Did I not consider something important?
Failure handling is getting much more complicated. Since you no longer create the group in a single transaction, you might find your application left in some intermediate state, e.g. when the service crashed (due to unrelated things) during the checkUser() call. You'll need something to ensure that there are no pending groups in your database for which no actual creation process is running. You'll need to give users the ability to retry a job - will insertGroup work if there already is a group with the same identifier in the error state? If you separate the group and the jobs into independent entities, do you need to ensure that no two pending jobs are trying to create the same group? Last but not least you might want to allow users to cancel a currently running job.

DDP Rate limiter on login attempts in meteor

I'm trying to put a DDP rate limiter on the number of login attempts coming in from the client to the server. I've gone through the official documentation but I'm unable to verify if any of it actually works.
I've added the package: ddp-rate-limiter
My server code is:
Meteor.startup(function() {
var preventBruteForeLogin= {
type: 'method',
name: 'Meteor.loginWithPassword'
}
DDPRateLimiter.addRule(preventBruteForeLogin, 1, 2000);
DDPRateLimiter.setErrorMessage("slow down");
});
My understanding with the above is that it has added a rate limiting rule on Meteor.loginWithPassword method that it only allows one attempt every 2 seconds. However, given the little information available in the documentation and elsewhere on the net, I'm unable to figure out if it's actually working or if I've done it wrong. I've also gone through MC's blog on this and frankly I don't understand the coffee script code.
Can someone guide me through this?
Firstly according to the Meteor docs
By default, there are rules added to the DDPRateLimiter that rate limit logins, new user registration and password reset calls to a limit of 5 requests per 10 seconds per session.
If you want to remove, or replace default limits you should call Accounts.removeDefaultRateLimit() somewhere in your server side code.
Next you should create method similar to this one below
NOTE: You should pass only hashed password from client side to server side
Meteor.methods({
'meteor.login' ({ username, password }) {
Meteor.loginWithPassword({ user: username, password })
}
})
Then on your server side you should limit just created method.
if (Meteor.isServer) {
DDPRateLimiter.setErrorMessage(({ timeToReset }) => {
const time = Math.ceil(timeToReset / 1000)
return 'Try again after ' + time + ' seconds.'
})
DDPRateLimiter.addRule({
type: 'method',
name: 'meteor.login',
connectionId () {
return true
},
numRequests: 1,
timeInterval: 10000
})
}
This one will limit meteor.login method to one call in 10 seconds using DDP connection id. When you call the method on your client side you can get remaining time using callback error object.
Personally, I do the rate limiting using a slightly changed method from themeteorchef guide. I suggest you to the same, because it is much easier to implement when you build app with more methods to limit and for me, it is more readable. It is written using ES6 syntax. I recommend to read a little bit about it and start using it(you don't have to install additional packages etc.). I am sure that you will quickly like it.
EDIT
We found that using wrapping Meteor.loginWithPassword() method in another method may cause security problems with sending password as plain text. Accounts package comes with Accounts._hashPassword(password) method which returns hashed version of our password. We should use it when we call our meteor.login method. It may be done like below
Meteor.call('meteor.login', username, Accounts._hashPassword(password), function (err) {
//asyncCallback
})
Meteor.loginWithPassword is client side... you can't call it at server side
easy solution from meteor official documentation.
// Define a rule that matches login attempts by non-admin users.
const loginRule = {
userId(userId) {
const user = Meteor.users.findOne(userId);
return user && user.type !== 'admin';
},
type: 'method',
name: 'login'
};
// Add the rule, allowing up to 5 messages every 1000 milliseconds.
DDPRateLimiter.addRule(loginRule, 5, 1000);

Retrieve groupchat history using strophe.js

I am using ejabberd 15.06 version with Strophe.js. Retrieving the one-to-one chat from my backend database works fine. But how can I retrieve the groupchat history from the database??
For example, if I have a "strophe" group. When new users joins in the strophe group, then the chat history done in the group by other users should be displayed.
I am using this code
var pres = $pres({ to: room + "/" + nickname, from: connection.jid });
connection.send( msg.c('x', {xmlns: NS_MUC}));
if(chat_history != null){
var msg_history = msg.c('x', { "xmlns": "http://jabber.org/protocol/muc"}).c("history", chat_history, {maxstanzas: 50});
debugger;
console.log(msg_history);
}
In my console it looks like
h.Builder {nodeTree: presence, node: x}
I am stuck how to fetch the history of groupchat. Please help
Usually, unless the room has been configured not to send any history, send the join presence should be enough to let you receive the latest chat room messages.
Please, note that old messages have a delay tag on them to provide the time at which the original message was send, so make sure your client is not discarding those messages.
If you want control about the history size, you can use the Strophe MUC plugin to join the room and send the max stanzas and time limit as the history_attrs variable. Your server and room must also be configured to provide the history.
conn.muc.join(room, nick, msg_handler_cb, pres_handler_cb, roster_cb, password,{ maxstanzas: 10, seconds: 3600 });

Sending push notification to specific device knowing phone number, parse.com

I would just to get things clear here or get other suggestions if possible if it is better.
This is how my application works now:
1) Anonymous user is created if its the first time the user open the application
2) Phone verification is needed to be done. If verified, i save the phone number in a custom field in user object ( do i need to make this user a real user after this or can i still go with anonymous user?)( verification is one time only of course)
3) The user will be able to pick a friend from his contact list(ABPeoplePicker) and then send a push notification to that friend's device.
Now i have set up a relationship with the User and the installation object with this code:
PFInstallation *installation = [PFInstallation currentInstallation];
installation[#"user"] = [PFUser currentUser];
[installation saveInBackground];
And this created a pointer to the users ObjectId
So my question is how would i create a query and send a push notification to a number retrieved from that Users friend list?. I am having a hard time to connect how i can get from a phone number to the installation device that i need to send the notification to. If you could provide help in javascript since i read it is safer to send it through cloud code!
Also a subquestion mentioned above if i need to make the anonymous user to a real user.
many thanks!!
I'd recommend subscribing each user to their own channel where the channel name is equal to their phone number (Subscription can't be done in Javascript):
NSString *userPhoneNumber = ...
PFInstallation *currentInstallation = [PFInstallation currentInstallation];
// "x" added to the beginning of the userPhoneNumber since
// Parse doesn't allow channels to begin with a number
[currentInstallation addUniqueObject:[NSString stringWithFormat:#"x%#", userPhoneNumber] forKey:#"channels"];
[currentInstallation saveInBackground];
That way no query is required before pushing the notification:
var friendPhoneNumber = ...
var friendChannel = "x" + friendPhoneNumber;
Parse.Push.send({
channels: [ friendChannel ],
data: {
alert: message
}
}, {
success: function() {
// Push was successful
},
error: function(error) {
// Handle error
}
});

Node.js Kue how to restart failed jobs

I am using kue for delayed jobs in my node.js application.
I have some problems to figure out how I can restart a job using the API of kue without having to move the id of a job manually from the the list of failed jobs to the list of inactive jobs using redis commands.
Is this possible using kue?
I don't want to set a fixed number of retry attempts - I just want to retry specific jobs.
Suggestions for a well maintained alternative to kue are also welcome.
i dont know if this is working but you could try to reset the state of the job to active, and save the job again:
job.on('failed', function() {
job.state('inactive').save();
Edit: setting state to inactive will correctly re-enqueue the task.
This can also be done using queue level events.
queue.on('job failed', function(id, result) {
kue.Job.get(id, function(err, job) {
if (!err && shouldRetry(job))
job.state('inactive').save();
});
});
Thus you don't need to do for every job that you wish to retry. Instead you can filter it in the queue level event.
see Failure Attempts in the official docs
By default jobs only have one attempt, that is when they fail, they
are marked as a failure, and remain that way until you intervene.
However, Kue allows you to specify this, which is important for jobs
such as transferring an email, which upon failure, may usually retry
without issue. To do this invoke the .attempts() method with a number.
queue.create('email', {
title: 'welcome email for tj'
, to: 'tj#learnboost.com'
, template: 'welcome-email'
}).priority('high').attempts(5).save();
reference: failure attempts

Categories

Resources