How to create a likes and comment system in PostgreSQL? - node.js

Context: I'm in an apprenticeship at Openclassrooms.com (Web dev path), and I'm doing the last project: I have to create a social network website from scratch, for the front-end I must use a framework of my choice (Vue.js, React, Angular, Svelte...) and for the back-end I must use an SQL database (like MySQL, PostgreSQL, Oracle, SQLite...).
For the Front-end, I'm using Angular.
For the Back-end, I'm using Node.js + Express + Sequelize
For the Database, I chose PostgreSQL
I did almost all the routes for the signup, login, view all posts and do CRUD operations with a single post.
But I'm stuck with the post controller for the likes and comments.
In the previous project of my apprenticeship, I had to create the Back-end where I had to do almost the same thing the posts were without comments), at the exception that I used Mongoose to make CRUD operations in MongoDB
The user had this model:
const mongoose = require("mongoose");
const sauceSchema = mongoose.Schema({
userId: { type: String, required: true },
name: { type: String, required: true },
manufacturer: { type: String, required: true },
description: { type: String, required: true },
mainPepper: { type: String, required: true },
imageUrl: { type: String, required: true },
heat: { type: Number, required: true, minimum: 1, maximum: 10 },
likes: { type: Number, required: true },
dislikes: { type: Number, required: true },
usersLiked: { type: Array, required: true },
usersDisliked: { type: Array, required: true },
});
module.exports = mongoose.model("Sauce", sauceSchema);
So here's how the likes for the previous project with MongoDB + Mongoose worked:
When the user liked a post, the front-end would send the userId (string) of the person who liked + like (integer between -1 and 1) + post_id (string) to the back-end
We search for the post in the database, and if it's present in the database, then we'll manipulate the likes/dislikes
Switch case 1: the like = 1/-1 → is the like/dislike present in the array of usersLiked/usersDisliked?
If it is → we remove the userId from the usersLiked/usersDisliked
If not → we add the userId in the usersLiked/usersDisliked array
And the amount of likes/dislikes is the length of the array
case 2: the like = 0 (meaning the user removes the like/dislike of the post) → Is the userId present in the usersLiked array?
If it is → we remove the userId from the usersLiked
If not → we remove the userId from the usersDisiked
Here's the code btw:
const findIndexInArray = require("../utils/likeSauce-function");
exports.likeSauce = (req, res, next) => {
Sauce.findOne({
_id: req.params.id,
})
.then((sauce) => {
let userId = req.body.userId;
let numberOfLikes = sauce.likes;
let numberOfDislikes = sauce.dislikes;
let usersLikedArray = sauce.usersLiked;
let usersDislikedArray = sauce.usersDisliked;
let userLikedOrDisliked = req.body.like;
console.log("Valeur de likes dans B2D: ", userLikedOrDisliked);
switch (userLikedOrDisliked) {
case 1:
{
//Où on like
let indexFound = findIndexInArray(usersLikedArray, userId);
if (indexFound > -1) {
console.log(
"User ID: " +
userId +
" found in the usersLikedArray → Like cancelled"
);
usersLikedArray.splice(indexFound, 1);
} else {
console.log(
"User ID: " +
userId +
" has NOT been found in the array of userIDs → Like added"
);
usersLikedArray.push(userId);
}
numberOfLikes = usersLikedArray.length;
let sauceObject = {
...JSON.parse(JSON.stringify(sauce)),
usersLiked: usersLikedArray,
likes: numberOfLikes,
};
Sauce.updateOne({ _id: req.params.id }, {...sauceObject, _id: req.params.id })
.then(() =>
res
.status(200)
.json({ message: "Sauce Object SUCCESSFULLY modified !" })
)
.catch((error) => res.status(400).json({ error }));
break;
}
//
case -1:
{
//Où on like
let indexFound = findIndexInArray(usersDislikedArray, userId);
if (indexFound > -1) {
console.log(
"User ID: " +
userId +
" found in the usersLikedArray → Like cancelled"
);
usersDislikedArray.splice(indexFound, 1);
} else {
console.log(
"User ID: " +
userId +
" has NOT been found in the array of userIDs → Like added"
);
usersDislikedArray.push(userId);
}
numberOfDislikes = usersDislikedArray.length;
let sauceObject = {
...JSON.parse(JSON.stringify(sauce)),
usersDisliked: usersDislikedArray,
dislikes: numberOfDislikes,
};
Sauce.updateOne({ _id: req.params.id }, {...sauceObject, _id: req.params.id })
.then(() =>
res
.status(200)
.json({ message: "Sauce Object SUCCESSFULLY modified !" })
)
.catch((error) => res.status(400).json({ error }));
break;
}
case 0:
{
let index = findIndexInArray(usersLikedArray, userId);
if (index > -1) {
usersLikedArray.splice(index, 1);
numberOfLikes = usersLikedArray.length;
let sauceObject = {
...JSON.parse(JSON.stringify(sauce)),
usersLiked: usersLikedArray,
likes: numberOfLikes,
};
Sauce.updateOne({ _id: req.params.id }, {...sauceObject, _id: req.params.id })
.then(() =>
res.status(200).json({
message: "Sauce Object SUCCESSFULLY liked !",
})
)
.catch((error) => res.status(400).json({ error }));
} else {
index = findIndexInArray(usersDislikedArray, userId);
if (index > -1) {
usersDislikedArray.splice(index, 1);
numberOfDislikes = usersDislikedArray.length;
let sauceObject = {
...JSON.parse(JSON.stringify(sauce)),
usersDisliked: usersDislikedArray,
dislikes: numberOfDislikes,
};
Sauce.updateOne({ _id: req.params.id }, {...sauceObject, _id: req.params.id })
.then(() =>
res.status(200).json({
message: "Sauce Object SUCCESSFULLY disliked !",
})
)
.catch((error) => res.status(400).json({ error }));
}
}
break;
}
default: //Pas de likes/dislikes AJOUTÉS par défaut
break;
}
})
.catch((error) => {
console.log("Error while attempting to find the sauce: " + error);
res.status(404).json({ error });
});
};
So that's the algorithm with the likes/dislikes of a post with MongoDB + Mongoose,
It's a rather simple implementation...
But how do I implement the like/dislike feature with PostgreSQL + Sequelize?
At first, I thought that I had to add a likes attribute to the table post, but that's wrong, I actually had to create a table for the likes and comments
So for the likes table:
-1 like_id attribute as the primary key
-2 other attributes as foreign keys (user_id and post_id)
For the comments table:
-1 comment_id attribute as the primary key
-3 other attributes as foreign keys (user_id, post_id and like_id)
Here also are the Sequelize models for each feature:
module.exports = (sequelize, Sequelize) => {
const Like = sequelize.define("like", {
like_id: {
type: Sequelize.INTEGER,
allowNull: false,
unique: true,
autoIncrement: true,
primaryKey: true,
},
user_id: {
type: Sequelize.STRING,
allowNull: false,
// references: "user", //Nom de notre table
// referencesKey: "user_id", //L'attribut référencé de la PK ce cette table
},
post_id: {
type: Sequelize.STRING,
allowNull: false,
// references: "post", //Nom de notre table
// referencesKey: "post_id", //L'attribut référencé de la PK ce cette table
},
});
return Like;
};
module.exports = (sequelize, Sequelize) => {
const Comment = sequelize.define("comment", {
comment_id: {
type: Sequelize.INTEGER,
allowNull: false,
unique: true,
autoIncrement: true,
primaryKey: true,
},
user_id: {
type: Sequelize.INTEGER,
allowNull: false,
references: "user",
referencesKey: "user_id",
},
post_id: {
type: Sequelize.INTEGER,
allowNull: false,
references: "post",
referencesKey: "post_id",
},
likes_id: {
type: Sequelize.INTEGER,
allowNull: false,
references: "likes", //Nom de notre table
referencesKey: "likes_id",
},
});
return Comment;
};
For the controller, I just have no idea as to how to do it, I looked at some posts but nothing very satisfying, if anyone could enlighten me, even just by giving me a hint, I'd be very grateful.
Also, here's an Entity Relationship Diagram to show all the tables I created:

