Possible to enrich data in a collection? - getstream-io

As the title suggests, I'm wondering if it's possible to enrich data in a collection or if there is another recommended best practice to achieve the same result.
Consider the following example:
const user = await client.user(userId).get();
const object = await client.collections.add({
'Message',
messageId,
message: {
contents: 'Hello world!',
user // this fails with the error "Converting circular structure to JSON"
}
});
await feed.addActivity({
actor,
verb,
object
});
// I want the objects enriched in this request to also contain the enriched user object
await feed.get({ enrich: true });

Related

Best practice for validating input in Express/Mongoose app (controllers and models)

I'm new to node/express/mongoose and backend in general, and I'm building an API from the ground up.
I split my code into controllers and DAOs (mongoose models) and I'm not sure where validation should be taking place, what exactly should the controller and model each be doing?
For example, for the route GET /users/:id, I want it to:
Return 400 if the id given is not a valid ObjectId
Return 404 if the id is a valid ObjectId, but no documents exist with this id
Return 200 if a document is found, and remove some fields (password, __v, and _id (because I made a virtual field "id" without underscore)) before sending the response
Return 500 otherwise
Here's what I tried. It's currently doing everything I want above, but I'm not sure if it's the best implementation:
users.controller.js
const UserModel = require('../models/users.model')
const mongoose = require('mongoose')
exports.getById = async (req, res) => {
// Check that the id is a valid ObjectId
if (!mongoose.Types.ObjectId.isValid(req.params.id)) {
return res.status(400).json({ error: 'Invalid ObjectID' })
}
try {
const user = await UserModel.findById(req.params.id)
if (!user) return res.status(404).json({ error: 'No user with this ID' })
res.status(200).send(user)
} catch (err) {
res.status(500).send(err)
}
}
users.model.js
exports.findById = async (id) => {
let user = await User.findById(id)
if (!user) {
return null
}
user = user.toJSON()
delete user._id
delete user.__v
delete user.password
return user
}
Is this the best way to structure things? Please critique and suggest any improvements and best practices.
I prefer to do all validation and logic in Controllers file and helpers classes in separate small js files.
Also trying to keep the models as lean as possible. Actually you can create different middleware functions to validate some inputs.
It is also helpful to create ErrorResponse middleware so you can do such simple and easy to read validations as:
if (!userFromDB) throw new ErrorResponse({code: 404, msg: "User doesn't exist"})
than catch it and handle in separate file with different options of the answer.
Backend has 100+ ways of implementing it. You need to choose you own 😁

Prevent _id field from being supplied in a MongoDB query

I'm using Mongoose in NodeJS to control a MongoDB database.
I'm creating an API and for obvious security reasons, I want to prevent the auto generated document _id field from getting replaced by a manually generated one in the API request.
Schema:
{ name: String }
Creating a document:
const record = {
_id: '5e35517cc894c90327a34baf'
name: 'bob'
}
const insertRecords = async () => {
await Quiz.create(record);
};
insertRecords();
Results in the following document:
{
_id: '5e35517cc894c90327a34baf'
name: 'bob'
}
As can be seen, the _id supplied in the query, as long as it's a valid ObjectID, would replace the _id that was supposed to be auto generated by mongo.
Is there a way to check if this _id field is in the query so that I can reject the API request? The .create method triggers the pre save middleware hook which would always have the _id of the final document so I cannot depend on it to know whether the _id was in the query or it's the auto generated one.
The only option I found is to disable the _id field altogether but this does not make sense.
Solution #1 - Use .create() method with an explicit object.
It's actually easier than you think. This is self-explanatory - we only define what we want to allow. Mongoose will ignore anything that's not in the object.
const record = {
_id: '5e35517cc894c90327a34baf'
name: 'bob'
}
const insertRecords = async () => {
await Quiz.create({
name: record.name // only allow names.
});
};
insertRecords();
Solution #2 - Define a function to clear unwanted objects.
You can define a helper function to clear out unwanted fields.
const filterObj = (obj, ...allowedFields) => {
const newObject = {};
// If the current field is one of the allowed fields, keep them in the new object.
Object.keys(obj).forEach((el) => {
if (allowedFields.includes(el)) {
newObject[el] = obj[el];
}
});
return newObject;
};
How to use:
const filteredRecord = filterObj(record, 'name'); // arbitrary list of allowed fields. In this case, we'll only allow 'name'.
await Quiz.create(filteredRecord);

What is the best way to update different properties?

