Sequelize afterValidate hook called twice when updating - javascript

I'm using Sequelize in a Node backend to access a MySQL database and I realized that, depending on how you update an object, the afterValidate hook is called once or twice. I'll explain it better with an example:
We have a Shop entity defined by the following Sequelize model:
module.exports = (sequelize, DataTypes) => {
const Shop = sequelize.define('Shop', {
name: DataTypes.STRING,
address: DataTypes.STRING
}, {
freezeTableName: true,
hooks: {
afterValidate: (shop, options) => {
shop.name += '_HOOKED!'; // Just for testing
}
}
});
};
I've added a couple of shops using db.Shop.create() that have id 1 and 2. Now, I'm trying these 2 options that Sequelize provides to update a shop object:
Option 1
db.Shop.update({ name: 'Coffee Shop TEST' },
{ where: { id: shop.id } }).then((data) => {
console.log("All good!!");
}).catch((error) => {
console.log("No good: ", error);
});
Option 2
db.Shop.findOne({ where: { id: 1 } }).then((shop) => {
shop.update({ name: 'Coffee Shop TEST' }).then((data) => {
console.log("All good!!: ");
}).catch((error) => {
console.log("No good: ", error);
});
}).catch((error) => {
console.error("Error al actualizar: ", error);
});
If I use the first option, the shop is updated correctly and the afterValidate hook is called once, so the name now is: Coffee Shop TEST_HOOKED!.
However, if I use the second option, the hook is called twice so the name is set incorrectly to Coffee Shop TEST_HOOKED!_HOOKED!.
Why is this happening? Thanks in advance,

I might be a little late but for anyone who comes across this problem, here is the solution:
You can set options.validate = false in the beforeUpdate hook in your model.
For some reason, Sequelize runs the afterValidate hook twice when updating objects...

I tried to update an object that doesn't have any associations with other models and I got the same result: option 1 calls afterValidate hook once and option 2 calls it twice. Also, I tried to go back to sequelize version 5.1.0 and nothing changed.
So, it seems that sequelize works this way for whatever reason. I'll keep in mind that, if I want to update an object that has hooks on it, I'll use the code in option 1 to avoid problems like hashing the password twice.
Cheers,

Related

Posting result data to a website (like API or a telegram bot) after a test on Cypress

I've finished writing my first Cypress test. Everything is good except I'm struggling to post the result data to a website. Because I want to send the result data and also if any errors occurs the result screenshot to our coworker telegram group.
For the last two days I've tried everything and couldn't find any solution.
I've tried those in my test script (cypress/integration/test.js);
Cypress.on('test:after:run', (test, runnable) => {
console.log('test,runnable', test, runnable)
const details = {
projectKey: Cypress.env('zephyr-project-key'),
testName: test.invocationDetails.relativeFile,
status: test.status,
error: runnable.err.message,
retries: runnable.retries.length,
duration: test.wallClockDuration,
startTime: test.wallClockStartedAt
}
cy.request('POST', 'http://mywebsite.com/notify.php', { body: details })
fetch('http://mywebsite.com/notify.php')
})
Also this didn't work (cypress/plugins/index.js);
module.exports = (on, config) => {
// `on` is used to hook into various events Cypress emits
// `config` is the resolved Cypress config
on('after:run', (results) => {
if (results) {
// results will be undefined in interactive mode
console.log(results.totalPassed, 'out of', results.totalTests, 'passed')
fetch('http://mywebsite.com/notify.php');
}
})
}
Edit: This is day 3 and I still couldn't solve this. What I've seen from Cypress help page is that cy.task() calls do not fire in 'test:after:run' event block;
https://github.com/cypress-io/cypress/issues/4823
I've seen some telegram groups who can do what I'm trying to do. All I need is to be able to get the results and post it to my website.
The third parameter to cy.request() is body, you don't have to wrap it.
Cypress.on('test:after:run', (test, runnable) => {
const details = {
projectKey: Cypress.env('zephyr-project-key'),
testName: test.invocationDetails.relativeFile,
status: test.status,
error: runnable.err?.message, // need err?.message if there is no error
retries: runnable.retries.length,
duration: test.wallClockDuration,
startTime: test.wallClockStartedAt
}
cy.request('POST', 'http://mywebsite.com/notify.php', details) // don't wrap details
.then(res => expect(res.status).to.eq(201)) // confirm result
})

