How to pass id to its associated records with Sequelize - postgresql

I'm building an app with Express on backend, Postgres for db and Sequelize for ORM.
I have 3 associated models:
Post
Event
Publishing, belongs to Post, belongs to Event
When I publish my Post, I update its state to 2, I need to create an Event and Publishing. Publishing will have the postId and eventId, among other things that I'm passing with a query. I tried the following code, it changes the state of the Post, creates an Event and Publishing, but they are not related to each other.
publishPost(req) {
return new Promise((resolve, reject) => {
async.parallel({
changeState: (callback) => {
Post.findAll({
where: { id: req.query.post_id },
attributes: ['id', 'state']
})
.then((updateState) => {
updateState.forEach((postState) => {
postState.updateAttributes({ state: 2 });
});
})
.then((updatedState) => {
callback(null, updatedState);
});
},
createEvent: (callback) => {
Event.create({
instructions: req.query.instructions,
})
.then((createdEvent) => {
callback(null, createdEvent);
});
},
createPublishing: (callback) => {
Publishing.create({
start_date: req.query.startDate,
end_date: req.query.endDate,
})
.then((createdPublishing) => {
callback(null, createdPublishing);
});
}
}, (error, result) => {
resolve(result);
});
});
}
How can I pass the IDs of the two records to the third model?

There are several problems with your implementation!
First of all, your promise never rejects.
Despite of that, you don't actually need to create a promise or use async for this, neither do you want them to run in parallel: If creating a Publishing record needs information about the Event, then you'd want to create first the event, so that you have its id, THEN pass it in the input for the publishing.
Another important thing, take a look at this piece of code:
.then((updateState) => {
updateState.forEach((postState) => {
postState.updateAttributes({ state: 2 });
});
})
.then((updatedState) => {
callback(null, updatedState);
});
Inside the first then, you are making multiple updates, which are promises. They will be dispatched and you never get their values back. Let me explain:
Think if you have just one update to make. It would be like this:
.then((updateStates) => {
return updateStates[0].updateAttributes({ state: 2 });
})
See, we are returning a promise (in this case the update), so the next then will only be called when the update resolves.
If we do this:
.then((updateStates) => {
updateStates[0].updateAttributes({ state: 2 });
})
It will dispatch the update (which takes time) but because you didn't return it, it will pass through and return nothing. Check this out:
var promise1 = new Promise(function(resolve, reject) {
setTimeout(function(){
resolve('foo')
}, 2);
});
var promise2 = new Promise(function(resolve, reject) {
setTimeout(function(){
resolve('foo2')
}, 2);
});
promise1
.then(function(result){
promise2
})
.then(function(result){
console.log(result) //will print undefined
})
promise1
.then(function(result){
return promise2
})
.then(function(result){
console.log(result) //will print foo2
})
So, you are calling multiple updates without getting their values back; if they fail, you'd never know.
Just one more thing: if something goes wrong along the way, you probably want to rollback all the changes made so far, for that, you should use transactions.
Maybe you should try something like this (not tested):
return Post.sequelize.transaction(function (t) {
Post.findAll({
where: { id: req.query.post_id },
attributes: ['id', 'state']
})
.then((updatedStates) => {
var promises = []
updatedStates.forEach((postState) => {
promises.push(postState.updateAttributes({ state: 2 }, {transaction: t}));
});
return Promise.all(promises);
})
.then((results) => {
return Event.create({
instructions: req.query.instructions,
}, {transaction: t})
})
.then((createdEvent) => {
return Publishing.create({
post_id: req.query.post_id,
event_id: createdEvent.id, //or event_id, depends on your model
start_date: req.query.startDate,
end_date: req.query.endDate,
}, {transaction: t})
});
}).then(function (result) {
// Transaction has been committed
// result is whatever the result of the promise chain returned to the transaction callback
}).catch(function (err) {
// Transaction has been rolled back
// err is whatever rejected the promise chain returned to the transaction callback
});

Related

Unable to return value from nano.view callback

