Remove unverified Meteor users after certain time period? - javascript

I'm working on a Meteor app and I want to rid the app of the antiquated "Create New Account" then "Check Your Email to Verify Account" workflow. I want users to sign up with an email (not a username) and immediately have some access to the app, a verification email will fire, and then they can verify at a later time to get full access. So I would be calling Accounts.createUser immediately and always (as long as their email isn't already taken.)
How would I go about "taking out the garbage" of any email-based accounts that get created but are never verified? Like if I wanted to delete an unverified account after 3 days for example?
The only way I can think of is to do a really long Meteor.setTimeout command in the Accounts.onCreateUser hook that would check if the accounts email is verified after three days (which is 259,200,000 ms BTW). Is this practical? Will it work? Is there another method in Meteor to do something like this? That isn't dependent on user actions. I don't want to do this when the user logs in because a user could create an account with a bad email and then never log in again, but a future user with that email would then be locked out.
Does Meteor have any type of "server rules" that would fire every so often to run checks? Like setup some kind of nightly maintenance function/routine? Also, is it possible to remove a User like this? I was reading another article that said something about not being able to remove users through the API. I definitely need to be able to do that because the whole point is to make that email/account available to a user that actually owns that email.
If I have to I can go to the "force verification" methodology, but I see other sites doing the above and I like it much better. It's also way slicker on mobile.
EDIT: I was just looking at the Meteor docs and sending a "verification email" requires a userId which means you have to create a user no matter what - Accounts.sendVerificationEmail(userId, [email]). So I guess no matter what a user with a bad email could get created. So it would be nice to know how to do the above.

You can use a simple cron with Meteor.setInterval.
I wouldn't advise using Meteor.setTimeout with the onCreateUser hook. This is because if you're server is restarted/has a crash/you update the code within this 3 day period the snippet won't run.
Server Side Code:
Meteor.setInterval(function() {
// new Date must always be new Date()
var three_days_ago = new Date(new Date().getTime() - (3600000*72))
Meteor.users.find({
createdAt: {
$lte: three_days_ago //Users created less than 3 days ago
},
'emails.0.verified': false
}).forEach(function(user) {
//Do action with 'user' that has not verified email for 3 days
});
}, 3600000);
The above code runs every hour, checking for users that were created more than 3 days ago (72 hours) that have not yet verified their first email address.

