I'm trying to count the number of comments made in a specific post but I am getting an undefined error...
The post and comments are in the mongodb.
The current Error I get in the console:
{stringValue: '"undefined"', valueType: 'string', kind: 'ObjectId', value: 'undefined', path: 'post', …}
My current code in the server-side:
//Get all amount of comments of the individual post
app.get("/:postId/comments/all", async (req, res) => {
try {
const comments = await Comment.countDocuments({
post: req.params.postId,
});
res.send(comments);
} catch (err) {
res.send({ error: err });
}
});
My current code in the client-side:
//Get comments quantity from the server
const getAllComments = async () => {
const res = await axios.get(`/api/posts/${postId}/comments/all`);
if(res.data.error){
setComments("");
console.log(res.data.error);
} else {
setComments(res.data);
}
};
Comment Schema:
const Comment = new mongoose.Schema({
post: {
type: Schema.Types.ObjectId,
ref: "post",
},
What I've tried:
I've tried looking different examples on how it's done but failed.
I tried using .find(postid).count() and send it to the front-end.
It appears to be that no matter what I do, it returns me undefined.
I've been trying to play around with it but had no luck.
I'd appreciate your support on this problem.
I was trying to use .countDocument() in the wrong route that had no :postId in there.
I added in my post Schema:
commentsCount: {
type: Number,
default: 0,
},
And then when a client submits a comment to a specific post, in server-side I increment the 'commentsCount' by one and then I render it on a specific post when the page loads.
Related
I'm creating a rest api for CRUD operations using Sequelize and MySql. I'm using a controller to run an update on a PATCH request to update fields of a product. It technically works, but I feel like there is a more elegant way to handle this.
Sequelize's update method will return an array of objects depending on the results. Array[0] is the number of rows affected by the update (should just be one in my case, as I'm updating by id). Array[1] will return an object with details about the update as well as all the old values and new values. Here's how I'm handling that currently:
//products.controller.js
//Update a single product using id (PUT/PATCH)
const patch = (req, res) => {
const id = req.params.id;
Product.update(req.body, { where: { id }, individualHooks: true })
.then((rowsAffected) => {
//Item not found
if (Object.entries(rowsAffected[1]).length === 0) {
res.status(404).send({
success: false,
status: 404, //Not found
message: `Product with id ${id} not found. Update failed.`,
});
return;
}
//if rowsAffected[0] === 1 then success
if (rowsAffected[0] === 1) { //row changed
res.status(200).send({
success: true,
status: 200,
message: `Product updated.`,
id: id,
payload: req.body,
});
} else {
// if rowsAffected[0] !== 1 then it failed.
res.status(200).send({
success: false,
status: 200, //Not Modified
message: `No fields have changed. Product not updated.`,
});
}
})
.catch((err) => {
res.status(500).send({
success: false,
status: 500,
message:
err.message || "Something went wrong while updating the product.",
});
});
}
As you can see, first I'm checking to see if the the update function returns the product details (meaning it successfully found it in the database). If not then sending 404. Then I check the affected rows. If 1 then success, if 0 then nothing changed. Finally I'm catching any server errors.
I feel like there is a better way rather than having to break down the update function's return (like Object.entries(rowsAffected[1]).length === 0)
This is ok if this is the only way you can check the effects of the update. What I can suggest is putting an abstraction above it.
First thing that checking (rowsAffected[0] === 1) does not make much sense, since the update is idempotent and you end up with the same resource state no matter what the actual values are. If you insist, then I would not pair success: false with a 200 ok status, because failure is failure and it requires an error message and 4xx or 5xx status. So either delete it or convert it into a proper error. Hard to find such a status code, but maybe using 409 conflict is ok in these cases https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409 though I would just remove this part of the code. I keep it for the sake of the example.
As of the success and status properties in the body, they don't make much sense either, because they travel in the header, and it is evident from the HTTP standard that 2xx means success, 4xx and 5xx means error. So I would remove those too.
If you don't want to support detailed error codes and exception types and parameters, then just send the error messages and the body can be even a string instead of an object.
Sending the err.message to the consumers is a bad idea by unexpected errors. You don't know what you send out. You need to log them and send something general instead. Communicating errors is always a higher abstraction level stuff, many times. As of the Product with id ${id} not found. Update failed. here adding the id is not necessary, because the request contains it.
So atm. the code looks like this:
const patch = (req, res) => {
const id = req.params.id;
Product.update(req.body, { where: { id }, individualHooks: true })
.then((rowsAffected) => {
if (Object.entries(rowsAffected[1]).length === 0) {
res.status(404).send({message: `Product not found. Update failed.`});
return;
}
//if rowsAffected[0] === 1 then success
if (rowsAffected[0] === 1) { //row changed
res.status(200).send({
message: `Product updated.`,
id: id,
payload: req.body,
});
} else {
res.status(409).send({message: "No fields have changed. Product not updated."});
}
})
.catch((err) => {
res.status(500).send({message: "Something went wrong while updating the product."});
});
}
We can go further by mapping status codes to status messages and extracting the possibly repeating parts of the story into separate functions.
const patch = (req, res) => {
const id = req.params.id;
const statusMessages = {
200: "Product updated."
404: "Product not found. Update failed."
409: "No fields have changed. Product not updated.",
500: "Something went wrong while updating the product."
};
Product.update(req.body, { where: { id }, individualHooks: true })
.then(updateStatusVerification)
.then(successHandler(res, statusMessages, () => {
return {
id: id,
payload: req.body,
};
}))
.catch(apiErrorHandler(res, statusMessages));
}
function successHandler(res, statusMessages, callback){
return function (){
let body = callback();
body.message = statusMessages[200];
res.status(200).send(body);
};
}
function apiErrorHandler(res, statusMessages){
return function (err){
let statusCode = 500;
if (err instanceof NotFoundError)
statusCode = 404;
else if (err instanceof NotUpdatedError)
statusCode = 409;
res.status(statusCode).send({
message: statusMessages[statusCode]
});
};
}
function updateStatusVerification(rowsAffected){
return new Promise((resolve, reject) => {
if (Object.entries(rowsAffected[1]).length === 0)
reject(new NotFoundError);
else if (rowsAffected[0] !== 1)
reject(new NotUpdatedError);
else
resolve();
});
}
class ApiError extends Error {}
class NotFoundError extends ApiError {}
class NotUpdatedError extends ApiError {}
We can move the status messages to the documentation. So you will end up with something like this and some utility functions:
const patch = (req, res) => {
const id = req.params.id;
statusMessages = docs.product.update.statusMessages;
Product.update(req.body, { where: { id }, individualHooks: true })
.then(updateStatusVerification)
.then(successHandler(res, statusMessages, () => {
return {
id: id,
payload: req.body,
};
}))
.catch(apiErrorHandler(res, statusMessages));
}
We can go even further if this is a frequent pattern:
const patch = (req, res) => {
const id = req.params.id;
handleUpdate(
Product.update(req.body, { where: { id }, individualHooks: true }),
() => {id: id, payload: req.body},
docs.product.update.statusMessages
);
}
function handleUpdate(dbUpdatePromise, successCallback, statusMessages){
dbUpdatePromise.then(updateStatusVerification)
.then(successHandler(res, statusMessages, successCallback))
.catch(apiErrorHandler(res, statusMessages));
}
So it can be as abstract as you like, it really depends on your needs and what the current usage allows. You can decide how many and what kind of layers you need based on actual use cases and repetitions.
I am trying to create a small ecommerce website where I need to have a cart page. I need to make it so when a user clicks on Add to cart button, the particular item will get sent to the backend and stored in the cart collection.
What I'm having trouble with is that when I click the add to card button and the function linked to it runs, I get an error stating : SyntaxError: Unexpected token " in JSON at position 0
Here's my function which is supposed to send data to the backend:
let addToCart = async (item) => {
// item is the object I get which contains all the information I need
try {
let product = item._id;
await fetch('http://localhost:5000/cart', {
method: 'post',
body: product,
headers: {
'Content-Type': 'application/json',
},
});
} catch (e) {
console.log({error : e});
}
};
This is my cart model :
const cartSchema = new mongoose.Schema(
{
userId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'users',
required: true,
},
cartProducts: {
type: Array
},
},
{
versionKey: false,
timestamps: true,
}
);
This is my cart controller POST CRUD
router.post('', authenticate, async (req, res) => {
const user = req.user;
console.log(user);`
try {
const cart = await Cart.findOneAndUpdate(
{ userId: user._id },
{
$push: {
cartProducts: req.body,
},
},
{ new: true, upsert: true }
);
return res.status(200).send(cart);
} catch (err) {
return res.status(400).send({ message: err.message });
}
});
I am able to get user using authenticate middleware.
I can post everything using Postman but I can't figure how to get rid of the Syntax error.
When I console.log typeof item, I get an object. Product's type is string. I have also tried using JSON.stringify but nothing seems to be working. I have also tried using bodyparser middleware.
So, I'm trying to set up a simple economy system in Discord.js using MongoDB. I first made a single database for just money. I also set up a command to mock the addition of currency to a user. It worked perfectly fine. Only when I tried to add a second, duplicate system with a different name, schema, and variables etc. did I run into issues. I set up the other database perfectly fine, and it actually worked really well. Then I tried to set up a command to mock adding value to that database, and upon using the command I'm presented with this error UnhandledPromiseRejectionWarning: CastError: Cast to number failed for value "undefined" at path "stars"(the second database is called stars). Due to some helpful console logs I implemented I think I tracked the issue to whenever the code hits this:
const starresult = await profileSchema.findOneAndUpdate({
guildId,
userId
}, {
guildId,
userId,
$inc: {
stars
}
}, {
upsert: true,
new: true
})
This code is exactly the same as I used for the first currency except with all the proper variables and stuff like that. Let me know if you need to see any more code. Thanks in advance!
EDIT: Here's some code related to stars
The whole related export:
const mongo = require('./mongo')
const profileSchema = require('./schemas/profile-schema')
const starsCache = {}
module.exports = (client) => {}
module.exports.addStars = async (guildId, userId, coins, stars) => {
return await mongo().then(async (mongoose) => {
try {
console.log('Running findOneAndUpdate()')
const starresult = await profileSchema.findOneAndUpdate({
guildId,
userId
}, {
guildId,
userId,
$inc: {
stars
}
}, {
upsert: true,
new: true
})
console.log('RESULT:', starresult)
starsCache[`${guildId}-${userId}`] = starresult.stars
return starresult.stars
} finally {
mongoose.connection.close()
}
})
}
A section of the profile schema:
const profileSchema = mongoose.Schema({
guildId: reqString,
userId: reqString,
stars: {
type: Number,
required: true
},
coins: {
type: Number,
required: true
},
})
The error is telling you what you need to know. You're trying to increment by amount stars, but stars is undefined. You'll want to look further up in your code to find out why stars doesn't have a numeric value.
I'm trying to implement a voting system so users of my app can vote on other users responses to a question.
It is a Node app using Express and Sequelize on the back-end, for the moment I'm using a SQLite database for ease of testing.
Here's the relevant parts of my models:
// in user.js
User.associate = function (models) {
// ...
models.User.belongsToMany(models.Response, {
through: models.Vote,
as: 'Votes'
})
}
// in response.js
Response.associate = function (models) {
// ...
models.Response.belongsToMany(models.User, {
through: models.Vote,
as: 'Votes'
})
}
// in vote.js
module.exports = (sequelize, DataTypes) => {
return sequelize.define('Vote', {
upVote: {
type: DataTypes.BOOLEAN,
allowNull: false
}
})
}
So now in my votes.js controller file I may try and create a new vote, given a UserId and a ResponseId as so:
const user = await User.findById(req.user.id)
const response = await Response.findById(req.params.responseId)
await response.addVote(user, {
upVote: true
})
res.send(await response.getVotes())
The problem is, although the Votes join table is created correctly, and although I am passing the addVote function a boolean directly indicating what value the upVote attribute should store, I just get an error stating that notNull Violation: Vote.upVote cannot be null.
The only reason I added the not null constraint to begin with was because without it, I just received a vote with a null value for upVote.
Does anyone have any idea why this might be happening?
I don't know exactly why, but replacing the response.addVote with this:
const vote = await Vote.create({
upVote: req.body.upVote,
UserId: req.user.id,
ResponseId: req.params.responseId
})
res.send(vote)
Works fine. Odd. I keep running into issues where the so-called 'magic' auto-generated functions that Sequelize is supposed to provide for relations either aren't available or don't work.
I am trying to build a blog API, and right now I have three fields in my schema:
const PostSchema = new Schema({
timestamp: {
type: Date,
default: Date.now
},
title: {
type: String,
required: [true, "Title is required"]
},
content: {
type: String,
required: [true, "Content is required"]
}
})
I also have createPost function, that is supposed to create a post (no shit):
// Create post
const createPost = (req, res, next) => {
const title = req.body.title
const content = req.body.content
console.log('body', req.body) // getting output
if (!title) {
res.status(422).json({ error: "Titel saknas!!!" })
}
if (!content) {
res.status(422).json({ error: "Skriv något för fan!" })
}
const post = new Post({
title,
content
})
post.save((err, post) => {
if (err) {
res.status(500).json({ err })
}
res.status(201).json({ post })
})
}
I have those two if statements to check if the title or the content is empty, but that is not working. I tried to send a POST request with Postman:
But the error says that my title is missing. But I am passing in my title key.
So I wonder why this is not working, it feels like some obvious stuff, but I just can't get this to work.
Thanks for reading.
I don't know Postman too well, but I'm going to guess that setting the body content type to raw uploads the body as text/plain, which means body-parser will not parse it in any way (console.log('body', typeof req.body) will show "body string").
Instead, try setting the content type to application/json (and make sure that your server uses the JSON middleware from body-parser).