Unable to store value outside of callback scope
I have tried declaring an array, an object and an empty variable outside of the callback scope and nothing is working.
router.post('/login', async (req, res, next) => {
try {
const user = await users.view('viewEmailandPassword', 'email', {keys: [`${req.body.email}`], include_docs: true},
function(err, body) {
if (!err) {
body.rows.forEach(function(doc) {
console.log(doc.value)
// return doc.value
});
}
});
console.log(user) <--- nothing is returned
}
catch(err){
next(err)
console.err(err, "this is the error")
}
})
I get an output of "undefined"
The problem here is that you're trying to use callback + promises. You need to either choose one or the other.
Here's the implementation using Promises (with async/await)
router.post('/login', async (req, res, next) => {
try {
const body = await users.view('viewEmailandPassword', 'email', {keys: [`${req.body.email}`], include_docs: true});
// Prints all the row values
body.rows.forEach(doc => console.log(doc.value));
// Let's say you want the first row
if(body.rows.length > 0){
console.log(body.rows[0].value);
} else {
console.log("Not value returned from the view");
}
}
catch(err){
next(err)
console.err(err, "this is the error")
}
})

Issue with getting information from mongodb

Ok so here is what I am trying to do, I have a Item collection that holds a users items along with price quantity and description. I have also created a collection called soldItems that will store information about a sold item once the user inputs the required data and submits the form. I am not sure if I am doing this correctly to begin with and I have been trying to research this but I don't exactly know what I am looking for.
Here is the code I have so far
router.post("/item/:id", middleware.isLoggedIn, (req, res) => {
Item.findById(req.params.id, (err, item) => {
if(err) {
console.log(err);
}
var soldItem = {
id: req.params.id,
item: req.item.name,
askPrice: req.item.price
};
var soldPrice = req.body.soldPrice;
var soldQuantity = req.body.soldQuantity;
var itemSold = {
soldItem: soldItem,
soldPrice: soldPrice,
soldQuantity: soldQuantity
};
SoldItem.create(itemSold, (err, newlyCreated) => {
if (err) {
console.log(err);
} else {
item.soldItems.push(newlyCreated);
item.save();
req.flash("success", "Item Sold");
res.redirect("back");
}
});
});
});
So... Item.findById(req.params.id) works because I am pulling the ObjectId from the url. However, how would I be able to use req.item._id. Because the issue I am having is when I am trying to get req.item.name and req.item.price I am being told that it cannot read property 'name' or 'price' of undefined. How can I get that information?

Observable switch/flatMap fires instantly

I have the following code in my service:
public loginWithFacebook(): Observable<any> {
console.log('Login');
return Observable.fromPromise(this.fb.login()).flatMap((userData) => {
return this.http.post(authFacebook, {access_token: userData.authResponse.accessToken}, { observe: 'response' });
}).do( (response: HttpResponse<any>) => {
const token = response.headers.get('x-auth-token');
if (token) {
localStorage.setItem('id_token', token);
}
});
}
getFacebookProfile():Observable<any> {
console.log("Get Profile");
return Observable.fromPromise(this.fb.getLoginStatus())
.filter((state) => state.status === 'connected')
.switchMapTo(Observable.fromPromise(this.fb.api('/me')));
}
And later I use it in my component to get the profile info once login is successful.
this.profileData = this.usersService.loginWithFacebook()
.flatMapTo(this.usersService.getFacebookProfile());
However, for some reason getFacebookProfile() fires instantly even before the login procedure is complete. And I get an authentication error. Also, I have to login twice to get profile info displayed.
I've been always thinking that switchMap and flatMap only switch to the next observable once the previous one emits a value.
What am I doing wrong here?
--EDIT--
If I subscribe to the first Observable and call getFacebookProfile() in the subscription, everything works normally. But it's not very elegant solution I feel.
The problem is that promises are eager. You are calling this.fb.login() when you compose your observable and you are passing the returned promise into fromPromise.
That means that the login is initiated when loginWithFacebook is called and not when subscribe is called on the observable it returns.
If you want the login to be deferred until subscribe is called, you can use defer:
public loginWithFacebook(): Observable<any> {
console.log('Login');
return Observable.defer(() => Observable.fromPromise(this.fb.login()))
.flatMap((userData) => {
return this.http.post(authFacebook, {
access_token: userData.authResponse.accessToken
}, { observe: 'response' });
})
.do( (response: HttpResponse<any>) => {
const token = response.headers.get('x-auth-token');
if (token) {
localStorage.setItem('id_token', token);
}
});
}
For more information on using observables and promises, see Ben Lesh's article: RxJS Observable interop with Promises and Async-Await
It worked at last thanks to #cartant's answer. However, for some reason, I had to wrap it with defer operator twice. I would be thankful if someone could explain why it was necessary to do it. It's a bit weird.
public loginWithFacebook(): Observable<any> {
return Observable.defer(() =>
Observable.defer(() => this.fb.login()).flatMap((userData) =>
{
return this.http.post(authFacebook, {access_token: userData.authResponse.accessToken}, { observe: 'response' });
}).do( (response: HttpResponse<any>) => {
const token = response.headers.get('x-auth-token');
if (token) {
localStorage.setItem('id_token', token);
}
})
);
}
getFacebookProfile():Observable<any> {
return Observable.defer(() =>
Observable.defer(() => this.fb.getLoginStatus())
.filter((state) => state.status === 'connected')
.switchMapTo(Observable.fromPromise(this.fb.api('/me')))
);
}

