This question does not seek answer as "best practice", but rather the more appropriate way compared to what I have below.
Basically I have a User model/table that has this associate with UserScore model/table.
User.belongsTo(models.UserScore, {
foreignKey: {
name: "userScoreId",
allowNull: false,
defaultValue: 1
},
as: "score",
targetKey: "id"
})
And then the UserScore model is defined as:
'use strict';
module.exports = (sequelize, DataTypes) => {
const UserScore = sequelize.define('UserScore', {
id: { type: DataTypes.INTEGER, allowNull: false, autoIncrement: true, unique: true, primaryKey: true },
score: DataTypes.INTEGER,
coins: DataTypes.INTEGER
}, {});
UserScore.associate = function (models) {
UserScore.hasMany(models.User, {
foreignKey: "userScoreId",
sourceKey: "id"
})
};
return UserScore;
};
The PROBLEM that I have is that I feel like I'm doing my Sequelize queries below incorrectly.
First I create UserScore assuming that I know the userId, using Facebook works because I can get id from Facebook. BUT NOT with signup with email and password! This is the first problem.
And after creating the UserScore, I then create a User data. This just feels so wrong. Although it works well with login with Facebook because like I said, I have the userId for creating UserScore, but what if I don't have it (e.g. using sign up with email and password)?
EDIT: The question is related to the error about constraints, because I tried making a User data without making a UserScore first. I can't it reproduce now because I've fixed it using UUId (https://www.npmjs.com/package/uuid-int).
AuthController.js
module.exports = {
// SIGN UP WITH EMAIL AND PASSWORD
signup: async (req, res, next) => {
const email = req.body.email
const password = req.body.password
if (!email || !password) {
return res.status(403).send({
message: "Error! Required parameters are: {email} and {password}."
})
}
// Check if there is a user with the same email
db.User.findOne({
where: { email: email }
})
.then(data => {
if (data) {
return res.status(409).send({
message: "Email is already in use."
})
}
// Create a new user...
const newUser = {
fbId: null,
email: email,
firstName: null,
lastName: null,
photoUrl: null
}
const newUserScore = {
score: 0,
userId: id, //// <---------- NO IDEA!!!
coins: 0
}
db.UserScore.create(newUserScore)
.then(userScoreData => {
db.User.create(newUser)
.then(data => {
console.log("Created new user! ✅")
// Generate the token
const token = signToken(newUser)
// Respond with token
res.status(200).json({ token })
})
.catch(err => {
console.log("Error creating a new user!!!." + err)
return res.status(409).send({
message: "An error has occured while creating a new user"
})
})
})
.catch(err => {
console.log("Error creating user score.")
done(err, null, err.message)
})
})
.catch(err => {
console.log(err)
res.status(500).send({
message: "An error has occured while retrieving data."
})
})
}
}
User Relations:
I tried making a User data without making a UserScore first.
The foreign key constraint you have defined between User and UserScore
will never allow you to add a UserScoreId in the table User that does not exist in the UserScore table, as this will violate
the SQL foreign key constraint. This is indeed a constraint !
From what I understand (correct me if I'm wrong),
you are modeling the fact that a user may have one or more score(s),
if so, it's the userId that should be in the UserScore table.
This way, if the user with the same email does not exist you can add the user first then add the user score.
So you have to change how you defined your sequelize associations
and set userId as a foreign key on the UserScore table.
Here's how you could define the associations
//will add userId to UserScore table
User.hasMany(UserScore, {
foreignKey: { name: "userId", allowNull: false, defaultValue: 1 },
as: "score",
targetKey: "id"
})
//same here, will add userId to UserScore table
UserScore.belongsTo(User, {
foreignKey: "userId",
sourceKey: "id"
})
Related
for reasons, I am trying to get the username using the user's ID, and so, I have tried many things but they all result in Cannot read property 'username' of undefined this is what I have:
user = bot.users.cache.find(user => user.id === key); //key is the user's id
console.log(user);
bot.channels.cache.get("847470926748057671").send(user.username); //this is where the error is
and where it outputs user to the log, it says
User {
id: '691615336943845407',
system: false,
locale: null,
flags: UserFlags { bitfield: 128 },
username: 'orangenal name',
bot: false,
discriminator: '7363',
avatar: 'e99b5e3d3edcfa7e22dd76a7eb869c62',
lastMessageID: '847498366236885053',
lastMessageChannelID: '847470926748057671'
}
so there is a username, but I do not know how to access it
Try using fetch() and make sure to handle the promise
bot.users.fetch(key)
.then(res => user = res)
.catch(console.error);
console.assert(!user, 'User was not found!');
const channel = bot.channels.cache.get("847470926748057671");
channel.send(user?.username);
I can't seem to find what the error in the invocation is
function findUser(req, res) {
const username = req.body.username;
prisma.user.findUnique({
where: { username: username },
select: { username: true }
})
.then(data => {
res.send({
'userExists': data ? true : false
})
})
.catch(err => {
res.status(500).send({
message: err.message || "Some error occurred while retrieving user."
})
})
.finally(async () => { await prisma.$disconnect()});
// schema.prisma
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
model User {
id Int #default(autoincrement()) #id
username String #unique
password String
salt String
createdAt DateTime #default(now())
updatedAt DateTime #updatedAt
}
From Prisma side everything is OK. The problem is probably req.body.username, if it's undefined you receive Invalid 'prisma.user.findUnique()' invocation.
You have to add validation for username, i.e.
if {typeof username !== string} return res.status(404).send('invalid username')
It might be the late response. I was also running into the same problem. But documentation saved my day. https://www.prisma.io/docs/reference/api-reference/prisma-client-reference#findunique
You just have to add #unique attribute to your username field in schema.prisma, like below —
username String #unique #db.VarChar(255)
After looking at your code it seems that the username is missing from your req.body. I would suggest always verify the params you want to extract from req.body. I refactor your code to es6.
Here is the updated code you can try,
function findUser(req, res) {
// Destructure username from req.body
const { username } = req.body;
if(username == null) throw new Error('Username undefined');
// when property and value is same, you can write 'username' like below
prisma.user.findUnique({
where: { username },
select: { username: true }
})
.then(data => {
res.send({
'userExists': data ? true : false
})
})
.catch(err => {
res.status(500).send({
message: err.message || "Some error occurred while retrieving user."
})
})
.finally(async () => { await prisma.$disconnect()});
I wrote a method to update a user in my application and everything works correctly there. In addition, I wrote some code that fires before the document is saved and it isn't functioning correctly.
The point of the code is to determine if the user modified their password. If they didn't, simply call next(). If they did, bcrypt will hash the password.
Here's the code in my controller where I'm doing the update:
// #desc Update user by ID
// #route PUT /api/users/:id
// #access Private
const updateUserById = asyncHandler(async (req, res) => {
// Destructure body content from request
const {
firstName,
lastName,
username,
email,
role,
manager,
learningStyle,
departments,
facility,
company,
isActive,
} = req.body;
// Search the database for the user
const user = await User.findById(req.params.id);
// Check to ensure the user was found. Else, respond with 404 error
if (user) {
// Update information accordingly
user.firstName = firstName || user.firstName;
user.lastName = lastName || user.lastName;
user.username = username || user.username;
user.email = email || user.email;
user.role = role || user.role;
user.manager = manager || user.manager;
user.learningStyle = learningStyle || user.learningStyle;
user.departments = departments || user.departments;
user.facility = facility || user.facility;
user.company = company || user.company;
user.isActive = isActive === undefined ? user.isActive : isActive;
// Save user to the database with updated information
const updatedUser = await user.save();
// Send the updated user to the client
res.json(updatedUser);
} else {
res.status(404);
throw new Error("User not found");
}
});
Here's my User model code:
import mongoose from "mongoose";
import bcrypt from "bcryptjs";
const userSchema = mongoose.Schema(
{
firstName: {
// The user's first name
required: true,
type: String,
trim: true,
},
lastName: {
// The user's last name
required: true,
type: String,
trim: true,
},
username: {
// The user's username - user can create their own
required: true,
type: String,
unique: true,
lowercase: true,
},
email: {
// The user's email
required: false,
trim: true,
lowercase: true,
unique: true,
type: String,
},
password: {
// The user's encrypted password (bcrypt hash)
required: true,
type: String,
},
role: {
// The user's role - drives what they're able to do within the application
required: true,
type: mongoose.Schema.Types.ObjectId,
ref: "Role",
},
manager: {
// The user's manager
type: mongoose.Schema.Types.ObjectId,
required: false,
ref: "User",
},
learningStyle: {
// The user's learning style (after assessment is taken)
type: mongoose.Schema.Types.ObjectId,
required: false,
ref: "LearningStyle",
},
departments: [
// The departments the user belongs to (used to drive what the user sees)
// Example: Finishing, Shipping, Printing
{
type: mongoose.Schema.Types.ObjectId,
required: false,
ref: "Department",
},
],
facility: {
// The facility the user works at. Example: Austin Facility
type: mongoose.Schema.Types.ObjectId,
required: false,
ref: "Facility",
},
company: {
// The company the user works for. Example: Microsoft
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: "Company",
},
isActive: {
required: true,
type: Boolean,
default: true,
},
},
{ timestamps: true }
);
// Match user's password using bcrypt
userSchema.methods.matchPassword = async function (enteredPassword) {
return await bcrypt.compare(enteredPassword, this.password);
};
// Generate user's encrypted password on save
userSchema.pre("save", async function (next) {
// Check to see if password is modified. If it is, encrypt it. If not, execute next();
if (!this.isModified("password")) {
console.log("Does this run?");
next();
}
console.log("Does this run as well?");
const salt = await bcrypt.genSalt(10);
this.password = await bcrypt.hash(this.password, salt);
});
const User = mongoose.model("User", userSchema);
export default User;
I have:
Added two console logs, both of which fire (see userSchema.pre("save")) in model.
Tried to prevent the encryption from firing by checking if password is modified
Tried dropping the entire users collection and starting over
Another application from a course with the exact same approach working fine
Notice in my controller I am NOT updating the password at all. Yet, every time I use Postman to send a PUT request and modify even the name, the password gets hashed again and both console logs fire.
The "save" middleware is calling next() but continuing on to complete the function. Use return or else to guard the rest of the code.
userSchema.pre("save", async function (next) {
// Check to see if password is modified. If it is, encrypt it. If not, execute next();
if (!this.isModified("password")) {
// Finish here
return next();
}
const salt = await bcrypt.genSalt(10);
this.password = await bcrypt.hash(this.password, salt);
});
I have a REST API built with Node JS and I'm currently using MongoDB as my database. I want to prevent the users from deleting another user's products and for this I checked if the userId from the decoded token is the same as the product userId.
Product schema
const mongoose = require("mongoose");
const productSchema = mongoose.Schema(
{
_id: mongoose.Schema.Types.ObjectId,
userId: mongoose.Schema.Types.ObjectId,
name: { type: String, required: true },
price: { type: Number, required: true },
productImage: { type: String, required: false },
category: {
type: mongoose.Schema.Types.ObjectId,
ref: "Category",
required: true
},
gender: { type: String, required: true }
},
{ timestamps: { createdAt: "created_at" } }
);
module.exports = mongoose.model("Product", productSchema);
The delete product method:
const id = req.params.productId;
Product.findById({ _id: id }).then((product) => {
if (product.userId != req.user._id) {
return res.status(401).json("Not authorized");
} else {
Product.deleteOne({ _id: id })
.exec()
.then(() => {
return res.status(200).json({
message: "Product deleted succesfully",
});
})
.catch((err) => {
console.log(err);
return res.status(500).json({
error: err,
});
});
}
});
};
As you guys see first I'm searching executing the findByID method to access the userId property of the product, then I'm comparing the userId from the response with the userId from the decoded token.
I don't think my method is very efficient since it's running both findById and deleteOne methods.
Can you help me with finding a better solution for this?
as Guy Incognito mentioned, what you are trying to do is an OK thing and you may want to keep it this way in case you want to send a 404 status stating the product they are trying to remove does not exist.
however, if you are trying to do it with only one request
Product.deleteOne({ _id: id, userId: req.user._id })
hope it helps!
This is my controller to update put user data. The controller accepts up to 4 values. I would like to do so if I send only a name to this route, This will change only the name and the rest will remain unchanged. (it will not be empty). Is it possible to do this? Do I have to do it in redux-saga, i.e. if it is empty, give it up-to-date
// Update basic User's Data
exports.setUserdata = (req, res) => {
const id = req.params.userId;
User.update(
{
password: req.body.password,
name: req.body.name,
surname: req.body.surname,
email: req.body.email,
},
{ where: { id } },
)
.then(() => {
res.status(200).json({ success: true });
})
.catch(() => {
res.status(500).json({ error: 'Internal server error' });
});
};
Pass params you want to update and don't pass other keys.
If req.body contains only name key, you can just pick up those 4 keys from req.body.
const updateParams = _.pick(req.body, ['name', 'password', 'surname', 'email'])
User.update(updateParams, { where: { id } })
If req.body has other properties with value null or undefined, you can filter them after picking.
const updateParams = _.chain(req.body).pick(['name', 'password', 'surname', 'email']).filter().value()
User.update(updateParams, { where: { id } })
Of course it depends on the ORM you are using but I believe most ORM don't update attributes which are not passed at all.