API for updating user role using a id - Node - node.js

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.

Related

How to create a likes and comment system in PostgreSQL?

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);
}
}

beforeBulkDestroy not finding model property to change

I am trying to use the beforeBulkDestory Sequelize hook on a user delete that will switch the deleted column boolean to true prior to updating the record to add a timestamp for deleted_at. However, when I console.log the function parameter it provides a list of options and not the model object that I can update for the record of focus. Am I approaching this the wrong way? Is this something that should be set using model instances?
API Call:
import db from '../../../models/index';
const User = db.users;
export default (req, res) => {
const {
query: { id },
} = req
console.log(User)
if (req.method === 'DELETE') {
User.destroy({
where: {
id: id
}
}).then(data => {
res.json({
message: 'Account successfully deleted!'
})
})
} else {
const GET = User.findOne({
where: {
id: id
}
});
GET.then(data => {
res.json(data)
})
}
}
Parameter Values (beforeBulkDestroy, afterBulkDestroy):
beforeBulkDestroy
{
where: { id: '5bff3820-3910-44f0-9ec1-e68263c0f61f' },
hooks: true,
individualHooks: false,
force: false,
cascade: false,
restartIdentity: false,
type: 'BULKDELETE',
model: users
}
afterDestroy
{
where: { id: '5bff3820-3910-44f0-9ec1-e68263c0f61f' },
hooks: true,
individualHooks: true,
force: false,
cascade: false,
restartIdentity: false,
type: 'BULKUPDATE',
model: users
}
Model (users.js):
'use strict';
const Sequelize = require('sequelize');
const { Model } = require('sequelize');
const bcrypt = require("bcrypt");
module.exports = (sequelize, DataTypes) => {
class users extends Model {
/**
* Helper method for defining associations.
* This method is not a part of Sequelize lifecycle.
* The `models/index` file will call this method automatically.
*/
static associate(models) {
// define association here
}
};
users.init({
id: {
type: DataTypes.UUID,
defaultValue: Sequelize.UUIDV4,
primaryKey: true
},
first_name: DataTypes.STRING,
last_name: DataTypes.STRING,
password: {
type: DataTypes.STRING
},
email: DataTypes.STRING,
active: {
type: DataTypes.BOOLEAN,
defaultValue: true
},
deleted: {
type: DataTypes.BOOLEAN,
defaultValue: false
}
}, {
hooks: {
beforeDestroy: (user, options) => {
console.log("beforeDestroy")
console.log(user)
console.log(options)
user.deleted = true
}
},
sequelize,
freezeTableName: true,
modelName: 'users',
omitNull: true,
paranoid: true,
underscored: true,
createdAt: 'created_at',
updatedAt: 'updated_at',
deletedAt: 'deleted_at',
hooks: {
beforeCreate: async function(user){
console.log("beforeCreate")
console.log(user)
const salt = await bcrypt.genSalt(12);
user.password = await bcrypt.hash(user.password, salt);
console.log(user.password)
},
beforeBulkDestroy: async function(user){
console.log("beforeBulkDestroy")
console.log(user)
},
afterBulkDestroy: async function(user){
console.log("afterDestroy")
console.log(user)
}
}
});
users.prototype.validPassword = async function(password) {
console.log("validatePassword")
console.log(password)
return await bcrypt.compare(password, this.password);
}
return users;
};
the before/after bulkDestroy hooks only receive the options, not the instances. One way you could do this is defining a before/after Destroy hook:
hooks: {
beforeDestroy: (user, { transaction }) => {
user.update({ deleted: true }, { transaction });
}
}
and calling User.destroy with the individualHooks option:
User.destroy({ where: { id: id }, individualHooks: true });
Be aware that this will load all selected models into memory.
Docs
Note: In your case, since you're only deleting one record by id, it would be better to just user = User.findByPk(id) then user.destroy(). This would always invoke the hooks and it also makes sure the record you want to delete actually exists.
Note 2: Not sure why you need a deleted column, you could just use deletedAt and coerce it into a boolean (with a virtual field if you want to get fancy).

Sequelize (PostgresSQL) many to many chat implementation