To mix and update the old answers (#Akshat wrote new Date(new Date.getTime() - (3600000*72)) but it's new Date(new Date().getTime() - (3600000*72)) ), in a cron job way every day
Install synced-cron
meteor add percolate:synced-cron
On server, in a cron.js
import { SyncedCron } from 'meteor/percolate:synced-cron';
import { deleteUnverifiedUsers } from './delete-unverifiedUsers.js';
SyncedCron.config({ log: false, utc: true });
SyncedCron.add({
name: 'Check verified Users',
schedule(parser) {
//return parser.text('every 10 seconds');
//return parser.text('every 1 hour');
return parser.text('every 24 hours');
},
job() {
deleteUnverifiedUsers();
}
});
SyncedCron.start();
On server in the task file (here delete-unverifiedUsers.js)
import { Meteor } from 'meteor/meteor';
// Set since variable in milliseconds*hours : ex 1h = 3600000*1
var one_day_ago = new Date(new Date().getTime() - (3600000*24))
Meteor.users.find({
// condition #1: users created since variable ago
createdAt: {
$lte: one_day_ago,
},
// condition #2: who have not verified their mail
'emails.0.verified': false
}).forEach(function(user) {
// Delete the users who match the 2 conditions
return Meteor.users.remove({_id: user._id})
});

Use percolatestudio:synced-cron:
meteor add percolatestudio:synced-cron
Then in your Javascript on the server:
if (Meteor.isServer) {
Meteor.startup(function () {
SyncedCron.add({
name: 'Remove unverified users',
schedule: function(parser) {
// parser is a later.parse object
return parser.text('every Wednesday at 12am');
},
job: function() {
//TODO: implement RemoveUnverifiedUsers function
var numUsersRemoved = RemoveUnverifiedUsers();
return numUsersRemoved;
}
});
// start the cron daemon
SyncedCron.start();
}
}
Edit #1:
The Meteor user will have an email object with verified:false in the Meteor.users collection. This part of the meteor docs provides the following example:
{
_id: "bbca5d6a-2156-41c4-89da-0329e8c99a4f", // Meteor.userId()
username: "cool_kid_13", // unique name
emails: [
// each email address can only belong to one user.
{ address: "cool#example.com", verified: true },
{ address: "another#different.com", verified: false }
],
createdAt: Wed Aug 21 2013 15:16:52 GMT-0700 (PDT),
profile: {
// The profile is writable by the user by default.
name: "Joe Schmoe"
},
services: {
facebook: {
id: "709050", // facebook id
accessToken: "AAACCgdX7G2...AbV9AZDZD"
},
resume: {
loginTokens: [
{ token: "97e8c205-c7e4-47c9-9bea-8e2ccc0694cd",
when: 1349761684048 }
]
}
}
}

Related

use HTML date picker in IBM watson conversation dialog to get date as input from user

I am trying to use <input type="date" id="birthday" name="birthday"> to display a calendar to be selected by user on IBM dialog. So, when this calendar will ask for date in the chat bot and then after user selecting the date, it should store the selected date as variable or any context variable.
How I have to implement it in IBM Watson chatbot.
Thanks
It really depends on how your front-end app is built to call IBM Watson API. Trying to be more generic, you would need to do:
First, you would need to add the html syntax into your answer/response node on Watson Conversation:
Please select your date: <br />
<input type="date" id="birthday" name="birthday">
And in your front-end code (probably index.html that contains your UI), you would need a function to identify what was selected, e.g:
document.getElementById("birthday").addEventListener("change", function() {
let inputDate = this.value;
let ifYouWantEntireDateFormat = new Date(inputDate);
console.log(inputDate); // 2020-04-20
console.log(ifYouWantEntireDateFormat); //e.g. Mon April 20 2020 00:00:00 etc
});
You could also use querySelector function. In addition, if no value is selected it will return "Invalid date"
With all that in mind, you also need to know that Watson API accepts the payload having the context variables on it, which is what you need. I would recommend checking the API docs first to understand more. But according to what I understood, your payload might be similar to:
const AssistantV2 = require('ibm-watson/assistant/v2');
const { IamAuthenticator } = require('ibm-watson/auth');
const assistant = new AssistantV2({
version: '2020-04-01',
authenticator: new IamAuthenticator({
apikey: '{apikey}',
}),
url: '{url}',
});
assistant.message({
assistantId: '{assistant_id}',
sessionId: '{session_id}',
input: {
'message_type': 'text',
'text': 'Hello',
'options': {
'return_context': true
}
},
context: {
'global': {
'myDatePicker': inputDate,
'system': {
'user_id': 'my_user_id'
}
},
'skills': {
'main skill': {
'user_defined': {
'account_number': '123456'
}
}
}
}
})
.then(res => {
console.log(JSON.stringify(res.result, null, 2));
})
.catch(err => {
console.log(err);
});
Note The context is included in message responses only if you return_context=true in the message request.
Important links:
Input type Date - MDN
Watson Assistant API doc - IBM Watson

Ask the user questions, then user it in a discord.js embed

I want to make it so a moderator does !Host then the bot does
What time do you want it to start?
User Response 17:00 CET
Bot then does What Gamemode
User Response Normal 2v2 or something like that
Bot Response Who do you want to host
User Response #NaP
Bot Response Bot then replaces the values below
if (m == '.nap tournament' || m == '.tournament info' || m == '.nap t' || m == '.nap tourny' || m == '.ti') {
message.channel.send({
embed: {
color: 000000,
author: {
name: bot.user.username,
icon_url: bot.user.avatarURL
},
title: "**Nap Weekly Tournament**",
description: "Every Week We Have A Tournament For All The Nappies!!!",
fields: [{
name: "**Time**",
value: "Saterdays at 17:00 CET or 5:00 pm CET."
},
{
name: "**Gamemode**",
value: "Normal 2v2"
},
{
name: "**Tournament Host**",
value: "<#!" + 'Whoister#7002' +
">"
}
],
timestamp: new Date(),
footer: {
icon_url: bot.user.avatarURL,
text: "Arrara bot"
}
}
});
I'd recommend using the RichEmbed class built into discord.js as it looks cleaner and it's quite easy to use as well, here's an example:
const embed = new Discord.RichEmbed()
.setTitle("This is your title, it can hold 256 characters")
.setAuthor("Author Name", "https://i.imgur.com/lm8s41J.png")
.setColor(0x00AE86)
.setDescription("Embed description here")
message.channel.send({embed});
Here is the link to see the other things you can add to the embed: https://discord.js.org/#/docs/main/stable/class/RichEmbed
Then for dealing with a user response like you want to, I'd use a message collector, https://discord.js.org/#/docs/main/stable/class/TextChannel?scrollTo=createMessageCollector
You can find an example there as well, you can set the filter to a user ID, for example const filter = m => m.author.id === message.author.id. You can also use maxMatches to set the maximum number of messages are collected before the end event is emitted. channel.createMessageCollector(filter, { maxMatches: 2 }); //Will collect 2 messages and then emit 'end' event. The other event that is emitted is collect which is emitted everytime a response comes back matching your filter, and to get the content of that response you could use something such as collector.on('collect', m => console.log("Collected:" + m.content));. You can create multiple collectors and send a message in between to prompt the user first and then get their input.

Mongoose push - find or update to array of objects

My situation is as following:
I have a session collection, each session is a chatroom.
When a user sends a new message, other users should be notified, but to prevent spam, I need to build a check if a user got a notification mail in the last 10 minutes.
So my session collection has a notification array of objects with: sessionId, userId and date
Everytime someone sends a new message, I have to send the mails and push to the notifications column, but it should overwrite the userId to prevent the notifications column from growing.
This is what I've tried:
sessionSchema
.findByIdAndUpdate(sessionId, {
$push: {
notifications: {
userId: userId,
date: moment().utc().toDate()
}
}
}, callback)
However with push, the column keeps growing. How can I use push to overwrite the already created notifications by a certain userId?
Maybe, try to add options in query to DB.
sessionSchema
.findByIdAndUpdate(sessionId, {
$push: {
notifications: {
userId: userId,
date: moment().utc().toDate()
}
}
}, { new: true }, callback)

Modelling a Chat like Application in Firebase

I have a Firebase database structuring question. My scenario is close to a chat application. Here are the specifics
- users(node storing several users of the app)
- id1
name: John
- id2
name: Meg
- id2
name: Kelly
- messages(node storing messages between two users)
- message1
from: id1
to: id2
text: ''
- message2
from: id3
to: id1
text: ''
Now imagine building a conversations view for an individual user. So I want to fetch all messages from that particular user
and to that particular user
I am writing it as follows right now:
let fromMessagesRef = firebase.database().ref('messages').orderByChild('from').equalTo(firebase.auth().currentUser.uid)
fromMessagesRef.once("value").then((snapshot) => {/* do something here*/})
let toMessagesRef = firebase.database().ref('messages').orderByChild('to').equalTo(firebase.auth().currentUser.uid)
toMessagesRef.once("value").then((snapshot) => {/* do something here*/})
Questions:
Is this the right way to model the problem?
If yes, is there a way to combine the above 2 queries?
I would store the data like this:
- users(node storing several users of the app)
- id1
name: John
messages
message1: true
message2: true
- id2
name: Meg
messages
message1: true
message3: true
- id3
name: Kelly
messages
message2: true
message3:true
- messages(node storing messages between two users)
- message1
from: id1
to: id2
text: ''
- message2
from: id3
to: id1
text: ''
- message3
from: id2
to: id3
text: ''
Firebase recommends storing things like this. So in your case your query would be
let fromMessagesRef = firebase.database().child('users').child(firebase.auth().currentUser.uid).child('messages')
This allows it to be very fast as there is no orderBy being done. Then you would loop over each message and get it's profile from the messages node.
The structure you have is one possible way to model this data. If you're building an application like this, I would highly recommend the angularfire-slack tutorial. One potentially faster way to model the data would be to model the data like is suggested in this tutorial https://thinkster.io/angularfire-slack-tutorial#creating-direct-messages
{
"userMessages": {
"user:id1": {
"user:id2": {
"messageId1": {
"from": "simplelogin:1",
"body": "Hello!",
"timestamp": Firebase.ServerValue.TIMESTAMP
},
"messageId2": {
"from": "simplelogin:2",
"body": "Hey!",
"timestamp": Firebase.ServerValue.TIMESTAMP
}
}
}
}
}
The one thing you need to watch for in this case if you choose to do it like this is that before your query, you need to sort which user will be the "primary user" under whom the messages will be stored. As long as you make sure that is the same every time, you should be good to go.
One improvement you could make to this structure is something you already pointed out - flattening your data and moving the messages to another node - like you did in your example.
To answer your second question, if you were to keep that structure, I think you would need both of those queries, because firebase does not support a more complicated OR query that would allow you to search both at the same time.
No. Firebase Auth subsystem is where you want to store the email, displayName, password, and photoURL for each user. The function below is how you do it for a password-based user. oAuth-based users are easier. If you have other properties you want to store, like age for example, put those under a users node with each users uid that Firebase Authentication provides you.
function registerPasswordUser(email,displayName,password,photoURL){
var user = null;
//NULLIFY EMPTY ARGUMENTS
for (var i = 0; i < arguments.length; i++) {
arguments[i] = arguments[i] ? arguments[i] : null;
}
auth.createUserWithEmailAndPassword(email, password)
.then(function () {
user = auth.currentUser;
user.sendEmailVerification();
})
.then(function () {
user.updateProfile({
displayName: displayName,
photoURL: photoURL
});
})
.catch(function(error) {
console.log(error.message);
});
console.log('Validation link was sent to ' + email + '.');
}
As for the messages node, get the random id from Firebase Realtime Database's push method and use that as the id of each message under messages. Firebase queries are used:
var messages = firebase.database().ref('messages');
var messages-from-user = messages.orderByChild('from').equalTo('<your users uid>');
var messages-to-user = messages.orderByChild('to').equalTo('<your users uid>');
messages-from-user.once('value', function(snapshot) {
console.log('A message from <your users uid> does '+(snapshot.exists()?'':'not ')+' exist')
});
messages-to-user.once('value', function(snapshot) {
console.log('A message to <your users uid> does '+(snapshot.exists()?'':'not ')+' exist')
});
Define an index for messages-from-user and messages-to-user in your Rules:
{
"rules": {
"messages": {
".indexOn": ["from", "to"]
}
}
}
Below data structure gives you more flexibility with you data. Instead of having to store each messages that user sent back and forth I would suggest to store it in separate node and store the messageID with each user that is involved in the conversation.
Obviously you need to set the security rules, so other user can't see conversation if they are not in the conversation.
By doing this we are not creating deep chain node inside user info
- users(node storing several users of the app)
- id1
name: John
messages: [msID1, msID9]
- id2
name: Meg
messages: [msID1, msID7]
- id3
name: Kelly
messages: [msID9, msID7]
- messages(node storing messages between two users)
- msID1
from: id1
to: id2
text: ''
- msID7
from: id3
to: id2
text: ''
- msID9
from: id3
to: id1
text: ''
Firebase has actually built a demo (and extendible) chat application called Firechat. The source and documentation is provided, and of particular note is the section on their data structures.
Although they've implemented chatrooms, you can see that they've flattened their data structures as in many of the other answers. You can read more about how and why this is done in the Firebase guide.

correct way to use Stripe's stripe_account header from oauth with meteor

I'm trying to build a platform based on Meteor that uses Stripe Connect. I want to use the "preferred" authentication method from Stripe (Authentication via the Stripe-Account header, https://stripe.com/docs/connect/authentication) so that I can create plans and subscribe customers on behalf of my users. I cannot get it to work. I tried with a second params object, similar to the exemple in the documentation:
var stripeplancreate = Meteor.wrapAsync(Stripe.plans.create, Stripe.plans);
var plan = stripeplancreate({
amount: prod.price,
interval: prod.interv,
name: prod.name,
currency: prod.curr,
id: prod.id+"-"+prod.price+"-"+prod.curr+"-"+prod.interv,
metadata: { prodId: prod._id, orgId: org._id },
statement_descriptor: prod.descr
},{stripe_account: org.stripe_user_id});
but I get "Exception while invoking method 'createStripeProduct' Error: Stripe: Unknown arguments ([object Object]). Did you mean to pass an options object? See https://github.com/stripe/stripe-node/wiki/Passing-Options." which does not seem to accurately reflect the issue but prompted me to try adding stripe_account in the params object itself:
var stripeplancreate = Meteor.wrapAsync(Stripe.plans.create, Stripe.plans);
var plan = stripeplancreate({
amount: prod.price,
(...)
statement_descriptor: prod.descr,
stripe_account: org.stripe_user_id
});
I then get the following error: "Exception while invoking method 'createStripeProduct' Error: Received unknown parameter: stripe_account"
Any ideas? Has anybody managed to have Stripe Connect stripe_account authentication work with Meteor, especially with Meteor.wrapAsync(...)?
This should work for wrapAsync, HOWEVER check out my answer here for possible issues with wrapAsync - Wrapping Stripe create customer callbacks in Fibers in Meteor:
Here is also a great video on wrapAsync: https://www.eventedmind.com/feed/meteor-meteor-wrapasync
var createStripePlanAsync = function(shoppingCartObject, callback){
stripe.plans.create({
amount: shoppingCartObject.plan.totalPrice,
interval: shoppingCartObject.plan.interval,
name: shoppingCartObject.plan.planName,
currency: "usd",
id: shoppingCartObject.plan.sku //this ID needs to be unique!
}, function(err, plan) {
// asynchronously called
callback(err, plan);
});
};
var createStripePlanSync = Meteor.wrapAsync(createStripePlanAsync);
var myShoppingCart = {
customerInfo: {
name: "Igor Trout"
},
plan: {
totalPrice: 5000,
interval: "month",
name: "Set Sail For Fail Plan",
sku: "062015SSFF"
}
};
// Creates the plan in your Stripe Account
createStripePlanSync(myShoppingCart);
Later when you subscribe a customer to a plan you just refer to the plan via the id that you gave the plan when you first created it.
After much trying multiple things, for now, I just managed to get it working using the stripe-sync package instead of the "normal" one + wrapAsync.
try{
var plan = Stripe.plans.create({
amount: prod.price,
...
},{stripe_account: org.stripe_user_id});
}catch(error){
// ... process error
}

Categories

Resources