I am creating a REST API with nodejs and sequelize and I have 2 tables:
Users table
Friends table
With
/api/friends
I get all my friends list (stored in my Friends table) but I don't know how to get their name (from the User table).
This is my request to get my friends list:
models.Friend.findAll({
where: {
$or: [{
UserID: userFound.id
},
{
idFriend: userFound.id
}],
status : "active"
}
})
In the picture I show you the Users table and Friends table
How can I get the name of friend in my request ?
UPDATE
this is my user model
'use strict';
module.exports = (sequelize, DataTypes) => {
var User = sequelize.define('User', {
email: DataTypes.STRING,
username: DataTypes.STRING,
password: DataTypes.STRING,
isAdmin: DataTypes.BOOLEAN,
isOut: DataTypes.BOOLEAN,
bio: DataTypes.STRING
}, {});
User.associate = function(models) {
// associations can be defined here
models.User.hasMany(models.Message)
models.User.hasMany(models.Friend)
};
return User;
};
and this is my friends models
'use strict';
module.exports = (sequelize, DataTypes) => {
var Friend = sequelize.define('Friend', {
UserID: DataTypes.INTEGER,
idFriend: DataTypes.INTEGER,
status: DataTypes.STRING
}, {});
Friend.associate = function(models) {
// associations can be defined here
models.Message.belongsTo(models.User, {
foreignKey:{
allowNull: false
}
})
};
return Friend;
};
and my get friends function
showFriend: function (req, res) {
var headerAuth = req.headers['authorization'];
var UserId = jwtUtils.getUserId(headerAuth);
// Params
asyncLib.waterfall([
function (done) {
models.User.findOne({
where: { id: UserId }
})
.then(function (userFound) {
done(null, userFound);
})
.catch(function (err) {
return res.status(500).json({ 'error': 'unable to verify user' });
});
},
function (userFound, TargetFound, done) {
models.Friend.findAll({
where: {
$or: [{
UserID: userFound.id
},
{
idFriend: userFound.id
}],
status : "active"
}
})
.then(function (friends) {
if (friends) {
res.status(200).json(friends);
} else {
res.status(404).json({ "error": "no friends found" });
}
})
.catch(function (err) {
return res.status(500).json({ 'error': 'cannot find Friend' })
})
}
], function (newFriend) {
if (newFriend) {
return res.status(201).json({
'newFriend': newFriend.id
})
} else {
return res.status(500).json({ 'error': 'cannot add Friendss' })
}
});
},
thanks
If Friend and Users are associated, then you have to include them in your query:
models.Friend.findAll({
where: {
$or: [
{ UserID: userFound.id },
{ idFriend: userFound.id }
],
status : "active"
},
include: [{
model: models.User
}]
})
Then you should be able to do something like:
const friends = models.Friend.findAll({ ... })
friends.forEach((friend) => {
/* depends on your Naming Strategy, I'm assuming
`Users` will load as property 'User' on `Friends`,
it depends on how your models and associations are defined
*/
console.log(friend.User.username)
})
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.
i was following brad traversy's one of his udemy course. after working on adding experience in profile in profile routes. i all time get server error. but it should end up with full profile details like in brad course. this is my github link for that project
https://github.com/arshad007hossain/findDevs
profile.js
const express = require("express");
const router = express.Router();
const auth = require("../../middleware/authmiddleware");
const { check, validationResult } = require("express-validator");
const Profile = require("../../models/Profile");
const User = require("../../models/User");
// #route GET api/profile/me
// #desc Get current users profile
// #access Private
router.get("/me", auth, async (req, res) => {
try {
const profile = await Profile.findOne({
user: req.user.id,
}).populate("user", ["name", "avatar"]);
if (!profile) {
return res.status(400).json({ msg: "There is no profile for this user" });
}
res.json(profile);
} catch (err) {
console.error(err.message);
res.status(500).send("Server Error");
}
});
// #route POST api/profile/me
// #desc create or update users profile
// #access Private
router.post(
"/",
[
auth,
[
check("status", "status is required").not().isEmpty(),
check("skills", "skills is required").not().isEmpty(),
],
],
async (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
const {
company,
website,
location,
bio,
status,
githubusername,
facebook,
linkedin,
twitter,
instagram,
youtube,
skills,
} = req.body;
//build user profile
const profileFields = {};
profileFields.user = req.user.id;
if (company) profileFields.company = company;
if (website) profileFields.website = website;
if (location) profileFields.location = location;
if (bio) profileFields.bio = bio;
if (status) profileFields.status = company;
if (githubusername) profileFields.githubusername = githubusername;
if (skills) {
profileFields.skills = skills.split(",").map((skill) => skill.trim());
}
//build social objects
profileFields.social = {};
if (youtube) profileFields.social.youtube = youtube;
if (twitter) profileFields.social.twitter = twitter;
if (linkedin) profileFields.social.linkedin = linkedin;
if (instagram) profileFields.social.instagram = instagram;
if (facebook) profileFields.social.facebook = facebook;
//console.log(profileFields.skills);
try {
let profile = await Profile.findOne({ user: req.user.id });
if (profile) {
//Update
profile = await Profile.findOneAndUpdate(
{ user: req.user.id },
{ $set: profileFields },
{ new: true }
);
return res.json(profile);
}
//create
profile = new Profile(profileFields);
await profile.save();
res.json(profile);
} catch (err) {
console.errora(err.message);
res.status(500).json("server error");
}
}
);
// #route GET api/profile
// #desc Get all profile
// #access Public
router.get("/", async (req, res) => {
try {
let profiles = await Profile.find().populate("user", ["name", "avatar"]);
res.json(profiles);
} catch (err) {
console.error(err.message);
res.status(500).json("server error");
}
});
// #route GET api/profile/user/user_id
// #desc Get single profile
// #access Public
router.get("/user/:user_id", async (req, res) => {
try {
const profile = await Profile.findOne({
user: req.params.user_id,
}).populate("user", ["name", "avatar"]);
if (!profile) return res.status(400).json({ msg: "Profile not found" });
res.json(profile);
} catch (err) {
if (err.kind == "ObjectId") {
return res.status(400).json({ msg: "Profile not found" });
}
console.error(err.message);
res.status(500).json("server error");
}
});
// #route DELETE api/profile
// #desc Delete profile, user
// #access Private
router.delete("/", auth, async (req, res) => {
try {
// Remove profile
await Profile.findOneAndRemove({ user: req.user.id });
// Remove user
await User.findOneAndRemove({ _id: req.user.id });
res.json({ msg: "User deleted" });
} catch (err) {
console.error(err.message);
res.status(500).send("Server Error");
}
});
// #route PUT api/profile/experience
// #desc Add profile experience
// #access Private
router.put(
'/experience',
[
auth,
[
check('title', 'Title is required field').not().isEmpty(),
check('company', 'Company is required field').not().isEmpty(),
check('from', 'From date is required field').not().isEmpty(),
],
],
async (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
const {
title,
company,
location,
from,
to,
current,
description,
} = req.body;
const newExp = {
title,
company,
location,
from,
to,
current,
description,
};
try {
const profile = await Profile.findOne({ user: req.user.id });
profile.experience.unshift(newExp);
await profile.save();
res.json(profile);
} catch (err) {
console.error(err.message);
res.status(500).send('Server Error');
}
});
module.exports = router;
User.js
const mongoose = require("mongoose");
const UserSchema = new mongoose.Schema({
name: {
type: String,
required: true,
},
email: {
type: String,
required: true,
unique: true,
},
password: {
type: String,
required: true,
},
avatar: {
type: String,
},
date: {
type: Date,
default: Date.now,
},
});
module.exports = User = mongoose.model("user", UserSchema);
Profile.js Model
const mongoose = require('mongoose');
const ProfileSchema = new mongoose.Schema({
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'user'
},
company: {
type: String
},
website: {
type: String
},
location: {
type: String
},
status: {
type: String,
required: true
},
skills: {
type: [String],
required: true
},
bio: {
type: String
},
githubusername: {
type: String
},
experience: [
{
title: {
type: String,
required: true
},
company: {
type: String,
required: true
},
location: {
type: String
},
from: {
type: Date,
required: true
},
to: {
type: Date
},
current: {
type: Boolean,
default: false
},
description: {
type: String
}
}
],
education: [
{
school: {
type: String,
required: true
},
degree: {
type: String,
required: true
},
fieldofstudy: {
type: String,
required: true
},
from: {
type: Date,
required: true
},
to: {
type: Date
},
current: {
type: Boolean,
default: false
},
description: {
type: String
}
}
],
social: {
youtube: {
type: String
},
twitter: {
type: String
},
facebook: {
type: String
},
linkedin: {
type: String
},
instagram: {
type: String
}
},
date: {
type: Date,
default: Date.now
}
});
module.exports = mongoose.model('profile', ProfileSchema);
I'm trying to learn Sequelize.js and I'm confused about its Many-To-Many Association.
What I've tried to do is simple Tasks Management with Users. Each task can be assignable to other users.
So, there's Users, Tasks and TaskContributors tables.
First, I made the POC version of my idea with Express.js. Then I found that I don't know how to insert to the Relational Table within different router.
Here are the model codes.
User Model
'use strict'
module.exports = (sequelize, DataTypes) => {
const User = sequelize.define(
'User',
{
uid: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4 },
name: DataTypes.STRING,
password: DataTypes.STRING,
},
{},
)
User.associate = function (models) {
User.belongsToMany(models.Task, {
as: 'Contributors',
through: 'TaskContributors',
foreignKey: 'userId',
})
}
return User
}
Task Model
'use strict'
module.exports = (sequelize, DataTypes) => {
const Task = sequelize.define(
'Task',
{
uid: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4 },
name: DataTypes.STRING,
description: DataTypes.TEXT,
status: DataTypes.BOOLEAN,
},
{},
)
Task.associate = function (models) {
Task.belongsToMany(models.User, {
as: 'Task',
through: 'TaskContributors',
foreignKey: 'taskId',
})
}
return Task
}
TaskContributor Model
'use strict'
module.exports = (sequelize, DataTypes) => {
const TaskContributor = sequelize.define(
'TaskContributor',
{
userId: {
allowNull: false,
type: DataTypes.INTEGER,
references: { model: 'Users', key: 'id' },
},
taskId: {
allowNull: false,
type: DataTypes.INTEGER,
references: { model: 'Tasks', key: 'id' },
},
userStatus: { allowNull: false, type: DataTypes.STRING },
},
{},
)
TaskContributor.associate = function (models) {}
return TaskContributor
}
Routers
Users Router
router.get('/create/:name', (req, res) => {
User.create({ name: req.params.name, password: '123' })
.then((result) => res.send(result))
.catch((err) => res.status(500).send(err))
})
Tasks Router
router.get('/create/:userId/:name', (req, res) => {
const { userId, name } = req.params
User.findOne({ where: { uid: userId } }).then(async (user) => {
console.log(user)
const maybeTask = await user
.addTask({
name,
description: '',
status: false,
through: { userStatus: 'owner' },
})
.catch((err) => err)
if (maybeTask instanceof Error) res.status(500).send(maybeTask)
res.send(maybeTask)
})
})
When I tried to create new Task, it said user.addTask is not a function.
From what I understand from the Docs is that they showed how to create M-M association with two model.create() Objects, but not with the scenario like creating in different file.
I have a scenario in which if you like a post, it will change
liked:false to liked:true
This liked is based if the current user liked the post. The problem is when a new user signs up, it will still show liked being true despite the new user NOT liking the post.
How would i be able to check if the current user liked the post ? I don't think my logic is somewhat right as far as checking if the current user liked the post.
I want to keep the findAll functionality, i should get all posts, not just by the current user.
Sorta like instagram, or facebook.
this is posts array
and this is how im liking a post
likePost
likePost: async (req: any, res: Response) => {
const created = await models.Likes.findOne({
where: {
userId: req.session.user.id,
resourceId: req.params.id
}
});
console.log(created);
const post = await models.Post.findOne({ where: { id: req.params.id } });
// if like not created then do this
if (!created && post) {
await models.Likes.create({
userId: req.session.user.id,
resourceId: req.params.id
}).then(() => {
post.increment("likeCounts", { by: 1 });
post.update({ liked: req.session.user.id ? true : false });
res.status(200).send({
message: "You liked this post"
});
});
// else if post does not exist
} else if (!post) {
res.status(200).send({
message: "there is not post to be liked"
});
} else {
// else if a like does exist destroy like
await models.Likes.destroy({
where: {
userId: req.session.user.id
}
}).then(() => {
post.decrement("likeCounts", { by: 1 });
res.status(200).send({
message: "You unliked this post"
});
});
}
this is how im getting the posts.
getPosts
getPosts: async (req: any, res: Response) => {
await models.Post.findAll({
include: [
{ model: models.User, as: "author", attributes: ["username"] },
{ model: models.Likes }
],
order: [["createdAt", "DESC"]],
limit: 6
}).then(posts => {
res.json(posts);
});
},
Post.js(model)
"use strict";
module.exports = (sequelize, DataTypes) => {
var Post = sequelize.define("Post", {
title: DataTypes.STRING,
postContent: DataTypes.STRING,
liked: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: false
},
likeCounts: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: 0,
validate: {
min: 0,
}
},
authorId: DataTypes.INTEGER
}, {});
Post.associate = function (models) {
Post.belongsTo(models.User, {
as: "author",
foreignKey: "authorId",
onDelete: "CASCADE"
});
Post.hasMany(models.Likes, {
foreignKey: "resourceId",
timestamps: false,
targetKey: "id",
onDelete: "CASCADE"
});
};
return Post;
};
I believe the bug you are seeing is because you are not resolving the promises that are returned by:
post.increment("likeCounts", { by: 1 });
post.update({ liked: req.session.user.id ? true : false });
This means that the response will be send before those queries execute. The post.liked value will be set to true any time there is a user.id on the session. You may want to consider using transactions to roll back some of the earlier queries if later ones fail. I would also recommend using Promise.all() to make concurrent queries (it will be faster) and use async/await exclusively without mixing in thenables.
likePost: async (req: any, res: Response) => {
// fetch created and post at the same time
const [ created, post ] = await Promise.all([
models.Likes.findOne({
where: {
userId: req.session.user.id,
resourceId: req.params.id
}
}),
models.Post.findOne({
where: {
id: req.params.id
}
}),
]);
// no post, no updates
if (!post) {
return res.status(200).send({
message: "there is no post to be liked"
});
}
// we are going to make updates, so use a transaction, you will need to reference sequelize
let transaction;
try {
transaction = await sequelize.transaction();
if (!created && post) {
// use Promise.all() for concurrency
await Promise.all([
models.Likes.create({
userId: req.session.user.id,
resourceId: req.params.id
}, { transaction }),
post.increment("likeCounts", { by: 1, transaction }),
post.update({ liked: req.session.user.id ? true : false }, { transaction }),
]);
await transaction.commit();
return res.status(200).send({
message: "You liked this post"
});
}
await Promise.all([
models.Likes.destroy({
where: {
userId: req.session.user.id
}
}, { transaction }),
post.decrement("likeCounts", { by: 1, transaction }),
]);
await transaction.commit();
return res.status(200).send({
message: "You unliked this post"
});
} catch (err) {
if (transaction) {
await transaction.rollback();
}
console.log('There was an error', err);
return res.status(500);
}
}
To only return Likes for the current user on the getPost()
getPosts: async (req: any, res: Response) => {
const posts = await models.Post.findAll({
include: [
{ model: models.User, as: "author", attributes: ["username"] },
// limit the likes based on the logged in user
{ model: models.Likes, required: false,
where: { userId: req.session.user.id },
},
],
order: [["createdAt", "DESC"]],
limit: 6
});
return res.json(posts);
},
So by following #doublesharp help, i was able to determine if the current user liked the post or not, by using a sequelize data type VIRTUAL, along with using getDataValue
updated code
Post(model)
"use strict";
module.exports = (sequelize, DataTypes) => {
var Post = sequelize.define("Post", {
title: DataTypes.STRING,
postContent: DataTypes.STRING,
liked: {
type: DataTypes.VIRTUAL,
allowNull: false,
defaultValue: false,
get: function () {
return this.getDataValue('Likes').length ? true : false;
}
},
likeCounts: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: 0,
validate: {
min: 0,
}
},
authorId: DataTypes.INTEGER
}, {});
Post.associate = function (models) {
Post.belongsTo(models.User, {
as: "author",
foreignKey: "authorId",
onDelete: "CASCADE"
});
Post.hasMany(models.Likes, {
foreignKey: "resourceId",
timestamps: false,
targetKey: "id",
onDelete: "CASCADE"
});
};
return Post;
};
//# sourceMappingURL=post.js.map
I am trying to upgrade Sequelize V3 to V4.
Followed the breaking changes instructions here.
I get an error when running the following query with V4:
Auth.findOne({ . // This is where it failes
include: {
required: true,
include: {
where: {
id: decoded.id,
token: token,
}
}
}
})
Setup:
Existing Code with V3 (works):
module.exports = function (sequelize, DataTypes) {
const Auth = sequelize.define('Auth', {
token: {
type: DataTypes.STRING,
allowedNull: true,
unique: true,
required: true,
validate: {
len: [5, 200]
}
}
device: {
type: DataTypes.STRING,
allowedNull: true,
unique: false,
}
}, {
tableName: 'Auth',
classMethods: {
associate: function (models) {
Auth.belongsTo(models.User, {
foreignKey: {
name: 'user_id',
allowedNull: false,
}
})
Auth.hasMany(models.Endpoint, {
as: 'endpoints',
foreignKey: 'auth_id'
})
},
findByToken: function (token) {
var User = this
var decoded
try {
decoded = jwt.verify(token, 'abc123')
} catch (e) {
return Promise.reject()
}
return Auth.findOne({
where: {
id: decoded.id,
token: token,
}
})
}
}, instanceMethods: {
generateAuthToken: function () {
var auth = this
var access = 'auth'
var token = jwt.sign({ id: auth.id, access }, 'abc123').toString()
auth.token = token
auth.code = null
auth.save().then(() => {
})
}
}
})
return Auth
}
Upgrading to V4 (TypeError occurs)
module.exports = function (sequelize, DataTypes) {
var Auth = sequelize.define('Auth', {
token: {
type: DataTypes.STRING,
allowedNull: true,
unique: true,
required: true,
validate: {
len: [5, 200]
}
},
device: {
type: DataTypes.STRING,
allowedNull: true,
unique: false,
}
})
Auth.associate = function (models) {
Auth.belongsTo(models.User, {
foreignKey: {
name: 'user_id',
allowedNull: false,
}
})
Auth.hasMany(models.Endpoint, {
as: 'endpoints',
foreignKey: 'auth_id'
})
}
Auth.findByToken = function (token) {
var User = this
var decoded
try {
decoded = jwt.verify(token, 'abc123')
} catch (e) {
return Promise.reject()
}
return Auth.findOne({ . // This is where it fails
include: {
required: true,
include: {
where: {
id: decoded.id,
token: token,
}
}
}
})
}
Auth.prototype.generateAuthToken = function () {
var auth = this
var access = 'auth'
var token = jwt.sign({ id: auth.id, access }, 'abc123').toString()
auth.token = token
auth.code = null
auth.save().then(() => {
})
}
return Auth
}
Middleware
var authenticate = (req, res, next) => {
var token = req.header('x-auth')
var db = req.app.get('db')
db.Auth.findByToken(token).then((auth) => {
if (!auth) {
return Promise.reject()
}
req.user_id = auth.user_id
req.device_id = auth.device_id
req.auth_id = auth.id
return next()
}).catch((e) => {
return res.status(401).send()
// I get an error here: `TypeError: Cannot read property '_expandIncludeAll' of undefined
})
}
What am I doing wrong?
Have you tried without the "include" ?
Something like:
return Auth.findOne({ . // This is where it fails
where: {
id: decoded.id,
token: token,
}
});
should work.
"include" is used for subqueries through associations but your where clause seems to be only on the Auth attributes (http://docs.sequelizejs.com/class/lib/model.js~Model.html#static-method-findAll)
You'd use "include" if you wanted to filter on some attributes on User.
You've to do include : { model : YourModel }.
Doing include: { include : Model} throws this exact error.