I've been trying to create a chat app with Node JS and Sequelize. Now i'm stuck at a problem of creating a query to find a conversation that has my id and user's id(the one i'm trying to text). So the thing i'm trying to do is send a post request with and id of a user i'm sending a message to, then i look through my Conversation model and check if that conversation has my id and id of the user i'm texting to.
My models are associated through Many to Many relationship. So the main objective is to find a conversation with only my ID and ID of the user i'm texting to with the same ConversationId.
Here are my models:
User
module.exports = (sequelize, DataTypes) => {
const User = sequelize.define(
"User",
{
name: { type: DataTypes.STRING },
password: { type: DataTypes.STRING, allowNull: false },
username: { type: DataTypes.STRING, allowNull: false },
email: { type: DataTypes.STRING, allowNull: false },
},
{}
);
User.belongsToMany(models.Conversation, {
as: "conversations",
foreignKey: "user_id",
through: models.ConversationUsers,
});
User.hasMany(models.Message, {
as: "messages",
});
};
return User;
};
Conversation
module.exports = (sequelize, DataTypes) => {
const Conversation = sequelize.define(
"Conversation",
{
lastMessage: DataTypes.STRING,
recipients: DataTypes.ARRAY(DataTypes.INTEGER),
},
{
sequelize,
modelName: "Conversation",
}
);
Conversation.associate = (models) => {
Conversation.belongsToMany(models.User, {
as: "participants",
foreignKey: "conversation_id",
through: models.ConversationUsers,
});
Conversation.hasMany(models.Message, {
as: "messages",
});
};
return Conversation;
};
ConversationUsers Many to Many through model
"use strict";
module.exports = (sequelize, DataTypes) => {
const ConversationUsers = sequelize.define(
"ConversationUsers",
{
user_id: DataTypes.INTEGER,
conversation_id: DataTypes.INTEGER,
},
{
sequelize,
modelName: "ConversationUsers",
}
);
return ConversationUsers;
};
Message
module.exports = (sequelize, DataTypes) => {
const Message = sequelize.define(
"Message",
{
conversationId: { type: DataTypes.INTEGER, allowNull: false },
sentTo: DataTypes.INTEGER,
sentFrom: DataTypes.INTEGER,
body: { type: DataTypes.STRING, allowNull: false },
},
{
sequelize,
modelName: "Message",
}
);
Message.associate = (models) => {
Message.belongsTo(models.User, {
as: "messageTo",
foreignKey: "sentTo",
});
Message.belongsTo(models.User, {
as: "messageFrom",
foreignKey: "sentFrom",
});
Message.belongsTo(models.Conversation, {
as: "messages",
});
};
return Message;
};
I think you can remove some pieces from your models and rework it a bit.
Messages don't need a sentTo, they only need a sentFrom. You can use the ConversationUsers table to know who the recipients are. This also gives you the flexibility to have Conversations with more than 2 members, because your current model essentially enforces that a Message can only be to one user.
So let's walk through the models first with changes
module.exports = (sequelize, DataTypes) => {
const User = sequelize.define(
"User",
{
name: { type: DataTypes.STRING },
password: { type: DataTypes.STRING, allowNull: false },
username: { type: DataTypes.STRING, allowNull: false },
email: { type: DataTypes.STRING, allowNull: false },
},
{
// I think moving the associations to other files might make this more clear
}
);
};
return User;
};
module.exports = (sequelize, DataTypes) => {
const Conversation = sequelize.define(
"Conversation",
{
// perhaps something like a subject could go here e.g.
subject: DataTypes.STRING(500),
},
{
sequelize,
modelName: "Conversation",
}
);
Conversation.associate = (models) => {
Conversation.hasMany(models.Message, {
as: "ConversationMessages",
}); // adds ConversationId onto Message, gives us Conversation.getConversationMessages() etc
models.Message.belongsTo(Conversation); // create association both ways for convenience methods to find convo from a message
models.Message.hasOne(Conversation, {
as: 'LastMessage',
constraints: false,
allowNull:true,
defaultValue:null
}); // adds LastMessageId onto Conversation model (you'll have to write code to maintain this value, probably through an afterCreate hook on Message model)
};
return Conversation;
};
module.exports = (sequelize, DataTypes) => {
const Message = sequelize.define(
"Message",
{
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true, // if you want to do the hook thing i talked about to set LastMessageId, you need to put this in
},
body: { type: DataTypes.STRING, allowNull: false },
},
{
sequelize,
modelName: "Message",
}
);
Message.associate = (models) => {
Message.belongsTo(models.User, {as: "sentFromUser"});
};
return Message;
};
// I'm going to rename your many-to-many table "ConversationMembers"
module.exports = (sequelize, DataTypes) => {
const ConversationMembers = sequelize.define(
"ConversationMembers",
{
// again, the associations will build these fields for you
},
{
sequelize,
modelName: "ConversationMembers",
}
);
models.Conversation.belongsToMany(models.User, {
through: "ConversationMember",
as: "Members",
}); // gives us Conversation.getMembers()
models.User.belongsToMany(models.Conversation, {
through: "ConversationMember",
as: "MemberConversations",
}); // gives us User.getMemberConversations()
ConversationMember.belongsTo(models.Message, { as: "LastReadMessage" }); // gives us the potential ability to track the last read message for each convo member as ConversationMember.LastReadMessageId, you'll need to set this value manually on read for each user if you care about having it
models.Conversation.hasMany(ConversationMember);
models.User.hasMany(ConversationMember);
return ConversationMember;
Okay now onto your question, which perhaps becomes simpler at this point. If you already know the ConversationId, all you need to do is check that the person who is sending the message is a member of the conversation. Then if they are, write a row into the Messages table. It doesn't matter who the message is "to"--you're writing to the members of the Conversation, not to any individual.
async function canMessageHelper({conversationId, userId }) {
const convo = await models.Conversation.findOne({
attributes: ["id"], // whatever attrs you need, probably not many if any
where: {
id: conversationId,
},
include: [{
model: models.ConversationMember,
attributes: ["ConversationId"], // whatever you need if anything
where: { // this where is critical, it creates an inner join so convo only returns if myUserId is a member of the Conversation
UserId: userId
}
}]
});
if (!convo) {
return false;
}
return convo;
}
async function sendMessage({conversationId, authorUserId, messageText}) {
const allowMessage = await canMessageHelper({conversationId, userId: authorUserId});
if (!allowMessage) {
return false;
}
await models.Message.create({sentFromUserId: authorUserId, body: messageText});
}
If you want to try this, be sure you remove any tables you've already created with these names from your database before you sync.
I have not provided any code for the hooks I mentioned, but you will have the groundwork to develop those ideas out.

Sequelize .save() on instance not working

I have a model defined as below. I then try to run the code below it to update the isTeamLead property of a retrieved instance but I get the error teamMember.save() is not a function.
const TeamMember = sequelize.define('teamMember', {
userId: Sequelize.INTEGER,
teamId: Sequelize.INTEGER,
slotNumber: Sequelize.INTEGER,
isTeamLead: Sequelize.BOOLEAN
});
Promise.all([db.models.TeamMember.findOne({ where: { $and: [{ userId: lead.id }, { teamId: id }] } })]).then((teamMember) => { teamMember.isTeamLead = true; teamMember.save() });
I was able to solve this by making sure I was operating on the instance. Above I was operating on the array not the actual teamMember instance.
Promise.all([db.models.TeamMember.findOne({ where: { $and: [{ userId: lead.id }, { teamId: id }] } })]).then((teamMember) => { teamMember[0].isTeamLead = true; teamMember[0].save() });
If you are using sequelize, you could do something like.
1) first find
2) if exist update
const model = await Model.findById(id);
if(!model) {
return res.status(400).json({ msg: 'Bad Request: Model not found' });
}
const updatedModel = await Model.update({
key: value,
)};