Braintree Node.js cannot get transaction.sale to work

I am building a reactjs app that among others will include Braintree Dropin UI integration. So far, I have managed to make the UI show up and send a payload to the back end. However, I cannot get the gateway.transaction.sale() part to work. Here is my code's relevant parts:
When the user clicks the pay button, this is fired:
instance.requestPaymentMethod().then(function (payload) {
console.log(payload);
completePayment(amount, payload.nonce, userId, sessionId).then((result) => {
console.log( result );
});
}).catch(function (err) {
alert(err.message);
});
And this is the code that should handle the transaction:
return gateway.transaction.sale({
amount: amount,
paymentMethodNonce: nonce,
customFields: {
session_id: sessionId,
user_id: userId
},
options: {
submitForSettlement: true
}
}).then(function (result) {
if (result.success) {
console.log('Transaction ID: ' + result.transaction.id);
} else {
console.error(result.message);
}
}).catch(( error ) => {
alert(error);
});
Every time this function is fired, I get this error from catch:
TypeError: can't assign to property "success" on :not an object
Can anyone point me in the right direction?
Please note that I am not very familiar with react, node etc so my code may not be the best thing around...
Check these points:
make sure you assigned your environment to the sandbox (braintree.Environment.Sandbox);
double check (merchantId, publicKey, and privateKey).

How to perform two firebase database operation on different nodes in single request/function?

I am a react-native developer and new to firebase. I am performing firebase realtime database operation, have a look at code below;
firebase.database().ref('events/wedding/items').push(object).then((data) => {
//success callback
dispatch(addPendingInvoice({ ...invoice, id: data.key }))
Alert.alert('Successfully added to Invoices', 'Please go to invoice section to clear first and continue.', [{ text: 'Ok' }])
}).catch((error) => {
//error callback
Alert.alert("Can't book package.", 'Please check your internet connection!', [{ text: 'OK', style: 'destructive' }])
})
Now, I wish to push another object to another node events/wedding/packages right after this firebase database function above. I can use another function inside then callback in above firebase functions. This is not a professional way to do this.
Is there any way to do this?
You can use the update() method to "simultaneously write to specific children of a node without overwriting other child nodes". Note that "simultaneous updates made this way are atomic: either all updates succeed or all updates fails", see the doc.
So in your case you would do along the following lines:
var newNodeKey = firebase.database().ref().child('events/wedding/items').push().key;
var updates = {};
updates['events/wedding/items/' + newNodeKey] = { foo: "bar" };
updates['events/wedding/packages/' + newNodeKey] = { bar: "foo" };
firebase.database().ref().update(updates)
.then(() => {
// The two writes are completed, do whatever you need
// e.g. dispatch(...);
});
All Firebase operations return a promise so you can use Promise.all() to run them all simultaneously.
Promise.all([
firebase.database().ref(reference).set({}),
firebase.database().ref(reference2).set({})
]).then(() => {
console.log("Operations Successful")
}).catch((e) => console.log(e))
You can also push all your operations to an array and then pass that array in Promise.all()

MERN Stack: How to prevent a User from registering (being added to an array) multiple times?