I'm building an API with Restify which is based on Express. I'm using Typeorm and I'm wondering what is the best way to update different properties which came from user input.
Essentially I have a route like this:
server.put('/users/:id', errorHandler(update));
which fire this method:
const update = async (req: Request, res: Response) => {
const user = { ...req.body, id: req.params.id } as User;
res.send(await userService.update(user));
}
as you can see I used the spread operator to create an User entity. Then, inside userService.update I have the following:
export const update = async (user: User): Promise<User> => {
const repository = getRepository(User);
const entity = await repository.findOne({ id: user.id });
if (!entity) throw new errors.ResourceNotFoundError(`There is no user with id of ${user.id}`);
Object.assign(entity, user, { id: entity.id, chat_id: entity.chat_id, project_id: entity.project_id, deleted: false });
return await repository.save(entity);
}
as you can see, I want prevent that the data provided by the API consumer will replace some important properties like: id, chat_id, project_id, deleted, so I used the method Object.assign to achieve this.
Is this a good way? What do you suggest for improve this?
You can use update method of typeorm like this, it will partially update the values that you give as a second argument.
// this will find a user with id ${user.id} and will only
// change the fields that is specified in the user object
await repository.update(user.id, user);
// check if updated for debugging
const updatedUser = await repository.findOne(user.id);
console.log(updatedUser, null, 2)
If you want to create a new record of the existing user in db, then you only need to change it's id. To do that
Deep clone the object so there will be another user object with new reference
Remove id field from the deep cloned object and use insert afterwards
// Deep clone user object
const clonedUser = JSON.parse(JSON.stringify(user))
// Delete id field from the deep clone
delete clonedUser.id;
// create a new user with different id
await repository.insert(clonedUser);
You can filter your important properties.
And pass the user id to the update method of your userService.
const { id, chat_id, project_id, deleted, ...user } = req.body;
const { id } = req.params;
res.send(await userService.update(id, user));
This will make sure user object don't have the properties(that is important).
And you can change your update method like below:
export const update = (userId: string, user: User): Promise<User> => {
return getRepository(User).update(userId, user);
}

Data is mutated but not updated in the database

I have a sever connected to a mongodb database. When I add a first level data and then save that, it works.
For example :
// this works fine
router.post('/user/addsomedata', async (req,res)=>{
try {
const user = await User.findOne({email : req.body.email})
user.username = req.body.username
await user.save()
res.send()
} catch(e) {
res.status(404).send(e)
}
})
BUT if I try to save the object with deeper level data, it's not getting saved. I guess the update is not detected and hence the user didn't get replaced.
Example :
router.post('/user/addtask', auth ,async (req,res)=>{
const task = new Task({
name : req.body.name,
timing : new Date(),
state : false,
})
try {
const day = await req.user.days.find((day)=> day.day == req.body.day)
// day is found with no problem
req.user.days[req.user.days.indexOf(day)].tasks.push(task)
// console.log(req.user) returns exactly the expected results
await req.user.save(function(error,res){
console.log(res)
// console.log(res) returns exactly the expected results with the data filled
// and the tasks array is populated
// but on the database there is nothing
})
res.status(201).send(req.user)
} catch(e) {
res.status(400).send(e)
}
})
So I get the tasks array populated on the console even after the save callback but nothing on the db image showing empty tasks array
You're working on the user from the request, while you should first find the user from the DB like in your first example (User.findOne) and then update and save that model.
Use .lean() with your find queries whenever you are about to update the results returned by mongoose. Mongoose by default return instance objects which are immutable by nature. lean() method with find returns normal js objects which can be modified/updated.
eg. of using lean()
const user = await User.findOne({email : req.body.email}).lean();
You can read more about lean here
Hope this helps :)

Map becomes empty after sending it through express params

I am using express and Discord.js.
website.js
const express = require("express");
const app = express();
//...
ap.get("/testpage/:guildID", checkAuth, (req, res) => {
const guild = client.guilds.get(req.params.guildID); //get the guild object which holds further object maps
//console.log(guild) output is expected with this console log
//I made the guildRoles since I thought maybe I had to send the roles map instead of the guild object, but it does not work either
const guildRoles = guild.roles;
//console.log(guildRoles) has expected output
renderTemplate(res, req, "configure.ejs", {guild, guildRoles});
};
configure.ejs
//...
<script>
//...
function testFunction() {
let guildRolesVar = <%- guildRoles %>;
console.log(guildRolesVar);
//this outputs an empty object, {}, when it should return {...} ... with data in it
//same with <%- guild %>, however, it does return data, however, object maps are empty (same as above), but things that are not maps
};
</script>
I expected that the object map data would pass over, but it doesn't, and I have no idea why.
A guild structure is:
Guild {
roles:
Collection [Map] {
'1111111111111110' => Role {
guild: [Circular],
id: '1111111111111110',
name: '#testRole' },
'1837219387129378' => Role {
guild: [Circular],
id: '1837219387129378',
name: '#anotherRole' } },
name: 'the guild name',
id: '12103981203980',
}
As you can see, the guild object has further object maps. When I console.log the guild object in my app.get(...), it outputs the full data with nothing missing. However, when I pass the object through, the roles object is empty ({}).
Why is this happening and how can I get the full data?
This is most likely happening because express will JSON stringify() them and because discord.js objects have circular references the will be {}.
A possible solution would be to use d.js master/v12, such allows JSON.stringify() on its objects.

Resources