I try this with mongoose but i hope with sequelize you can adjust to fit your request. I hope this can help you if there's any problem you can fix this comment and let me know.
export const commentPost = async (req, res) => {
try {
const { id } = req.params;
const { comment } = req.body;
if (!mongoose.Types.ObjectId.isValid(id)) throw new Error(`No post with id: ${id}`);
const post = await PostMessage.findById(id);
if (!post) throw new Error(`No post found with id: ${id}`);
post.comments.push({ text: comment });
const updatedPost = await post.save();
res.json(updatedPost);
} catch (err) {
res.status(404).send(err.message);
}
}

Related

API for updating user role using a id - Node

I am trying to create a node API and test it using Postman but I keep running into errors. I can't seem to get to update the userRole of my user table. I have tried console.log(req.body.userRole) to print the incoming field I want to update the user role but it is coming up as undefined or throwing an error relating to my Cors settings / unhandledPromiseRejection error when I remove the try catch loop.
In the code below I get a message that it is updated but nothing happens to the value of the table. The userRole is the 7th column of my table and it is spelt correctly. There is a foreign key attached to the userRole table that i'm not sure is causing the error.
It is likely an error in the code as i haven't many update functions working correctly yet.
exports.updateUser = async (req, res) => {
try{
const userID = req.params.userId;
//const role = req.body.userRole;
const user = await User.findByPk(userID);
//console.log("userRole", req.body.userRole)
if(!user){
// return a response to client
res.status(404).json({
message: "Not Found for updating a user with id = " + user,
user: "",
error: "404"
});
} else {
console.log("userRole", req.body.userRole)
// update new change to database
let updatedUser = {
userID: userID,
//userRole: role
}
let userRes = user.update(updatedUser, {
returning: true,
where: {userID: userID}
});
// return the response to client
if(!userRes) {
res.status(500).json({
message: "Error -> Can not update a user with username = " + req.params.userId,
error: "Can't Update",
});
}
res.status(200).json({
message: "Updated role successfully user = " + req.params.userId,
user: updatedUser,
});
}
} catch(error){
res.status(500).json({
message: "Error -> Can not update a customer with userName = " + req.params.userId,
error: error.message
});
}
}
model
module.exports = (sequelize, Sequelize) => {
const userLogin = sequelize.define("userLogins", {
userID: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true,
allowNull: false
},
username: {
type: Sequelize.TEXT
},
password: {
type: Sequelize.TEXT
},
userEmail: {
type: Sequelize.TEXT
},
userFirstName: {
type: Sequelize.TEXT
},
userSurname: {
type: Sequelize.TEXT
},
userRole: {
type: Sequelize.INTEGER
},
photos: {
type: Sequelize.TEXT,
}},
{
timestamps: false
})
userLogin.associate = function(models){
userLogin.userRole.hasOne(models.userRole, {foreignKey: 'userRoleID', as: 'userRoleID'})
};
return userLogin;
};
role model
module.exports = (sequelize, Sequelize) => {
const userRole = sequelize.define("userRoles", {
userRoleID: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true,
allowNull: false,
},
userRoleName: {
type: Sequelize.TEXT
}},
{
timestamps: false
},{});
return userRole;
};
example:
model: User
fields:
Id ( PrimaryKey )
RoleId ( Foreign Key )
model: Role
fields:
Id ( PrimaryKey )
Association in User model is like This (if a user just have a role):
User.belongsTo(models.Role);
If you change default scheme then you have to determine it:
model: User
fields:
Id ( PrimaryKey )
i_am_reference_iD ( Foreign Key to Role ID)
User.belongsTo(models.Role, {foreignKey: 'i_am_reference_iD'});
Sequelize has a complete documentation. You need to study it to avoid mistakes which they wasting too much time on dev time.
Also you need to clean your codes too.