Semi-new developer building a project using the MERN stack.
The app has two models, one for Users and one for Tournaments. Tournament model has an attribute called participants which is an array.
I wrote an Express backend route so that a User can register for Tournaments.participants[].
This looks like:
router.post('/:id', (req, res) => {
Tournament.findById(req.params.id)
.then(tournament => {
tournament.participants.push(req.body);
return tournament.save();
})
.then(savedTournament => res.json(savedTournament))
.catch(err => res.json(err));
});
However, a User can just keep clicking Sign Up and I'd have a bunch of duplicate users, so I'm trying to write a conditional that will disable Sign Up if the user is already in Tournament.participants[].
I tried writing a conditional inside the Express route using Array.includes(req.body) but couldn't hack it.
Looked something like
Tournament.findById(req.params.id)
.then(tournament => {
if (tournament.participants.includes(req.body) {
return res.status(400).json({ msg: "This user already signed up for this tournament" });
} else {
tournament.participants.push(req.body);
return tournament.save();
}
})
.then(savedTournament => res.json(savedTournament))
.catch(err => res.json(err));
I tried different variations as well, like if (tournament.participants.includes(!req.body)) then push(req.body), etc.
And I also tried just rendering a different button if the participants.includes(user) but I believe this should be done on the backend anyway.. I'm open to suggestions.
Can anyone help me out?
In general, you can't use the native comparison operators with objects, includes included:
const foo = { id: 1 };
const bar = [{ id: 1 }];
console.log(bar.includes(foo)); // outputs `false`
You should use some kind of item id in order to check if its already exists:
function isIdIncluded(arr, id) {
return arr.some(x => x.id === id)
}
const foo = { id: 1 };
const bar = [{ id: 1 }];
console.log(isIdIncluded(bar, 1)); // outputs `true`
I assume you are keeping the users's _id in the participants array, and your tournament schema is similar to this:
const tournamentSchema = new mongoose.Schema({
name: String,
participants: Array,
});
Now if you send a request with this body:
{
"user": "5e97255a342f395774f30162" //the user id who wants to participate
}
You can use this code (I just changed the req.body to req.body.user)
Tournament.findById(req.params.id)
.then((tournament) => {
if (tournament.participants.includes(req.body.user)) {
return res.status(400).json({ msg: "This user already signed up for this tournament" });
} else {
tournament.participants.push(req.body.user);
return tournament.save();
}
})
.then((savedTournament) => res.json(savedTournament))
.catch((err) => res.status(500).json(err));
Now when a user first time participates a tournament, the document will be like this:
{
"participants": [
"5e97255a342f395774f30162"
],
"_id": "5e97255a342f395774f30161",
"name": "Chess Tournament"
}
And when the same user tries, the response will be like this with a 400 status code.
{
"msg": "This user already signed up for this tournament"
}
Also please note that, the user id shouldn't be send in the request body, but it must be the user's id who is logged in.

MeteorJS Infinite loop when using meteor call and meteor method

I have a sample code that goes like this:
Client Helper:
getUsername: function (userId) {
Meteor.call("getUsername", userId, function (err, result) {
if(!err) {
Session.set("setUsername", result);
else {
console.log(err);
}
});
return Session.get("setUsername");
}
Server
Meteor.methods({
"getUsername": function (userId) {
var x = Meteor.users.find({_id: userId}, {fields: {username:1}}).fetch()[0];
return x.username;
}
});
The result of this code is an infinite loop of username passing to the client. Is there a way to stop the loop and pass only the data that is needed on the client? I believe the reactivity is causing the data to loop infinitely and I am not sure how to stop it. I tried using "reactive":false on my query in the server but it does not work.
If you want to access username everywhere in client templates (so thats why you put it into session), I would not set it in template helper. I would set it on startup and get username from session in template helpers (without calling server method)
If you need username just in one template, so you want to return its value from your template helper, do not put it into session, just return it in your server method callback.
Based on your sample code, I assume, you have a set of posts and you are retrieving user name based on user id for each post. Then instead of doing it this way, you should use publish composite package to publish related users as well.
Meteor.publishComposite('getPosts', function (postIds) {
return [{
find: function() {
return Posts.find({ _id: { $in: postIds }});
// you can also do -> return Posts.find();
// or -> return Posts.find({ /* or what ever your selector is to get the posts you need*/ });
},
children: [{
find: function(post) {
return Meteor.users.find({
id: post.userId //or the correct field in your post document to get user id
}, {
fields: {
"profile": 1
}
});
}
}}
}]
});
This way your publication will take care of publishing related users along with posts. You don't need to use methods and call them each time.

Categories