GraphQL x MongoDB

I'm trying to read some data from a mongodb database with graphql and mongoose but everytime I query the db it returns null but no error is thrown.
Here's the code:
// Controller.js
exports.user_read = function(id) {
return new Promise((resolve, reject) => {
Contact.findById(id, function(err, user) {
err ? reject(err) : resolve(user);
}
});
}
// Resolver.js
var contact = require('Controller');
...
// root object passed as rootValue to graphqlHTTP
getUser: ({ id }) => {
contact.user_read(id)
}
...
Any tips and help would be appreciated.
P.S. This also seems to be happening with all my queries which take the same Promise format in the controller.js file.
You need to await contact.user_read(id). Without the await, you are simply sending back a Promise. It's most likely pending when it is returned, therefore the null return.
Including Daniel Rearden's suggestion to get rid of the extra Promise, here's what your code would look like:
// Controller.js
exports.user_read = async id => {
return Contact.findById(id, (err, user) => {
err ? reject(err) : resolve(user);
});
}
// Resolver.js
var contact = require('Controller');
...
// root object passed as rootValue to graphqlHTTP
getUser: ({ id }) => {
return await contact.user_read(id)
}
...

Sails inconsistent record creation

I use the following piece of code to create some records. If I provide incorrect values, say(password and passwordConfirmation does not match), then sometimes an institute record is created without a rollback and sometimes, rollback happens properly.
I would appreciate any help. Is there a better way to do this?
create: function (req, res) {
User.query("BEGIN TRANSACTION", function(result){
if(result) {
sails.log.info(result);
return res.serverError(result);
} else {
Institute.create({
name: req.param('name'),
shortName: req.param('shortName'),
phoneNumber: req.param('phoneNumber'),
subdomain: req.param('subdomain'),
managerEmail: req.param('email')
}, function(error, institute){
if(error) {
sails.log.info(error);
Institute.query("ROLLBACK", function(result) {
sails.log.info(result);
return res.badRequest(error);
});
} else {
User.create({
email: req.param('email'),
password: req.param('password'),
passwordConfirmation: req.param('passwordConfirmation'),
account: institute.id
}, function(error, user) {
if(error) {
sails.log.info(error);
Institute.query("ROLLBACK", function(result) {
sails.log.info(result);
return res.badRequest(error);
});
} else {
User.query("COMMIT", function(result){
sails.log.info(result);
return res.created(user);
});
}
});
}
});
}
});
}
You have a few of options, in no particular order.
1. Write a function that makes all the possible security checks before creation occurs, or use the beforeCreate life cycle call for your models.
For example, you could write a function verifyParams(params) that makes checks such as password comparison (and any other checks you want) for your user creation parameters before you create the institution, or you could just include these checks in your institution creation's beforeCreate method.
2. Delete if there is an error during your user creation
Delete theInstitute model instance in your error case of user creation:
...
User.create(..., function (error, user) {
if (error) {
Institute.destroy(institute.id, function instDestroyed(err) {
...
});
} else {
...
}
});
3. Create a user in your institute model's beforeCreate method.
module.exports = {
attributes: { ... },
beforeCreate: function(values, next) {
User.create(..., function (err, user) {
if (err) { return next(err) }
return next();
});
}
}
Personally, I use method #2 in my own apps.