Get data from main table by querying sub table and main table

I've got table product and sub table series in one too many relation
const Product = sequelize.define('product', {
id: {type: Sequelize.INTEGER, autoIncrement: true, allowNull: false, primaryKey: true},
order: Sequelize.STRING
})
const Series = sequelize.define('series', {
id: {type: Sequelize.INTEGER, autoIncrement: true, allowNull: false, primaryKey: true},
name: Sequelize.STRING,
})
Relation
Product.belongsTo(Series);
Series.hasMany(Product);
I want to get all products where series name is some text and product order name is some text.
exports.searchProduct = async (req, res, next) => {
const name = req.body.name || ''
const order = req.body.order || ''
try {
const series = await Series.findAll({
where: {
name: {[Op.like]: '%' + name + '%'}}
});
const products = await series.getProducts //<= get stuck here
res.status(200)
.json({result: products})
}
catch (err) {
if (!err.statusCode) {
err.statusCode = 500
}
next(err);
}
}
I managed to query series but get stuck to query product result with order name. So how can I query product result with order name? I tried to add where clause in getProducts but that come up with error getProducts is not a function
How about this
cosnt products = await Products.findAll({
where: {
order: {[Op.like]: '%' + order + '%'}
},
include: [
{
model: Series,
where: {
name: {[Op.like]: '%' + name + '%'}
},
required: true
}
]
})

Is there a way i could keep track of the Time and the entity that was changed in a model