How do I establish a one-to-many association between two models in sequelize?

I have made two sequelize models, one for storing students and one for storing their tasks.
I want to establish a one to many relation between student and task model...
The task model
const Student= require('./student.js')
module.exports = (sequelize, Sequelize) => {
const Task = sequelize.define("task", {
title: {
type: Sequelize.STRING
},
description: {
type: Sequelize.STRING
}
});
Task.associate = (Student) => {
Task.belongsTo(Student)
};
return Task;
};
The Student model
const Task = require('./task.js')
module.exports = (sequelize, Sequelize) => {
const Student = sequelize.define("student", {
name: {
type: Sequelize.STRING,
allowNull:false
},
email: {
type: Sequelize.STRING,
unique:true,
allowNull:false
},
branch: {
type: Sequelize.STRING,
allowNull:false
},
id: {
type: Sequelize.INTEGER,
autoIncrement: true,
primaryKey: true
},
});
Student.associate = (Task) =>{
Student.hasMany(Tasks, {as: "tasks"))
}
return Student;
};
I want to fetch the tasks of each student along with the student data..
below is the function for same
// Retrieve all Students from the database.
const Task= require('../models/task.js')
exports.findAll = (req, res) => {
Student.findAll({
include: Task
})
.then(data => {
res.send(data);
})
.catch(err => {
res.status(500).send({
message:
err.message || "Some error occurred while retrieving students."
});
});
};
But when I hit the query, I get the following error.
{
"message": "task is not associated to student!"
}
I'm stuck at this issue for 2 days...kindly help me sort this out.
Thank you in advance

Resources