Bookshelf cascade delete - mysql

I have two models Company and CompanyAdmin. A model Company has many CompanyAdmins. I am trying to delete CompanyAdmins when a parent Company is deleted, using bookshelf plugin bookshelf-cascade-delete. I also use knex to connect mysql db. Here are my models:
const db = bookshelf(connection);
db.plugin(cascadeDelete);
const Company = db.Model.extend({
tableName: 'company',
idAttribute: 'id',
hasTimestamps: true,
company_admins: function () { return this.hasMany(CompanyAdmin); }, // console.log(this);
}, {
dependents: ['company_admins'],
});
const CompanyAdmin = db.Model.extend({
tableName: 'company_admin',
idAttribute: 'id',
hasTimestamps: true,
company: function () { return this.belongsTo(Company) },
});
When I console.log(this) in company_admins function, I get this data:
ModelBase {
tableName: 'company',
idAttribute: 'id',
hasTimestamps: true,
company_admins: [Function: company_admins] }
Here is my DELETE route handler:
.delete((req, res) => {
if (req.user) {
Company.forge({
id: req.user.attributes.company_id,
})
.destroy()
.then(() => {
res.status(200)
.json({
status: 200,
error: false,
});
req.logout();
}).catch((err) => {
console.log(err);
res.status(500)
.json({
status: 500,
error: err.message,
});
});
} else {
res.status(401)
.json({
status: 401,
message: 'User not authenticated',
});
}
});
I am getting this error:
TypeError: Cannot read property 'hasMany' of undefined
Anybody with the same problem?

I solved this. Error was in importing bookshelf.
The code I was using before was:
import bookshelf from 'bookshelf';
const db = bookshelf(connection);
And the correct import of bookshelf look like this:
const db = require('bookshelf')(connection);
Not quite sure why the previous code didn't work, but the modified version works well!

Related

Delete an image of a post stored in a backend folder when i delete a user of a social network application

