Stack: NodeJs, PostgreSQL, Sequelize, Express.
I have two associated models (User and Role) through User_Roles.
When I create new user via Sequelize.create(), how can i fill the User_Roles table to define which roles (i get the array filled with role ID) the user have. The roles are already defined in table (roles)
The models:
const User = sequelize.define('users', {
id: { type: DataTypes.INTEGER, autoIncrement: true, primaryKey: true },
email: { type: DataTypes.STRING(320), allowNull: false, unique: true, isEmail: true },
});
const Role = sequelize.define('roles', {
id: { type: DataTypes.INTEGER, autoIncrement: true, primaryKey: true },
title: { type: DataTypes.STRING(64), allowNull: false, unique: true },
});
const User_Roles = sequelize.define('user_roles', {}, { timestamps: false });
// Associations
User.belongsToMany(Role, { through: User_Roles });
Role.belongsToMany(User, { through: User_Roles });
UserController:
async create(req, res, next) {
const { email, roles } = req.body;
if (!email) {
return next(ApiError.badRequest('Invalid request'));
}
if (!Array.isArray(roles) || !roles.length) {
return next(ApiError.badRequest('Invalid request'));
}
const isExistThisEmail = await User.findOne({ where: { email } });
if (isExistThisEmail) {
return next(ApiError.badRequest('The email already exists'));
}
const user = await User.create({ email });
return res.status(201).json(createdUser);
}
You can use dynamically added methods (according to associations) in the model:
await user.setRoles([1,2,3]) // or indicate the roles instances
See special methods/mixins
Related
I'm using this schema for the USERS Collection
const usersSchema = new Schema({
username: {
type: String,
required: true,
},
email: {
type: String,
required: true,
unique: true,
},
password: {
type: String,
required: true,
select: false,
},
phone: {
type: Number,
required: true,
},
books: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "books",
},
],
});
and when the user want to delete a book in books collection, it should be delete in the user's books document (The above array) as well.
and I use this query but i get an error
const deleteBook = async (req, res, next) => {
try {
const { id } = req.query;
const deletedProduct = await books.findByIdAndDelete(id).populate(
"author"
);
// IT WORKS AND DELETE THE BOOK IN BOOKS COLLECTION
if (!deletedProduct) {
return next(new ErrorHandler("Product Was Not Found", 404));
}
await Users.findByIdAndUpdate(
deletedProduct.author._id,
{
$pull: { books: deletedProduct._id },
},
(err, docs) => {
// ERROR IS NULL IN HERE
console.log(err);
console.log(docs);
}
);
res.status(200).json("Success");
} catch (err) {
next(new ErrorHandler(err.message, 500));
}
};
Its My query but i get this error.
"Query was already executed: users.findOneAndUpdate({ _id: new ObjectId("THE USER'S ID"
i want to say. The book will be deleted from the books collection, but stay as same in the User's books array.
Im trying to create a sequalized query that Searches For a Team Name & Filter To Sport Type but when I test the JSON body on Postman I get an error and its not returning the data. The JSON body matches the data that its meant to return but its not matching up.
Test on Postman
{
"searchTerm": "Kaizer fc",
"sportType": "2"
}
teams.js
const Teams = require('../models').teams;
const {sequelize, QueryTypes } = require('sequelize');
const db = require('../models')
const search = (searchTerm, sportType) => {
return new Promise(async (resolve, reject) => {
try {
console.log("Testing");
const teams = await db.sequelize.query(
`SELECT teams.name, institutions.name, addresses.line1, addresses.line2, addresses.country
FROM teams
INNER JOIN institutions
ON teams.institution_id = institutions.id
INNER JOIN addresses
ON institutions.address_id = addresses.id
WHERE teams.name like :search_name and teams.sport_type_id LIKE :sport_type`,
{
replacements: { search_name: searchTerm, sport_type: sportType },
type: QueryTypes.SELECT
}
);
return resolve(teams);
} catch (err) {
return reject(err)
}
})
}
teams.js - Models Folder
'use strict';
module.exports = (sequelize, DataTypes) => {
const teams = sequelize.define('teams', {
name: { type: DataTypes.STRING, allowNull: false },
media_id: { type: DataTypes.STRING, allowNull: true },
state_id: { type: DataTypes.INTEGER, allowNull: true },
status_id: { type: DataTypes.INTEGER, allowNull: true },
sport_type_id: { type: DataTypes.INTEGER, allowNull: false },
created_by: { type: DataTypes.INTEGER, allowNull: true, references: { model: 'users', key: 'id' } },
modified_by: { type: DataTypes.INTEGER, allowNull: true, references: { model: 'users', key: 'id' } },
primary_user_id: { type: DataTypes.INTEGER, allowNull: true, references: { model: 'users', key: 'id' } },
institution_id: { type: DataTypes.INTEGER, allowNull: true, references: { model: 'institutions', key: 'id' } },
}, {
createdAt: 'created_at',
updatedAt: 'updated_at',
indexes: [
{
unique: true,
fields: ['id']
}
],
});
return teams;
};
search.js - Controllers
const helper = require('../utils/helper');
const teamsService = require('../services/teams');
const search = async (req, res) => {
try {
const data = await teamsService.search(req.body);
console.log("TESTING");
console.log(data);
return res.send(data);
} catch (err) {
return helper.handleError(err, req, res);
}
}
module.exports = search;
search function has two arguments: searchTerm and sportType and you pass the whole req.body as a first argument so that's why it becomes a value for searchTerm and you got this error about the whole value from Sequelize.
Just extract both props from req.body OR define search with props passed by as an object:
const { searchTerm, sportType } = req.body;
const data = await teamsService.search(searchTerm, sportType);
OR
const data = await teamsService.search(req.body);
...
const search = ({ searchTerm, sportType }) => {
return new Promise(async (resolve, reject) => {
I'm trying to make a simple social media app using react, express and mongodb.
This is the user model:
const UserSchema = new mongoose.Schema(
{
username: { type: String, required: true, unique: true },
password: { type: String, required: true },
email: { type: String, required: true, unique: true},
followers: { type: Array, required: false },
following: { type: Array, required: false },
likes: { type: Array, required: false},
},
{ collection: 'users' }
)
This is the express server:
app.post('/api/follow', async (req, res) => {
const {token, username} = req.body
if (token === null)
{
return res.json({status: 'error'})
}
const user = await User.findOne({username}).lean()
const _visitor = jwt.verify(token, JWT_SECRET)
const visitor = await User.findOne({username: _visitor.username})
if (!user)
{
return res.json({status: 'error', error: 'User not found.'})
}
if (!visitor)
{
return res.json({status: 'error', error: 'User not found.'})
}
visitor.following.push(user._id)
user.followers.push(me._id)
return res.json({status: 'ok'})
})
But when I check the mongodb compass the following and followers arrays are empty.
The best way is to use findOneAndUpdate() method to update a value.
Also, if you are updating from two different collections you can use transactions. This is optional but can be useful to avoid inconsitences in your DB.
So your code can be something similar to this:
const updateVisitor = await User.findOneAndUpdate(
{
username: _visitor.username
},
{
$push:{
following: user._id
}
})
Example here
An the same code for user:
const updateUser = await User.findOneAndUpdate(
{
username: username
},
{
$push:{
followers: me._id
}
})
My model has "id", "liked", "likedBy" and "matched" fields.
I can update my database and add id according to my hypotethical likes; it stores target's id to my current user's liked field, current user's id to target's likedBy field.
I'm trying to achieve, if a user has both liked and likedBy id matching then put liked id to my matched field on both users, but I can't for some reason. It just doesn't care if statement there.
Any ideas why?
//like user by using it's id. update it to liked
app.put("/like/:id", auth, async (req, res) => {
try {
const user = await User.findById(req.params.id);
const loggedUser = await User.findById(req.user.id).select("-password");
//check if it is already liked
if (
user.likedBy.filter((like) => like.user.toString() === req.user.id)
.length > 0
) {
return res.status(400).json({ msg: "Already Liked" });
}
user.likedBy.unshift({ user: req.user.id });
loggedUser.liked.unshift({ user: req.params.id });
await user.save();
await loggedUser.save();
//check matching
if (user.likedBy === user.liked) {
user.matched.unshift({ user: req.user.id });
}
await user.save();
await loggedUser.save();
res.status(200).send("Liked!");
} catch (err) {
console.error(err.message);
res.status(500).send("Server Error");
}
});
My Schema:
const mongoose = require("mongoose");
const UserSchema = new mongoose.Schema({
email: {
type: String,
required: true,
unique: true,
},
password: {
type: String,
required: true,
},
firstname: {
type: String,
required: true,
},
lastname: {
type: String,
required: true,
},
picture: {
data: Buffer,
contentType: String,
},
age: {
type: Number,
required: true,
},
gender: {
type: String,
required: true,
},
job: {
type: String,
required: true,
},
desc: {
type: String,
default: "Hasn't written anything yet.",
},
liked: [{}],
likedBy: [{}],
matched: [{}],
});
module.exports = User = mongoose.model("user", UserSchema);
I found the mistake I made.
I'm trying to compare objects, which isn't possible really. I got index of my array then extracted the data I need and stored it into value1 & value2.
I found my mistake the moment I console.log'ed my conditions as below:
if(console.log(user.liked) === console.log(user.likedBy)){
...}
Working version:
//like user by using it's id. update it to liked
app.put("/like/:id", auth, async (req, res) => {
try {
const user = await User.findById(req.params.id);
const loggedUser = await User.findById(req.user.id).select("-password");
//check if it is already liked
if (
user.likedBy.filter((like) => like.user.toString() === req.user.id)
.length > 0
) {
return res.status(400).json({ msg: "Already Liked" });
} else {
user.likedBy.unshift({ user: req.user.id });
loggedUser.liked.unshift({ user: req.params.id });
await user.save();
await loggedUser.save();
const value1 = user.likedBy[0].user;
const value2 = user.liked[0].user;
if (value1 === value2) {
user.matched.unshift({ user: req.user.id });
loggedUser.matched.unshift({ user: req.params.id });
await user.save();
await loggedUser.save();
res.status(200).send("Liked & Matched!");
} else {
res.status(200).send("Liked!");
}
}
} catch (err) {
console.error(err.message);
res.status(500).send("Server Error");
}
});
Imagine I have defined the following custom validator function:
isUnique: function () { // This works as expected
throw new Error({error:[{message:'Email address already in use!'}]});
}
However, when I attempt to query the DB I run into problems:
isUnique: function (email) { // This doesn't work
var User = seqeulize.import('/path/to/user/model');
User.find({where:{email: email}})
.success(function () { // This gets called
throw new Error({error:[{message:'Email address already in use!'}]}); // But this isn't triggering a validation error.
});
}
How can I query the ORM in a custom validator and trigger a validation error based on the response from the ORM?
You can verify if the email already exists like that:
email: {
type: Sequelize.STRING,
allowNull: false,
validate: {
isEmail:true
},
unique: {
args: true,
msg: 'Email address already in use!'
}
}
Here's a simplified sample of a functioning isUnique validation callback (works as of SequelizeJS v2.0.0). I added comments to explain the important bits:
var UserModel = sequelize.define('User', {
id: {
type: Sequelize.INTEGER(11).UNSIGNED,
autoIncrement: true,
primaryKey: true
},
email: {
type: Sequelize.STRING,
validate: {
isUnique: function(value, next) {
UserModel.find({
where: {email: value},
attributes: ['id']
})
.done(function(error, user) {
if (error)
// Some unexpected error occured with the find method.
return next(error);
if (user)
// We found a user with this email address.
// Pass the error to the next method.
return next('Email address already in use!');
// If we got this far, the email address hasn't been used yet.
// Call next with no arguments when validation is successful.
next();
});
}
}
}
});
module.exports = UserModel;
With Sequelize 2.0, you need to catch Validation Errors.
First, define the User Model with a custom validator:
var User = sequelize.define('User',
{
email: {
type: Sequelize.STRING,
allowNull: false,
unique: true,
validate: {
isUnique: function (value, next) {
var self = this;
User.find({where: {email: value}})
.then(function (user) {
// reject if a different user wants to use the same email
if (user && self.id !== user.id) {
return next('Email already in use!');
}
return next();
})
.catch(function (err) {
return next(err);
});
}
}
},
other_field: Sequelize.STRING
});
module.exports = User;
Then, in the controller, catch any Validation Errors:
var Sequelize = require('sequelize'),
_ = require('lodash'),
User = require('./path/to/User.model');
exports.create = function (req, res) {
var allowedKeys = ['email', 'other_field'];
var attributes = _.pick(req.body, allowedKeys);
User.create(attributes)
.then(function (user) {
res.json(user);
})
.catch(Sequelize.ValidationError, function (err) {
// respond with validation errors
return res.status(422).send(err.errors);
})
.catch(function (err) {
// every other error
return res.status(400).send({
message: err.message
});
});
Success callback is called even if no user is found. You have to check if the function passes a user as an argument:
isUnique: function (email) {
var User = seqeulize.import('/path/to/user/model');
User.find({where:{email: email}})
.success(function (u) { // This gets called
if(u){
throw new Error({error:[{message:'Email address already in use!'}]}); // But this isn't triggering a validation error.
}
});
}
Define the User Model with a custom validator:
const { DataTypes } = require('sequelize');
const sequelize = require('../config/db');
const UserModel = sequelize.define('user', {
id: {
type: DataTypes.INTEGER(11).UNSIGNED,
autoIncrement: true,
primaryKey: true,
},
name: {
type: DataTypes.STRING,
allowNull: false,
},
email: {
type: DataTypes.STRING,
allowNull: false,
validate: {
isUnique: (value, next) => {
UserModel.findAll({
where: { email: value },
attributes: ['id'],
})
.then((user) => {
if (user.length != 0)
next(new Error('Email address already in use!'));
next();
})
.catch((onError) => console.log(onError));
},
},
},
password: {
type: DataTypes.STRING,
allowNull: false,
},
createdAt: {
type: DataTypes.DATE,
allowNull: false,
},
updatedAt: {
type: DataTypes.DATE,
allowNull: false,
},
});
module.exports = UserModel;