How can I add/remove user specific data in Meteor - javascript

A few questions about storing user data in MongoDB. What is the best place in mongo to store user specific data, such as User settings, User photo url, User friends, User events?
In Mongo, user data is stored in:
Meteor
/ Collections
/ users
/ _id
/ profile
/ services
Should I add there a new collections? In a following way:
/ events / _id's
/ friends / _id's
/ messages / _id's
/ settings
How should I publish user's private data and manipulate this collections, to be sure it's save and no one else will modify or have access to private data of another person.

You can add data to the users profile field like this:
Meteor.users.update( id, { $set: { 'profile.friends': someValue } } );
To only publish specific fields you can do something like this:
Meteor.publish( 'users', function () {
return Meteor.users.find( {}, { fields: { 'profile.friends': 1 } } );
});
Hope this helps.

Normalization
"Database normalization is the process of organizing the attributes and tables of a relational database to minimize data redundancy."
MongoDB is a non relational database. This makes normalized data hard to query for. This is why in MongoDB we denormalize data. That makes querying for it easier.
It depends on your use-case. The question is basically when to demormalize. It's mostly a matter of opinion. But objective here are some pros and cons:
Pros to demormalization
It's easier to retrieve data (Due to Mongo not beeing a relational DB)
It performs better if you are always getting the data in bulk
Cons to demormalization
It doesn't scale well for things like user.messages (You can't just publicize some messages)
In your case I'd definitly go for seperate collections for events, friends and messages. Setting can't expand infinitly. So I'd put it into the users collection.
Security
I'd use a publications and allow and deny for this. Let me make an example for Messages:
Collection
Messages = new Mongo.Collection('Messages')
Messages.insert({
sender: Meteor.userId,
recipient: Meteor.users.findOne()._id,
message: 'Hello world!'
})
Publication
Meteor.publish('userMessages', function (limit) {
return Messages.subscribe({
$or: [
{sender: this.userId},
{recipient: this.userId}
]
}, {limit: limit})
})
Allow
function ownsMessage (user, msg) {
return msg.sender === user ? true : false
}
Messages.allow({
insert: function (userId, newDoc) {
!!userId
},
update: function (userId, oldDoc, newDoc) {
if(
ownsMessage(userId, oldDoc) &&
ownsMessage(userId, newDoc)
) return true
return false
},
remove: function () {
return false
}
})
This code is untested, so it might contain small errors

Related

Prisma how to update many rows with different data