I have a problem on my application, it is a social network. The user can create a post with a message and an image, stored in a backend images folder thanks to Multer. I use sequelize and MySql. When I delete a post, the image is indeed deleted in the images folder since I use multer in my post deletion function so everything goes well but when I delete the author, since I go through a relationship between tables so that when I delete a user, their posts are deleted. This works but in this case the images are not deleted from the folder they are stored in, since Multer is not in the loop. How do I get the images to be deleted from the images folder too in this specific case? Thank you for your help !
`
// Template for the Post table
const User = require("../models/User");
const Sequelize = require("sequelize");
const database = require("../config/database");
const Post = database.define("post", {
content: { type: Sequelize.STRING, allowNull: false },
image: { type: Sequelize.STRING, allowNull: true },
likes: { type: Sequelize.INTEGER, allowNull: false, default: 0 },
});
module.exports = Post;
// Relationship with the User table
User.hasMany(Post, { onDelete: "CASCADE", foreignKey: "userId" });
Post.belongsTo(User, { onDelete: "CASCADE" });
`
`
// deletePost function
exports.deleteOnePost = (req, res, next) => {
Post.findOne({ where: { id: req.params.id } })
.then((post) => {
if (!post) {
return res.status(404).json({
error: new Error("Post non trouvé !"),
});
}
if (post.userId === req.auth.userId || req.auth.userAdmin) {
if (post.image) {
const filename = post.image.split("/images/")[1];
fs.unlink(`images/${filename}`, () => {});
}
Post.destroy({ where: { id: req.params.id } })
.then(() => res.status(200).json({ message: "Post sans supprimé" }))
.catch((error) => res.status(400).json({ error }));
} else {
return res.status(403).json({
error: new Error("Requête non autorisée !"),
});
}
})
.catch((error) => res.status(500).json({ error }));
};
`
#Anatoly Thank you very much for your help, I'm sorry, I'm a beginner, I tried to adapt what you sent me to the method I use. I don't use the async/await method much and don't know much about it. Do you think I'm getting closer to the solution with what i made ? thanks again !
`
exports.deleteUser = (req, res, next) => {
const userId = req.params.id;
User.findOne({ where: { id: userId } }).then((user) => {
if (!user) {
return res.status(404).json({
error: new Error("User not found!"),
});
}
});
const userPosts = User.getAllPosts();
const postImages = posts.map((x) => x.image).filter((x) => x);
User.destroy({ where: { id: userId } })
.then((post) => {
Post.findOne({ where: { userId } })
.then((post) => {
Post.destroy({ where: { userId } }).then((res) =>
res.status(200).json({
message: "User is deleted",
})
);
for (const image of postImages) {
const filename = image.split("/images/")[1];
fs.unlink(`images/${filename}`, () => {});
}
})
.catch((error) =>
res.status(400).json({
error,
})
);
})
.catch((error) => res.status(500).json({ error }));
};
`
I don't see how Multer is related to a file deletion. It only helps you to store them. Any way you just need to get all posts of a certain user and delete them and a user in a transaction and then delete their images in a cycle:
// I did not use try/catch for simplicity
exports.deleteUser = async (req, res, next) => {
// get the user id somehow (req.params or the request context, for instance)
const userId = ...
const user = await User.findById(userId);
if (!user) {
return res.status(404).json({
error: new Error("User not found!"),
});
}
const userPosts = await user.getPosts();
const postImages = poists.map(x => x.image).filter(x => x);
// here 'sequelize' is the Sequelize instance, you used to register models
await sequelize,transaction(async transaction => {
await Post.destroy({ where: { userId } })
await User.destroy({ where: { id: userId } })
});
for (const image of postImages) {
const filename = image.split("/images/")[1];
fs.unlink(`images/${filename}`, () => {});
}
res.status(200).json({ message: "User is deleted" }))
}
I come back to put the fonction that works with my method, i often use ".then()" ".catch()", many thanks to Anatoly for helping me to find the solution, here is the result of my work :
exports.deleteUser = (req, res, next) => {
User.findOne({ where: { id: req.params.id } })
.then((user) => {
if (!user) {
return res.status(404).json({
error: new Error("user not found !"),
});
}
// I get all the posts of the author
Post.findAll({ where: { userId: req.params.id } })
.then((posts) => {
// I start a loop in the posts of the author to find the posts with an image
posts.forEach((post) => {
if (post.image) {
// I erase the files in the images backend directory
const filename = post.image.split("/images/")[1];
fs.unlink(`images/${filename}`, () => {});
}
// Now i can erase the author
User.destroy({ where: { id: req.params.id } })
.then(() =>
res.status(200).json({
message: "User erased !",
})
)
.catch((error) =>
res.status(400).json({
error,
})
);
});
})
.catch((error) =>
res.status(400).json({
error,
})
);
})
.catch((error) => res.status(500).json({ error }));
};

Adding JSON data to React

