Mongoose push - find or update to array of objects - javascript

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)

Related

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.

Meteor User table value axtracting

How do I pick the email address value from meteor Mongo user table?
I have written below query to pick the element:
users=Meteor.users.find({},{emails:1})
This the code I have written to fetch the email address, but I don't know how much it's affecting performance in the code:
users = Meteor.users.find({})
users.forEach(function(key,option){
key.emails.forEach(function (key,option){
console.log(key.address)
});
});
In meteor, you should call:
users = Meteor.users.find({}, { fields: { emails: 1 } })
Reference in docs
EDIT
Please remember users is a cursor object. Cursor objects can be handled directly in templates, and must be the return of publications. You can't iterate a cursor directly in a javascript loop.
Example: (remember authorization in production publications)
Meteor.publish('user-emails', function() {
return Meteor.users.find({}, { fields: { emails: 1 } });
});
If you want to directly access the user instances, for example to iterate them in a javascript code, you need to fetch the cursor (reference in docs).
Example:
var users = Meteor.users.find({}, { fields: { emails: 1 } }).fetch();
Now users is an array of users. Feel free to iterate them.
Example (I'm using underscore.js):
var users = Meteor.users.find({}, { fields: { emails: 1 } }).fetch();
_.each(users, function(user) {
console.log(user.emails);
});
Now, if you need a vector only with emails, one on each index, you can pluck the emails from a fetched array with underscore.js (reference of pluck)
var emails = _.pluck(Meteor.users.find({}, { fields: { emails: 1 } }).fetch(), 'emails');
Hope it works :)
if its not working, dont forget to return
return users

Remove unverified Meteor users after certain time period?

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 }
]
}
}
}

Express returning "User is not authorized" when modifying object from another user

I have a Classroom model that looks like this:
/**
* Classroom Schema
*/
var ClassroomSchema = new Schema({
created: {
type: Date,
default: Date.now
},
participants: [{
type: Schema.ObjectId,
ref: 'User'
}],
lesson: {
type: Schema.ObjectId,
ref: 'Lesson',
required: 'Define a lesson for this classroom',
},
user: {
type: Schema.ObjectId,
ref: 'User'
},
currentTaskIndex: {
type: Number,
default: 0
},
});
As you can see, the model keeps a reference to the user that have created the Classroom (User) and it also has many participants (more Users).
I'm trying to add the functionality to allow other Users (not the creator of the Classroom) to join the Classroom by sending a PUT request to the classroom API with a new User in the participants field. However, since the User sending the request is not the one who created the object, Express is returning a 403 (Forbidden):
exports.hasAuthorization = function(req, res, next) {
if (req.classroom.user.id !== req.user.id) {
return res.status(403).send('User is not authorized');
}
next();
};
What's the best approach/pattern to solve this allowing Users to join Classrooms created by another User but not to do other actions like deleting the object. Other fields might be updated by another participants, like the currentTaskIndex.
Thanks for your help!
You could add a 'master' or 'admin' field to your classroom, and store the Creator's userId in it.
Then you can allow the delete/modify, etc actions only for the user who is master of this particular classroom, while letting everyone in.

Access user email address in Meteor JS app

I am building an app using Meteor and need to access the stored email address of a logged-in user.
I am currently using:
var userObj = Meteor.user();
console.log(userObj);
to access the user. However, I am only able to access the id. The email address is stored in a nested object that looks like this:
[Object {address="address#gmail.com", verified=false}]
I have tried various ways to traverse the JSON object but can't figure out how to access the value I need.
Meteor.user().emails[0].address works for me.
Here's what the doc says:
By default the server publishes username, emails, and profile. See
Meteor.users for more on the fields used in user documents.
Example user document:
{
_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: 1349761684042,
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 }
]
}
}
}
You don't specify how you are authenticating users. For example, if you were using Google authentication only, the email address would be found only in
Meteor.user().services.google.email
So, it depends.
Try this:
Meteor.user().emails[0].address
Regards,

Categories

Resources