what is the best way to update many records with different data ?
I'm doing it like this
const updateBody = JSON.parse(req.body);
try {
for (let object of updateBody) {
await prisma.comissions.upsert({
where: {
producer: object.producer,
},
update: {
rate: object.rate,
},
create: object,
});
}
I'm being able to update it, but it's taking a really long time to do so. I'm aware of transaction, but i'm not sure how to use it.
In Prisma transaction query is used in two ways.
Sequential operations: Pass an array of Prisma Client queries to be executed sequentially inside of a transaction.
Interactive transactions: Pass a function that can contain user code including Prisma Client queries, non-Prisma code, and other control flow to be executed in a transaction.
In our case we should use the interactive transaction, Because it contain user code, To use the callback function in the Prisma transaction, we need to add a preview feature to the Prisma.schema file
generator client {
provider = "prisma-client-js"
previewFeatures = ["interactiveTransactions"]
}
prisma.$transaction(async(prisma) => {
try {
for (let object of updateBody) {
await prisma.comissions.upsert({
where: {
producer: object.producer,
},
update: {
rate: object.rate,
},
create: object,
});
}
});

azure Mobile app query like sql Statement

I am working with Azure Mobile app and here is my SQL statement
return new Promise((resolve, reject) => {
var query = {
sql: "SELECT Id FROM Groups WHERE Members LIKE '%#userId%' and Members '%#friendId%' and type = #roomType ",
parameters: [
{ name: 'userId', value: userId},
{ name: 'friendId', value: friendId },
{ name: 'roomType', value: type }
]
};
context.data.execute(query)
.then(function (results) {
resolve(results);
}).catch(error => { reject (error);});
});
and I go to SQL and fill all variable like this
SELECT Id FROM Groups Where Members LIKE '%05adf56b-c128-4203-802f-d8d0e2916210%' and Members LIKE '%21B69402-7E9C-4BA3-99A8-6D84A96FA866%' and type = 0
and i got many records. so i didn't know what i did wrong
It seems that your SQL statement looks incorrect, you may miss a like keyword for Members '%#friendId%'.
I have tested the following SQL statement on my azure mobile app, it returns nothing.
SELECT Id FROM Groups WHERE Members LIKE '%#userId%'
After some trials, I found the following statement could work as expected:
var query = {
sql: "SELECT * FROM test5 where name LIKE #name",
parameters: [
{name:"name",value:"%"+req.query.param1+"%"}
]
};
SELECT Id FROM Groups Where Members LIKE '%05adf56b-c128-4203-802f-d8d0e2916210%' and Members LIKE '%21B69402-7E9C-4BA3-99A8-6D84A96FA866%' and type = 0
and i got many records. so i didn't know what i did wrong
Based on your description, it seems that your Members column contains many GUIDs. You need to check your table records by yourself, or you could update your question with more details about your records for us to narrow down this issue.

Join in bookshelf.js

How do i achieve the following relation in bookshelf.js
SELECT user_accounts.user_id, `event_id`
FROM `calendar_events`
JOIN user_accounts ON user_accounts.user_id = calendar_events.`created_by`
LIMIT 10
My Model
var CalendarEvent = bookshelf.Model.extend({
tableName: 'calendar_events',
hasTimestamps: ['created_on'],
user: function() {
return this.hasOne('UserAccount','user_id');
}
});
var UserAccount = bookshelf.Model.extend({
tableName: 'user_account'
});
If you wanted to get that exact style query, you could use the knex query builder and try to build a query to match your needs
have not tested this but should be something like this
CalendarEvent.forge().query(function(qb){
qb.join('user_accounts', 'user_accounts.user_id', '=', 'calendar_events.created_by');
//qb.where() //if you wanted
//qb.andWhere(); //and if you wanted more
qb.limit(10);
})
.fetchAll();
(you dont need the .where or .andWhere, just added those for fun)
It might be possible to do it purely in bookshelf, but i'm not sure of how at the moment.
I suppose you found the solution since 2016 but here is the answer
var CalendarEvent = bookshelf.Model.extend({
tableName: 'calendar_events',
hasTimestamps: ['created_on'],
user: function() {
return this.belongsTo(UserAccount, 'user_id', 'created_by');
}
});
var UserAccount = bookshelf.Model.extend({
tableName: 'user_account'
});
Use belongsTo() method because the foreign key is in the CalendarEvent Model and not in the UserAccount model. Append the name of the foreign key to belongsTo() parameters to specify the name of the foreign key.
Then, use the Bookshelf model like this :
CalendarEvent
.forge()
.fetchPage({ pageSize: 10, withRelated: ['user'] })
.then(data => {
// Data retrieval
})
.catch(exc => {
// Error management
});
The fetchPage method uses page and pageSize to create a pagination. You will retrieve a data.pagination object containing rows info (number to rows, page number, page size, page total count) and data.toJSON() will retrieve your models.

How to add additional fields to user documents (not in profile)?

I know that the classic way to add data to user collection is in profile array, but according to this document, it is not the best way to store data.
Is there an alternative to that, for example to create a field in the root of user collection at the same level with default fields (_id, username, etc.)?
There is nothing wrong per-se with the profile field, other than the fact that a users can (currently) directly update their own profile by default.
I don't find this behavior desired, as a user could store arbitrary data in the profile.
This may become a real security risk if the developer uses that field as a source of authority; for example, stores the user's groups or roles in it.
In this case, users could set their own permissions and roles.
This is caused by this code:
users.allow({
// clients can modify the profile field of their own document, and
// nothing else.
update: function (userId, user, fields, modifier) {
// make sure it is our record
if (user._id !== userId)
return false;
// user can only modify the 'profile' field. sets to multiple
// sub-keys (eg profile.foo and profile.bar) are merged into entry
// in the fields list.
if (fields.length !== 1 || fields[0] !== 'profile')
return false;
return true;
}
});
The first thing to do is to restrict writes to it:
Meteor.users.deny({
update() {
return true;
}
});
It could then be updated using methods and other authorized code.
If you add your own fields and want to publish them to the currently logged-in user, you can do so by using an automatic publication:
Meteor.publish(null, function () {
if (this.userId) {
return Meteor.users.find({
_id: this.userId
}, {
fields: {
yourCustomField1: 1,
yourCustomField2: 1
}
});
} else {
return this.ready();
}
});
Meteor.users is just a normal Mongo.Collection, so modifying it is done just like any other Collection. There is also the creation hook, Accounts.onCreateUser which allows you to add custom data to the user object when it is first created, as mentioned in #MatthiasEckhart's answer.
You could add extra fields to user documents via the accountsServer.onCreateUser(func) function.
For example:
if (Meteor.isServer) {
Accounts.onCreateUser(function(options, user) {
_.extend(user, {
myValue: "value",
myArray: [],
myObject: {
key: "value"
}
});
});
}
Please note: By default, the following Meteor.users fields are published to the client username, emails and profile. As a consequence, you need to publish any additional fields.
For instance:
if (Meteor.isServer) {
Meteor.publish("user", function() {
if (this.userId) return Meteor.users.find({
_id: this.userId
}, {
fields: {
'myValue': 1,
'myArray': 1,
'myObject': 1
}
});
else this.ready();
});
}
if (Meteor.isClient) {
Meteor.subscribe("user");
}

Migrate simple List Array key to another key with an extra attribute in MongoDB

Sorry if I'm not getting the terminology right. Here's what I have currently my MongoDB user docs db.users:
"liked" : [
"EBMKgrD4DjZxkxvfY",
"WJzAEF5EKB5aaHWC7",
"beNdpXhYLnKygD3yd",
"RHP3hngma9bhXJQ2g",
"vN7uZ2d6FSfzYJLmm",
"NaqAsFmMmnhqNbqbG",
"EqWEY3qkeJYQscuZJ",
"6wsrFW5pFdnQfoWMs",
"W4NmGXyha8kpnJ2bD",
"8x5NWZiwGq5NWDRZX",
"Qu8CSXveQxdYbyoTa",
"yLLccTvcnZ3D3phAs",
"Kk36iXMHwxXNmgufj",
"dRzdeFAK28aKg3gEX",
"27etCj4zbrKhFWzGS",
"Hk2YpqgwRM4QCgsLv",
"BJwYWumwkc8XhMMYn",
"5CeN95hYZNK5uzR9o"
],
And I am trying to migrate them to a new key that also captures the time that a user liked the post
"liked_time" : [
{
"postId" : "5CeN95hYZNK5uzR9o",
"likedAt" : ISODate("2015-09-23T08:05:51.957Z")
}
],
I am wondering if it might be possible to simply do this within the MongoDB Shell with a command that iterates over each user doc and then iterates over the liked array and then updates and $push the new postId and time.
Or would it be better to do this in JavaScript. I am using Meteor.
I almost got it working for individual users. But want to know if I could do all users at once.
var user = Meteor.users.findOne({username:"atestuser"});
var userLiked = user.liked;
userLiked.forEach(function(entry) {
Meteor.users.update({ username: "atestuser" },
{ $push: { liked_times: { postId: entry, likedAt: new Date() }}});
console.log(entry);
});
Still a bit of a newbie to MongoDB obviously......
Here is something i made real quick you should run this on the server side just put it into a file e.g. "migrate.js" in root meteor and run the meteor app
if (Meteor.isServer) {
Meteor.startup(function () {
var users = Meteor.users.find().fetch();
users.forEach(function (doc) {
liked.forEach(function (postId) {
Meteor.users.update(doc._id, { $push: { liked_times: { postId: postId, likedAt: new Date() } } });
});
});
console.log('finished migrating');
});
}
p.s I didn't test it
If this is a one time migration i would do something like this in a one time js script.
Get all users
Iterate over each user
Get all likes
Iterate over them, get likedAt
var liked_times = _.collect(likes, function (likeId) {
return {
'postId' : likeId,
'likedAt': // get post liked time from like id.
}
});
Insert the above in the collection of choice.
Note:
The above example makes use of lodash
I would rather just save likedAt as a timestamp.

Categories