mongoose Schema to sequelize model - mysql

I made one app with mongodb (mongoose as ODM) but now I want to work with MySQL (work obligation) so I took Sequelize module for that, but I really don't understand how to convert my userSchema to user model with all its méthodes (I'm working with passportJs for authentication, so I have some methods that I'm using for example setpassword ...)
Here my userSchema (mongoose) that works perfectly.
var mongoose = require('mongoose');
var crypto = require('crypto');
var jwt = require('jsonwebtoken');
var validator = require('node-mongoose-validator');
var Schema = mongoose.Schema;
var userSchema = new Schema({
name: {
type: String,
maxlength: 50
},
mail: {
type: String,
required: true,
maxlength: 50,
index: {
unique: true
}
},
hash: String,
salt: String,
{
collection: "user"
}
);
userSchema.methods.setPassword = function(password) {
this.salt = crypto.randomBytes(16).toString('hex');
this.hash = crypto.pbkdf2Sync(password, this.salt, 1000, 64).toString('hex');
};
userSchema.methods.validPassword = function(password) {
var hash = crypto.pbkdf2Sync(password, this.salt, 1000, 64).toString('hex');
return this.hash === hash;
};
userSchema.methods.generateJwt = function() {
var expiry = new Date();
expiry.setDate(expiry.getDate() + 7);
return jwt.sign({
_id: this._id,
mail: this.mail,
name: this.name,
exp: parseInt(expiry.getTime() / 1000),
}, process.env.JWT_SECRET); // secret code from .env
};
module.exports = mongoose.model('user', userSchema);
and here what I've tried with sequelize:
var crypto = require('crypto');
var jwt = require('jsonwebtoken');
var User = sequelize.define('user', {
name: Sequelize.STRING,
mail: Sequelize.STRING,
hash: Sequelize.STRING,
salt: Sequelize.STRING
});
User.methods.setPassword = function(password) {
this.salt = crypto.randomBytes(16).toString('hex');
this.hash = crypto.pbkdf2Sync(password, this.salt, 1000, 64).toString('hex');
};
User.methods.validPassword = function(password) {
var hash = crypto.pbkdf2Sync(password, this.salt, 1000, 64).toString('hex');
return this.hash === hash;
};
User.methods.generateJwt = function() {
var expiry = new Date();
expiry.setDate(expiry.getDate() + 7);
return jwt.sign({
_id: this._id,
mail: this.mail,
name: this.name,
exp: parseInt(expiry.getTime() / 1000),
}, process.env.JWT_SECRET); // DO NOT KEEP YOUR SECRET IN THE CODE!
};
module.exports = User;
I did not test that because I need to develop one other part, but I need to know that do you think about that, I feel that its full of errors
Thank you in advance

I will focus on defining a model with instance methods, some logic specific to passportjs and mongoose you might have to implement differently.
There are 2 ways to define a model.
With sequelize.define like the way you implemented, you can attach instance methods using User.prototype.yourMethod since User is a ES6 class
var User = sequelize.define('user', {
name: DataTypes.STRING,
mail: DataTypes.STRING,
hash: DataTypes.STRING,
salt: DataTypes.STRING,
/* ... */
});
User.prototype.validPassword = function(password) {
var hash = crypto.pbkdf2Sync(password, this.salt, 1000, 64).toString('hex');
return this.hash === hash;
};
/*
If you want an equivalent of User.statics.yourStaticMethod = function() {}
or User.static('yourstaticmethod', function() {})
You can use the following
*/
User.yourStaticMethod = function() {};
You can also extend Model
class User extends Model {
static yourStaticMethod() {} // in mongoose equivalent to User.statics.yourStaticMethod = function() {}
validPassword(password) {
var hash = crypto.pbkdf2Sync(password, this.salt, 1000, 64).toString('hex');
return this.hash === hash;
}
};
User.init({
name: DataTypes.STRING,
mail: DataTypes.STRING,
hash: DataTypes.STRING,
salt: DataTypes.STRING,
/* ... */
}, {
sequelize,
modelName: 'user'
});

Related

how to store logs into mysql db using express-winston in Node.js

I am trying to store logs into mysql db(with Sequelize) using express-winston in Node.js.
According to doc(https://www.npmjs.com/package/winston-sql-transport), I should do the following:
const { Logger } = require('winston');
const { SQLTransport } = require('./../lib/winston-sql-transport');
const logger = new Logger({
transports: [
new SQLTransport({
tableName: 'winston_logs',
})]
});
module.exports = logger;
I apply above code in app.js, but not successful.
app.js
const { SQLTransport } = require('./../lib/winston-sql-transport'); // //Error: Cannot find module './../lib/winston-sql-transport'
const mysqlOption = {
tableName: 'winston_logs'
}
app.use(
expressWinston.logger({
transports: [
new SQLTransport(mysqlOption)
],
format: winston.format.combine(
winston.format.timestamp({ format: timezoned }),
winston.format.json(),
),
metaField: null,
expressFormat: true,
statusLevels: false,
level: function (req, res) {
var level = "";
if (res.statusCode < 400) { level = "info"; }
if (res.statusCode >= 400) { level = "warn"; }
if (res.statusCode >= 500) { level = "error"; }
return level;
}
})
)
I tried to change the import to const { SQLTransport } = require('winston-sql-transport');
Resulting error:
Error: You have to define client
at new SQLTransport (/server/node_modules/winston-sql-transport/lib/winston-sql-transport.js:40:13)
I found that people rarely talk about this package.
So I wonder if there is any way to do it?
Update 1:
I updated mysqlOption as suggested by the comment
const { SQLTransport } = require('winston-sql-transport');
const mysqlOption = {
tableName : "winston_logs",
client: 'mysql',
connection: {
host: '127.0.0.1:3306',
user: 'root',
password: '',
database: 'mydb'
}
}
The logs successfully appear in Console, but nothing store in my database.
Update 2:
Since I am using Sequelize, I'm going to provide the schema for the logs table
// See http://docs.sequelizejs.com/en/latest/docs/models-definition/
// for more of what you can do here.
const Sequelize = require('sequelize');
const DataTypes = Sequelize.DataTypes;
module.exports = function (app) {
const sequelizeClient = app.get('sequelizeClient');
const logs = sequelizeClient.define('winston_logs', {
id: {
type: DataTypes.INTEGER(10),
allowNull: false,
autoIncrement: true,
primaryKey: true
},
level: {
type: DataTypes.STRING(45),
allowNull: false
},
message: {
type: DataTypes.TEXT,
allowNull: false
},
meta: {
type: DataTypes.STRING(255),
allowNull: false
},
hostname: {
type: DataTypes.STRING(255),
allowNull: false
},
timestamp: {
type: DataTypes.DATE,
allowNull: false
},
}, {
hooks: {
beforeCount(options) {
options.raw = true;
}
}
});
logs.associate = function (models) {
};
return logs;
};
Finally giving up to store logs in mysql db, since mysql transport package seems a bit outdated.
Instead, I store the logs locally, using winston-daily-rotate-file
It can set the frequency of rotation, Maximum size of the file etc.
link: https://github.com/winstonjs/winston-daily-rotate-file

Sequelize: refer a foreign key between 2 models

I'm trying to refer a foreign key between 2 models. but I'm getting this error:
throw new AssociationError(`${source.name}.belongsToMany(${target.name}) requires through option, pass either a string or a model`);
AssociationError [SequelizeAssociationError]: Order.belongsToMany(User) requires through option, pass either a string or a model
I check some similar questions but it didn't help.
I started working with Sequelize today so please give an example to answer.
db.js
const fs = require("fs");
const path = require("path");
const Sequelize = require("sequelize");
const basename = path.basename(module.filename);
const db = {};
const sequelize = new Sequelize("name", "user", "password", {
host: "localhost",
dialect: "mysql",
logging: false,
});
fs.readdirSync(__dirname).filter(file =>
(file.indexOf(".") !== 0) &&
(file !== basename) &&
(file.slice(-3) === ".js"))
.forEach(file => {
const model = sequelize.import(path.join(__dirname, file));
db[model.name] = model;
});
Object.keys(db).forEach(modelName => {
if (db[modelName].associate) {
db[modelName].associate(db);
}
});
db.sequelize = sequelize;
db.Sequelize = Sequelize;
module.exports = db;
user.js
const Sequelize = require("sequelize");
module.exports = (sequelize, DataTypes) => {
const User = sequelize.define("User",
{
id: {
type: Sequelize.BIGINT,
autoIncrement: true,
allowNull: false,
primaryKey: true,
},
email: Sequelize.STRING(60),
password: Sequelize.STRING(60),
fullName: {
type: Sequelize.STRING(60),
allowNull: true,
},
},
);
User.associate = function (models) {
models.User.belongsToMany(models.Order);
};
return User;
};
order.js
const Sequelize = require("sequelize");
module.exports = (sequelize, DataTypes) => {
const Order = sequelize.define("Order",
{
id: {
type: Sequelize.BIGINT,
autoIncrement: true,
allowNull: false,
primaryKey: true,
},
title: Sequelize.STRING(60),
orderNumber: Sequelize.STRING(60),
status: Sequelize.STRING(60),
amount: Sequelize.STRING(60),
trackingCode: Sequelize.STRING(60),
},
);
Order.associate = function(models) {
models.Order.belongsToMany(models.User);
};
return Order;
};
when I use lowercase for define name like this:
const User = sequelize.define("user",
{
...
}
);
const User = sequelize.define("order",
{
...
}
);
I get this error:
TypeError: Cannot read property 'belongsToMany' of undefined
sequelize: 5.21.6
I answered similar question here. Please take a look at it. Also pay attention to comments and to the link at the end of the discussion.
If you'll have any questions after reading feel free to ask them in comments to this answer.

SequelizeEagerLoadingError: (parent) is not associated to (child)!

I am building an application using sequelize. I currently have 3 tables; a User, a Tour, and a Location. The Location has a n:1 relationship with the Tour. The Tour has a n:1 relationship with the user.
Without the User association, the other two tables work fine. Once I add in the user association (and I have tried to do so through a migration AND by dropping and then recreating my entire database), I get a SequelizeEagerLoadingError: Location is not associated with Tour!
Here are my models:
module.exports = function(sequelize, DataTypes) {
var Location = sequelize.define("Location", {
title: {
type: DataTypes.STRING,
allowNull: false
},
description: {
type: DataTypes.TEXT,
allowNull: false,
validate: {
len: [500]
}
},
address: {
type: DataTypes.TEXT,
allowNull: false
}
});
Location.associate = function(models) {
Location.belongsTo(models.Tour, {
onDelete: "cascade"
});
};
return Location;
};
module.exports = function(sequelize, DataTypes) {
var Tour = sequelize.define("Tour", {
title: {
type: DataTypes.STRING,
allowNull: false
},
description: {
type: DataTypes.TEXT,
allowNull: false,
validate: {
len: [1, 1000]
}
},
neighborhood: {
type: DataTypes.STRING,
allowNull: false
},
URL: {
type: DataTypes.TEXT,
allowNull: false,
validate: {
len: [1, 1000]
}
},
numberOfStops: DataTypes.INTEGER,
duration: {
type: DataTypes.INTEGER,
allowNull: false
},
tags: DataTypes.STRING
});
Tour.associate = function(models) {
Tour.hasMany(models.Location);
};
Tour.associate = function(models) {
Tour.belongsTo(models.User);
};
return Tour;
};
var bcrypt = require("bcrypt-nodejs");
module.exports = function(sequelize, DataTypes) {
var User = sequelize.define("User", {
name: {
type: DataTypes.STRING,
allowNull: false
},
email: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
validate: {
isEmail: true
}
},
password: {
type: DataTypes.STRING,
allowNull: false
}
});
User.prototype.validPassword = function(password) {
return bcrypt.compareSync(password, this.password);
};
User.hook("beforeCreate", function(user) {
user.password = bcrypt.hashSync(
user.password,
bcrypt.genSaltSync(10),
null
);
});
User.associate = function(models) {
User.hasMany(models.Tour);
};
return User;
};
And here is the include statement where it is failing, and where we establish the link with the tourId to the location:
app.get("/tour/:id", function(req, res) {
db.Tour.findOne({
where: { id: req.params.id },
include: [db.Location]
}).then(function(tour) {
res.render("tour", {
tour: tour
});
});
});
var API = {
saveTour: function(tour) {
return $.ajax({
headers: {
"Content-Type": "application/json"
},
type: "POST",
url: "api/tours",
data: JSON.stringify(tour)
});
},
saveLocations: function(locations) {
return $.ajax({
headers: {
"Content-Type": "application/json"
},
type: "POST",
url: "api/locations",
data: JSON.stringify(locations)
});
},
getUserId: function() {
return $.ajax({
type: "GET",
url: "api/user_data"
});
}
};
var tour = {
Users: thisUser.getUserId(),
title: title,
description: description,
neighborhood: neighborhood,
URL: URL,
duration: duration,
tags: tags
};
// console.log(tour);
if (!errors.length) {
// Post our tour to the Tours table, then reveal the form and set our local tour object.
API.saveTour(tour).then(function(tour) {
document.getElementById("submit-tour").remove();
document.getElementById("tourstopssection").style.display = "block";
thisTour.setId(tour.id);
});
}
}
// Function takes in the newly created tour object, grabs DOM values for each.
function addTourLocations(e) {
e.preventDefault();
// Grab and process all of our tour stops.
var locationElements = document.getElementsByClassName("tourstop");
var areStopErrors = false;
var locations = [];
// Loop over every location element on the DOM.
for (var j = 0; j < locationElements.length; j++) {
var children = locationElements[j].children;
// Initialize this location with the tour id; we'll pass in data...
var thisLocation = {
TourId: thisTour.getId()
};
// ... by looping over the DOM children and grabbing their form values.
for (var k = 0; k < children.length; k++) {
if (
children[k].classList.value.includes("stoptitle") &&
children[k].value
) {
var stopTitle = children[k].value;
thisLocation.title = stopTitle;
}
if (
children[k].classList.value.includes("stopaddress") &&
children[k].value
) {
var stopAddress = children[k].value;
thisLocation.address = stopAddress;
}
if (
children[k].classList.value.includes("stopdescription") &&
children[k].value
) {
var stopDescription = children[k].value;
thisLocation.description = stopDescription;
}
}
// Push this location into our locations array.
locations.push(thisLocation);
Finally, this is how the app/db are synced:
require("dotenv").config();
var express = require("express");
var session = require("express-session");
var exphbs = require("express-handlebars");
var helpers = require("./lib/helpers");
var db = require("./models");
var passport = require("./config/passport");
var app = express();
var PORT = process.env.PORT || 3000;
// Middleware
app.use(express.urlencoded({ extended: true }));
app.use(express.json());
app.use(express.static("public"));
var hbs = exphbs.create({
defaultLayout: "main",
helpers: helpers // Require our custom Handlebars helpers.
});
//Sessions are used to keep track of our user's login status
app.use(
session({ secret: "keyboard cat", resave: true, saveUninitialized: true })
);
app.use(passport.initialize());
app.use(passport.session());
app.use(function(req, res, next) {
res.locals.user = req.user; // Set a local variable for our user.
next();
});
// Handlebars
app.engine("handlebars", hbs.engine);
app.set("view engine", "handlebars");
// Routes
require("./routes/apiRoutes")(app);
require("./routes/htmlRoutes")(app);
var syncOptions = { force: false };
// If running a test, set syncOptions.force to true
// clearing the `testdb`
if (process.env.NODE_ENV === "test") {
syncOptions.force = true;
}
// Starting the server, syncing our models ------------------------------------/
db.sequelize.sync(syncOptions).then(function() {
app.listen(PORT, function() {
console.log(
"==> 🌎 Listening on port %s. Visit http://localhost:%s/ in your browser.",
PORT,
PORT
);
});
});
module.exports = app;
I've been googling for four days....help!
Try adding this to your associations, also why are you defining twice the association function on Tour?
module.exports = function(sequelize, DataTypes) {
var Location = sequelize.define("Location", {
//
});
Location.associate = function(models) {
Location.belongsTo(models.Tour, { as:'Tour', foreignKey:'tourId', onDelete: "cascade"});
};
return Location;
};
module.exports = function(sequelize, DataTypes) {
var Tour = sequelize.define("Tour", {
//
});
Tour.associate = function(models) {
Tour.hasMany(models.Location, { as: 'Locations', foreignKey: 'tourId'});
Tour.belongsTo(models.User, { as: 'User', foreignKey: 'userId' });
};
return Tour;
};
module.exports = function(sequelize, DataTypes) {
var User = sequelize.define("User", {
//
});
User.associate = function(models) {
User.hasMany(models.Tour, {as: 'Tours', foreignKey: 'userId'});
};
return User;
};
And add the same on the query.
db.Tour.findOne({
where: { id: req.params.id },
include: [{
model: db.Location,
as: 'Locations'
}]
}).then(function(tour) {
res.render("tour", {
tour: tour
});
});
I figured it out - the fact that I had defined the association on the tours model twice was breaking everything. Once I combined them as mentioned above, everything worked perfectly!
One other thing to note - sequelize automatically assigns the foreign key and the alias, so I left that part out.

Sequelize findAll is not a function

I'm making a project with Sequelize and I'm stucked in this step. The problem is that when I try to log in and the passport-local code is executed, when it reaches the User.findAll(...) it throws that findAll is not a function.
If I make console.log(User) it shows [function].
My structure:
/config/config.js
/config/passport.js
/models/index.js
/models/nuke_users.js (generated by sequelize-auto)
/index.js
config.js:
//Setting up the config
var Sequelize = require('sequelize');
var sequelize = new Sequelize('rocarenav2', 'root', '123456', {
host: "localhost",
port: 3306,
dialect: 'mysql'
});
module.exports = sequelize;
passport.js:
// config/passport.js
// load all the things we need
var LocalStrategy = require('passport-local').Strategy;
// load up the user model
var User = require('../models/nuke_users');
var crypto = require('crypto');
function hashPasswordForNuke(password) {
return md5password = crypto.createHash('md5').update(password).digest('hex');
}
// expose this function to our app using module.exports
module.exports = function(passport) {
// =========================================================================
// passport session setup ==================================================
// =========================================================================
// required for persistent login sessions
// passport needs ability to serialize and unserialize users out of session
// used to serialize the user for the session
passport.serializeUser(function(user, done) {
done(null, user.id);
});
// used to deserialize the user
passport.deserializeUser(function(id, done) {
User.findById(id, {})
.then(function (user) {
done(err, user);
})
.catch(function (error){
done(error);
});
});
// =========================================================================
// LOCAL LOGIN =============================================================
// =========================================================================
// we are using named strategies since we have one for login and one for signup
// by default, if there was no name, it would just be called 'local'
passport.use('local-login', new LocalStrategy({
// by default, local strategy uses username and password, we will override with email
usernameField : 'email',
passwordField : 'password',
passReqToCallback : true // allows us to pass back the entire request to the callback
},
function(req, email, password, done) { // callback with email and password from our form
User.findAll({
where: {
'user_email': email
}
}).then(function (user) {
if(!user)
return done(null, false, req.flash('loginMessage', 'No user found.')); // req.flash is the way to set flashdata using connect-flash
// if the user is found but the password is wrong
if ((user.user_password).localeCompare(hashPasswordForNuke(password)) === -1)
return done(null, false, req.flash('loginMessage', 'Oops! Wrong password.')); // create the loginMessage and save it to session as flashdata
return done(null, user);
})
.catch(function (error){
done(error);
});
}));
};
models/index.js
'use strict';
var fs = require('fs');
var path = require('path');
var Sequelize = require('sequelize');
var basename = path.basename(module.filename);
var config = require(__dirname + '/../config/config');
var db = {};
//Create a Sequelize connection to the database using the URL in config/config.js
var sequelize = config;
//Load all the models
fs
.readdirSync(__dirname)
.filter(function(file) {
return (file.indexOf('.') !== 0) && (file !== basename) && (file.slice(-3) === '.js');
})
.forEach(function(file) {
var model = sequelize['import'](path.join(__dirname, file));
db[model.name] = model;
});
Object.keys(db).forEach(function(modelName) {
if (db[modelName].associate) {
db[modelName].associate(db);
}
});
//Export the db Object
db.sequelize = sequelize;
db.Sequelize = Sequelize;
module.exports = db;
/models/nuke_users.js
/* jshint indent: 2 */
module.exports = function(sequelize, DataTypes) {
return sequelize.define('nuke_users', {
user_id: {
type: DataTypes.INTEGER(11),
allowNull: false,
primaryKey: true,
autoIncrement: true
},
username: {
type: DataTypes.STRING,
allowNull: false,
defaultValue: "",
references: {
model: 'reps_table',
key: 'PostName'
}
},
user_email: {
type: DataTypes.STRING,
allowNull: false,
defaultValue: ""
},
user_avatar: {
type: DataTypes.STRING,
allowNull: false,
defaultValue: ""
},
user_password: {
type: DataTypes.STRING,
allowNull: false,
defaultValue: ""
}
}, {
tableName: 'nuke_users'
});
};
/index.js
...
var models = require('./models/');
...
So, what am I doing wrong?
The nuke_users module is exporting a function that, when called, returns the Model. Because you aren't calling this function, it is not returning the Model, and thus the function you are looking for does not exist.
To call this exported function you would need to pass in the sequelize instance and DataTypes, as so:
var User = require('../models/nuke_users')(sequelize, DataTypes);
In your case you are using a loader in the index.js file, and it is exporting the db object which contains the models keyed by their name.
var models = require('../models'); // loads index.js
var User = models.nuke_user; // the model keyed by its name
User.findOne(...); // search the model
Instead of returning the model, export it from NukeUser.js:
const NukeUser = sequelize.define('nuke_users', {
// ...
});
module.exports = NukeUser;
Then in index.js:
const NukeUser = require('../models/NukeUser');
NukeUser.findAll() //.then() ...
You need to check the models route in the collections, for example, in my case, I had read() as a method in my collections like that:
async read(id) {
try {
if(id) {
return await this.model.findOne({where: {id: id}});
} else {
return await this.model.findAll();
}
} catch (e) {
console.error(`Error in reading data with the id: ${id}`);
}
}
So, findAll() wouldn't work. Instead, I used read()

Create row including association with hasOne in Sequelize

I'm testing different ORM's for Node.js and got stuck at this error:
Possibly unhandled TypeError: undefined is not a function
# person.setUser(user);
Tried person.setUsers, user.setPerson and user.setPeople. Also tried console.log to find the function with no luck.
What am I doing wrong?
var config = require('./config.json');
var Sequelize = require('sequelize');
var sequelize = new Sequelize(config.connection, {
define: {
freezeTableName: true,
underscoredAll: true,
underscored: true
}
});
var Person = sequelize.define('person', {
first_name: Sequelize.STRING,
last_name: Sequelize.STRING
});
var User = sequelize.define('user', {});
Person.hasOne(User);
sequelize.sync().then(run);
function run() {
var person = Person.create({ first_name: 'Markus', last_name: 'Hedlund' });
var user = User.create();
person.setUser(user);
}
I think what you want to do is
Person.create({ first_name: 'Markus', last_name: 'Hedlund' }).then((person) => {
User.create().then((user) => {
person.setUser(user);
});
});
Although dege answer is correct I would write it with a nice promise chain:
Person.create({ first_name: 'Markus', last_name: 'Hedlund' })
.bind({})
.then(function(person){
this.person = person;
return User.create()
})
.then(function(user){
return this.person.setUser(user);
});