Basically I'm trying to get the time and the entity changed in a particular model when ever the update method is called.
This is my model I want to keep track of:
const mongoose = require("mongoose");
const modelSchema = mongoose.Schema({
user: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
},
name: {
type: String,
required: true,
},
note1: String,
note2: String,
note3: String,
images: {
type: Array,
required: true
},
status: {
enum: ['draft', 'pending_quote', 'pendong_payment', 'in_production', 'in_repair', 'pemding_my_review', 'fulfilled'],
type: String,
default: "draft"
},
price: {
type: mongoose.Schema.Types.ObjectId,
ref: "Price",
}
}, {
timestamps: true,
})
module.exports = mongoose.model("Model", modelSchema)
And this is the method I call to update the status:
exports.updateModel = async (req, res) => {
try {
let id = req.params.id;
let response = await Model.findByIdAndUpdate(id, req.body, {
new: true
})
res.status(200).json({
status: "Success",
data: response
})
} catch (err) {
res.status(500).json({
error: err,
msg: "Something Went Wrong"
})
}
}
you can add a new field in your schema like:
logs:[{
entity: String,
timeStamp: Date
}]
Then updating it basing on your current code:
let id = req.params.id;
// I don't know whats in the req.body but assuming that it
// has the correct structure when passed from the front end
let response = await Model.findByIdAndUpdate(id,
{
$set:req.body,
$push:{logs:{entity:'your entity name here',timeStamp:new Date()}}
}, {
new: true
})

Mongoose: how to only populate, sort and return a nested object?

I have a User schema, with a messages array. The message array is filled by conversations id and referenced to a Conversation schema.
I want to fetch all conversations from a user, sort them by unread and then most recent messages. Finally, I must only return an array of lastMessage object.
For the moment, I have only managed to populate the whole user object.
Here is the Conversation Schema:
const conversationSchema = new mongoose.Schema(
{
name: { type: String, required: true, unique: true },
messages: [{ message: { type: String }, authorId: { type: String } }],
lastMessage: {
authorId: { type: String },
snippet: { type: String },
read: { type: Boolean },
},
},
{ timestamps: true }
);
conversationSchema.index({ name: 1 });
module.exports = mongoose.model("Conversation", conversationSchema);
And here is my code:
router.get("/conversations", async (req, res) => {
try {
const { userId } = req.query;
const user = await User.findById({ _id: userId }).populate("messages");
.sort({ updatedAt: 1, "lastMessage.read": 1 });
return res.json({ messages: user.messages });
} catch (err) {
console.log("error", err);
return res.json({ errorType: "unread-messages-list" });
}
});
How to do this?

Mongoose unique property still allowing me to save to db

Im under the assumption that adding unique: true to a field would stop from saving to the database using the same value. But im still allowed to do it.
"mongoose": "^5.4.19",
const SessionSchema = new Schema({
jobId: {
type: String,
required: false,
unique: true,
index: true,
},
productId: {
type: String,
required: true,
},
status: {
type: String,
default: "Pending",
},
mode: {
type: String,
default: "authentication",
},
result: {
type: Schema.Types.Mixed,
},
requests: [RequestSchema],
callback_at: {
type: Date,
},
}, {
timestamps: { createdAt: "created_at", updatedAt: "updated_at" },
});
I have already tried deleting and recreating the collection. See the image below i can create new session with the same jobId being 1.
public store = async (req: Request, res: Response): Promise<any> => {
const input = req.body;
let session = new Session({
productId: input.productId,
jobId: input.jobId,
});
try {
session = await session.save();
const response = {
success: true,
status: 201,
data: { session },
message: "SESSION CREATED",
};
return res.status(response.status).json(response);
} catch (err) {
const response = {
success: false,
status: 500,
errors: [],
message: "UNEXPECTED SESSION ERROR",
};
if (err.code === 11000) {
response.errors.push({
code: 11000,
message: "Duplicate key error jobId",
});
}
return res.status(response.status).json(response);
}
db.sessions.getIndex();
[
{
"v" : 2,
"key" : {
"_id" : 1
},
"name" : "_id_",
"ns" : "myDB.sessions"
}
]
You have to understand that unique is an index configuration option in your schema.
For instance, if the users collection doesn't have a unique index on userName, then you need to wait for the index to build before you start relying on it.
const user = new mongoose.Schema({
userName: { type: 'String', unique: true },
});
const User = db.model('User', user);
const doc = new User({
userName: 'Bob'
});
return User.init() // `User.init()` returns a promise that is fulfilled when all indexes are done
.then(() => User.create(doc))
.then(() => User.create({ userName: 'Bob' }));
}
I was not using unique properly: https://mongoosejs.com/docs/validation.html#the-unique-option-is-not-a-validator
Need to wait until the indexes are built before relying on unique to be a validator.
I changed my mongoose connect options to look like the following
options: {
useNewUrlParser: true,
useCreateIndex: true,
autoIndex: true,
},
I;m not sure if its the most appropriate solution, but its the one ive gone with for now.

Resources