I have been able to pull data from an API that I built using MongoDB and Express, but am having trouble rendering the nested data to my React component.
For example, if I type in <p>{restaurant.cuisine}</p> I am able to retrieve Burgers, American, but if I try and access {restaurant.status.delivery}, I get an error that says:
Cannot read property 'delivery' of undefined.
But if I {console.log(restaurant.status} I can see the object? I tried turning the object into an array using Object.values, but that didn't work either.
The same thing happens if I try to access the nested objects in {restaurant.images} and {restaurant.geometry}.
Here's a copy of my React hook:
import { useReducer, useEffect } from 'react';
import axios from 'axios';
const ACTIONS = {
MAKE_REQUEST: 'make-request',
GET_DATA: 'get-data',
ERROR: 'error',
};
function reducer(state, action) {
switch (action.type) {
case ACTIONS.MAKE_REQUEST:
return { loading: true, restaurant: [] };
case ACTIONS.GET_DATA:
return {
...state,
loading: false,
restaurant: action.payload.restaurant,
};
case ACTIONS.ERROR:
return {
...state,
loading: false,
error: action.payload.error,
restaurant: [],
};
default:
return state;
}
}
export default function useFetchSingleRestaurant({ id }) {
const [state, dispatch] = useReducer(reducer, {
restaurant: [],
loading: true,
});
useEffect(() => {
dispatch({ type: ACTIONS.MAKE_REQUEST });
axios
.get('http://localhost:4444/restaurants/' + id)
.then((res) => {
dispatch({
type: ACTIONS.GET_DATA,
payload: { restaurant: res.data.restaurant },
});
})
.catch((e) => {
dispatch({
type: ACTIONS.ERROR,
payload: { error: e },
});
});
}, [id]);
return state;
}
I'm accessing it in my SingleRestaurant component:
function SingleRestaurant({ match }) {
const { restaurant } = useFetchSingleRestaurant({ id: match.params.id });
return (
<p>{restaurant.status.delivery}</p>
)
}
And then here's my backend setup as well:
showRestaurant = async (req, res) => {
const restaurant = await Restaurant.findById(req.params.id)
.populate({ path: 'reviews', populate: { path: 'author' } })
.populate('author');
if (!restaurant) {
req.flash('error', 'Restaurant not found.');
return res.redirect('/restaurants');
}
res.send({ restaurant });
};
Until your server request returns restaurant it will be set as the default [] that you have set.
An empty array does not have a property of status, so hence the error.
if you change your default to null:
const [state, dispatch] = useReducer(reducer, {
restaurant: null,
loading: true,
});
And then check for a value:
function SingleRestaurant({ match }) {
const { restaurant } = useFetchSingleRestaurant({ id: match.params.id });
if (!restaurant) return 'Loading'
return (
<p>{restaurant.status.delivery}</p>
)
}
You could also pass back the loading state from your hook and then do a check on that.

error when executing insert with nodejs + sequelize

I have the following function to insert a record in a MySQL database. I am using NodeJS and Sequelize.
await WeatherData.create ({
control_esp_id: req.body.control_esp_id,
variable_id: req.body.variable_id,
read_date: req.body.date,
value: req.body.value
})
.then ((weatherdata) => {
return res.status (200) .json ({
error: false,
message: "Data regarding the Line / Bay / Room successfully registered!",
weatherdata
});
})
.catch ((err) => {
console.log (err);
return res.status (400) .json ({
error: true,
code: 203,
message: err
});
})
However, the following error message is occurring when I execute the function. i have other inserts and they are working perfectly. Only from this type of model that the error is occurring to me:
TypeError: Cannot read property 'length' of undefined
at WeatherData._initValues (backend/node_modules/sequelize/lib/model.js:140:49)
at new Model (backend/node_modules/sequelize/lib/model.js:118:10)
at new WeatherData (backend/src/app/models/WeatherData.js:9:1)
at Function.build (backend/node_modules/sequelize/lib/model.js:2157:12)
at Function.create (backend/node_modules/sequelize/lib/model.js:2207:23)
at store (backend/src/app/controllers/WeatherDataController.js:62:31)
class WeatherData extends Model {
static init(sequelize) {
super.init({
read_date: DataTypes.DATE,
value: DataTypes.DOUBLE,
}, {
sequelize,
});
return this;
}
static associate(models) {
this.belongsTo(models.ControlEsp, {
foreignKey: 'control_esp_id',
as: 'control_esp',
});
this.belongsTo(models.Variable, {
foreignKey: 'variable_id',
as: 'variable',
});
}
}
sequelizePaginate.paginate(WeatherData);
export default WeatherData
Regards

Sequelize Error: Include unexpected. Element has to be either a Model, an Association or an object

I am receiving the error when I make a call to my API with a get request:
Include unexpected. Element has to be either a Model, an Association or an object.
My Models look like this:
module.exports = (sequelize, Sequelize) => {
const Productions = sequelize.define("productions", {
id: {
type: Sequelize.SMALLINT,
autoIncrement: true,
primaryKey: true
},
setupTime: {
type: Sequelize.DECIMAL(6, 3)
},
notes: {
type: Sequelize.TEXT
}
}, { timestamps: false });
return Productions;
};
module.exports = (sequelize, Sequelize) => {
const ProductionPrints = sequelize.define("productionPrints", {
id: {
type: Sequelize.SMALLINT,
autoIncrement: true,
primaryKey: true
},
compDate: {
type: Sequelize.DATE
}
}, { timestamps: false });
return ProductionPrints;
};
The relationship between the models is defined here:
db.productions = require("./productions.model.js")(sequelize, Sequelize);
db.productionprints = require("./production-prints.model.js")(sequelize, Sequelize);
db.productions.hasOne(db.productionprints, {
foreignKey: {
name: 'productionId',
allowNull: false
}
});
db.productionprints.belongsTo(db.productions, { foreignKey: 'productionId' });
And the sequelize query looks as so:
const db = require("../models");
const Productions = db.productions;
const ProductionPrints = db.productionPrints;
exports.findAll = (req, res) => {
Productions.findAll({
include: [ { model: ProductionPrints, as: 'prints' } ]
})
.then(data => {
res.send(data);
})
.catch(err => {
res.status(500).send({
message:
err.message || "An error occurred while finding the productions."
});
});
};
I have checked around for others with the issue but have had no avail with any solutions posted on those problems. Generally it was caused by typos, or error in the require paths. I have checked those and all my other includes work, just not on any of the models I include on the productions model.
Any feedback is greatly appreciated.
Error was being caused by a typo:
db.productions = require("./productions.model.js")(sequelize, Sequelize);
db.productionprints = require("./production-prints.model.js")(sequelize, Sequelize);
when this was being referenced in the assigned to a constant:
const Productions = db.productions;
const ProductionPrints = db.productionPrints;
shame on me for changing my case use:
db.productionprints != db.productionPrints
I had the same issue , this is usually caused by naming issue , to track the issue you can check one of the following places to resolve it
check if you are calling the correct model class name
when importing models becarefull not to call the file name instead of model name => the one exported
3.check if you got your association correctly by calling the exported model not the file name
check if your cases e.g users vs Users.
a bonus tip is to use same name for model and file name to avoid these issues because the moment you make them different you likely to make these mistakes
Following the answer of Kelvin Nyadzayo, i have the model.findOne(options) method with a
options.include like this:include: [ { } ] in the options parameter
The include has to have the proper syntax: [{model: Model, as: 'assciationName'}]
And the mine was empty
So this, was triggering the same error

Sequelize can create data but can't get data from Models

I'm unable to understand what actually is wrong with my code. But I know there is a problem in how I'm implementing Promise. As Shipment.findAll() returns a Promise, and I'm creating a promise again for my router to consume.
Then why createShipment is working fine, and getAllShipments is not working.
Controller for Shipment
const Worker = require ('../models').Worker;
const Shipment = require ('../models').Shipment;
function createShipment (shipmentName, shipmentStatus) {
return new Promise ((resolve, reject) => {
Shipment.create({name: shipmentName, status: shipmentStatus})
.then (shipment => resolve(shipment))
.catch (error => reject(error));
});
}
function getAllShipments () {
return new Promise ((resolve, reject) => {
Shipment.findAll()
.then(allShipments => {
console.log(allShipments);
return resolve(allShipments);
})
.catch(error => reject(error))
})
}
module.exports = {
createShipment,
getAllShipments
}
Shipment Router
var router = require('express').Router();
var Shipment = require('./../../../controllers/shipment');
router.post ('/' , (req,res) => {
Shipment.createShipment ('New Shipment', 'Pending')
.then (shipment => {
res.status(200).json({status: true, data: shipment, errors: null, msg: "Shipment Added Successfully"});
})
.catch (error => {
res.status(200).json({status: false, data: {}, errors: error, msg: "Error Creating Shipment. Please see details in Errors Object"});
});
});
router.get('/' , (req, res) => {
Shipment.getAllShipments()
.then(allShipments => {
return res.status(200).status({status: true, data: allShipments, errors: null, msg: "All Shipments fetched successfully"});
})
.catch(error => {
return res.status(200).json({status: false, data: {}, errors: error, msg: "Error Fetching Shipments. Please see details in Errors Object"});
});
})
module.exports = router;
What I'm doing wrong ? Because getAllShipments is giving my output on console but route is not sending response and just waiting and waiting.
Change :
Shipment.getAllShipments()
.then(allShipments => {
return res.status(200).status({status: true, data: allShipments, errors: null, msg: "All Shipments fetched successfully"});
})
to:
return res.status(200).json({status: true, data: allShipments, errors: null, msg: "All Shipments fetched successfully"});
Just a typo and you wrote down a status two times instead of json