So I've been stuck on this problem for the last week I'd say.
I'm trying to add a workout to my database and I'm receiving from my frontend 3 things: the workout name as a string, an array called exercises that contains objects for each exercise in that workout. And finally, an array called sets that is a 2d array, each array contains an object for each set
const exercises = [
{name: ''}
]
const sets = [
[
{
reps: '',
kg: ''
}
]
]
I'm using knex and I want to insert this data into my database. This is my code:
app.post('/newworkout', (req, res) => {
const {workoutName, exercises, sets} = req.body;
const loggedSets = [];
let haveRoutinesFinished = false;
let haveExercisesFinished = false;
PushRoutinesToDB = () => {
db('routines')
.returning('id')
.insert({
userid: user.id,
name: workoutName
})
.then(routineID => {
workoutInfo.routineID = routineID[0];
haveRoutinesFinished = true;
console.log('1')
return db('loggedroutines')
.insert({
userid: user.id,
routineid: workoutInfo.routineID,
routinedate: new Date()
})
})
}
PushExercisesToDB = () => {
exercises.map(exercise => {
console.log('2')
db('exercises')
.insert({
userid: user.id,
routineid: workoutInfo.routineID,
name: exercise.name
})
.returning('id')
.then(exerciseID => {
console.log('exercise id', exerciseID)
exerciseIDArray.push(exerciseID[0])
console.log("exerciseIDArray", exerciseIDArray)
haveExercisesFinished = true;
return db('loggedexercises')
.insert({
userid: user.id,
routineid: workoutInfo.routineID,
exerciseid: exerciseID[0]
})
})
})
}
PushSetsToDB = () => {
sets.map((setsArray, i) => {
setsArray.map(set => {
loggedSets.push({
userid: user.id,
routineid: workoutInfo.routineID,
exerciseid: exerciseIDArray[i],
reps: set.reps,
kg: set.kg
})
})
})
console.log(loggedSets)
db('loggedsets').insert(loggedSets)
}
PushRoutinesToDB()
console.log(haveRoutinesFinished)
if (haveRoutinesFinished === true) {
PushExercisesToDB()
}
if (haveExercisesFinished === true) {
PushSetsToDB()
}
})
The code itself works, but due to the first database call being asynchronous, the second one doesn't work and it depends on the first one. I tried to use callbacks to counter this issue but that just causes my code to not run. Any ideas?
Since your functions are Asynchronous the thread continues even if the first function has not yet completed.
Using async/await must solve your problem.
NOTE THE COMMENTS TO SEE CHANGES
app.post('/newworkout', async (req, res) => { //asynchronous Function
const {workoutName, exercises, sets} = req.body;
const loggedSets = [];
let haveRoutinesFinished = false;
let haveExercisesFinished = false;
PushRoutinesToDB = async () => { //asynchronous Function
db('routines')
.returning('id')
.insert({
userid: user.id,
name: workoutName
})
.then(routineID => {
workoutInfo.routineID = routineID[0];
haveRoutinesFinished = true;
console.log('1')
return db('loggedroutines')
.insert({
userid: user.id,
routineid: workoutInfo.routineID,
routinedate: new Date()
})
})
}
PushExercisesToDB = async () => { //asynchronous Function
exercises.map(exercise => {
console.log('2')
db('exercises')
.insert({
userid: user.id,
routineid: workoutInfo.routineID,
name: exercise.name
})
.returning('id')
.then(exerciseID => {
console.log('exercise id', exerciseID)
exerciseIDArray.push(exerciseID[0])
console.log("exerciseIDArray", exerciseIDArray)
haveExercisesFinished = true;
return db('loggedexercises')
.insert({
userid: user.id,
routineid: workoutInfo.routineID,
exerciseid: exerciseID[0]
})
})
})
}
PushSetsToDB = async () => { //asynchronous Function
sets.map((setsArray, i) => {
setsArray.map(set => {
loggedSets.push({
userid: user.id,
routineid: workoutInfo.routineID,
exerciseid: exerciseIDArray[i],
reps: set.reps,
kg: set.kg
})
})
})
console.log(loggedSets)
db('loggedsets').insert(loggedSets)
}
// Now your all functions are asynchronous You can call them in series with 'await'
await PushRoutinesToDB()
await PushExercisesToDB()
await PushSetsToDB()
})
Related
In a hook I want to confirm whether a password has changed before executing encryption process.
Mongoose has a function "isModified" and I believe Sequelize's "changed" function servers the same purpose.
I cannot get the "changed" function to work. I am looking for an example of how it is used.
*******Snippet of code
{
hooks: {
beforeCreate: async (user) => {
if (changed([user.password]) === false) return next();
const salt = await bcrypt.genSalt(12);
user.password = await bcrypt.hash(user.password, salt);
user.passwordConfirmed = undefined;
},
},
instanceMethods: {
validPassword: function (password) {
return bcrypt.compare(password, this.password);
},
},
}
You must to use hook beforeUpdate with previous function. Try something like that:
let { Sequelize, DataTypes } = require('sequelize');
let sequelize = new Sequelize({
dialect: 'sqlite',
storage: 'database.sqlite',
});
(async () => {
try {
await sequelize.authenticate();
console.log('connected');
let Person = sequelize.define('Person', { name: DataTypes.STRING, age: DataTypes.INTEGER });
Person.addHook('beforeUpdate', (person) => {
if (person.previous('name') != person.name) {
console.log('name changed', person.previous('name'), person.name);
}
});
await Person.sync({ force: true });
await Person.create({ name: 'John', age: 30 });
let [person] = await Person.findAll();
person.name = 'Paul';
await person.save();
} catch (error) {
console.error('Unable to connect to the database:', error);
}
})();
So I'm working on a graphql server and I'm trying to make an async/await external call to facebook's server.
The issue is that all this information does come back but it does not save to the db.
How do I know it does come back? if I use findOne instead of findOneAndUpdate I can console.log all the info and see it successfully but once I switch back I don't even get an error.
I have looked at the mongoose docs and tried to apply the findOneAndUpdate properly but it just laughs at me no error it just doesn't do anything.
Any way here is my code if any one can give me some advice it would really be appreciated.
The first set is code using findOne which I know works 100% but does not save or update the doc.
The second one is when I try to do it with FindOneAndUpdate.
getFacebookPageID: {
type: FacebookType,
description: 'Gets all the content we want from facebook once a user has granted permissions',
args: {
id: { type: GraphQLString },
accessToken: { type: GraphQLString },
facebook: { type: InputFacebookType }
},
resolve: (parent, args) => User.findOne({ _id: args.id }, async (err, docs) => {
console.log('next step is getcontent');
// const clientId = process.env.FACEBOOK_ID;
// const reDirectFBUri = process.env.FACEBOOK_ID_URI;
const { accessToken } = docs.tokens.find((item) => item.kind === 'facebook');
const userId = docs.facebookId;
console.log(userId);
const getFBaccounts = `https://graph.facebook.com/${userId}/accounts?access_token=${accessToken}`;
let pages = null;
await axios.get(getFBaccounts)
.then((response) => {
pages = response.data.data;
// this above line is all the fb pages user has give us access to
console.log('pages', response.data.data);
const query = { _id: args.id, };
console.log('This should be object id', query);
User.updateOne(query, {
pages,
}, (err, docs) => {
console.log('Any errors here are problems with saving!', err, docs);
});
})
.catch((err) => console.log(err));
return FacebookType;
}
),
},
I have also tried this below and that simply will not even console log anything. This is me trying to use findOneAndUpdate
getFacebookPageID: {
type: FacebookType,
description: 'Gets all the content we want from facebook once a user has granted permissions',
args: {
id: { type: GraphQLString },
accessToken: { type: GraphQLString },
facebook: { type: InputFacebookType }
},
resolve: (parent, args) => User.findOneAndUpdate({ _id: args.id },{ pages: async (err, docs) => {
console.log('next step is getcontent');
const { accessToken } = docs.tokens.find((item) => item.kind === 'facebook');
const userId = docs.facebookId;
console.log(userId);
const getFBaccounts = `https://graph.facebook.com/${userId}/accounts?access_token=${accessToken}`;
let pages = null;
await axios.get(getFBaccounts)
.then((response) => {
pages = response.data.data;
console.log('pages', response.data.data);
})
.catch((err) => console.log(err));
return FacebookType;
}
},
{new: true}
),
},
you use async/await in mix with promises. You should choose one approach (I suggest async/await);
resolve: async (parent, args) => {
try {
//get user
const user = await User.findById(args.id); //user is mongooose instance - https://mongoosejs.com/docs/api.html#document_Document-save
const { accessToken } = user.tokens.find((item) => item.kind === 'facebook');
const userId = user.facebookId;
const getFBaccounts = `https://graph.facebook.com/${userId}/accounts?access_token=${accessToken}`;
//get pages
const fbResponse = await axios.get(getFBaccounts);
//use mongoose prototype method "save" to update the user
user.pages = fbResponse.data.data;
await user.save();
return user;
} catch(e) {
console.log(e);
}
}
My tests are working and passing, but chai's done function is messing up. I've tried a few different methods, but I don't understand where I'm going wrong in these unit tests. I'm very new to unit testing and chai, so any help would be greatly appreciated
The places where the test is failing: line 40 (create), line 98 (update)
Any ideas?
const chai = require('chai')
let should = chai.should()
let expect = chai.expect
let db = require('../app/models')
db.hosts.modelName = 'Hosts'
db.victims.modelName = 'Victims'
let models = [db.hosts, db.victims]
models.forEach(model => {
describe(`${model.modelName} Model`, function (done) {
var modelData = {
guest_count: 3,
start_date: "2018-01-11T00:00:00.000Z",
end_date: "2018-01-12T00:00:00.000Z",
location: {
type: "Point",
coordinates: [
-74.323564,
40.232323
]
},
first_name: "Sean",
last_name: "Destroyed",
phone: "7325556677",
address: "123 main street, red bank, nj",
email: "test#gmail.com",
}
it(`should create a new ${model.modelName}`, function () {
model.create(modelData).then(function (user) {
//victim name should be equivalent to the fake submission we are using
expect(user.first_name).to.equal("Sean");
//remove the entry from the database
model.destroy({
where: {
id: user.id
}
})
done()
})
});
it(`should delete a ${model.modelName} from the database`, function () {
model.create(modelData).then(function (user) {
//victim name should be equivalent to the fake submission we are using
//remove the entry from the database
model.destroy({
where: {
id: user.id
}
})
try {
model.findOne({
where: {
id: user.id
}
})
} catch (err) {
expect(user.first_name).to.undefined;
if (err) {
done()
}
}
})
})
it(`should update the ${model.modelName} entry in the database`, function () {
model.create(modelData).then(function (user) {
//after user is created, then update a value
modelData.guest_count = 12
model.update(modelData, {
where: {
id: user.id
}
}).then(function(data) {
model.findOne({
where: {
id: user.id
}
}).then(function (data) {
expect(data.guest_count).to.equal(12);
}).then(function () {
model.destroy({
where: {
id: user.id
}
})
}).then(function() {
done()
})
})
})
})
})
});
There are two things to keep in mind:
(1) Sequelize uses promises for its ORM methods. So, even after you are calling destroy, you need to attach a callback, e.g.:
model.destroy({
where: {
id: user.id
}
})
.then(function() {
// now do something
});
(2) The done method in chai should be attached to each test, as opposed to the test block:
describe('some test block', function() {
it('should do something,' function(done) {
User.findAll().then(function(users) {
// expect users to do something
done(); // tests are done
});
});
});
In your case, here are the two failing test cases:
// ensure "destroy" has a callback
it(`should create a new ${model.modelName}`, function (done) {
model.create(modelData).then(function (user) {
//victim name should be equivalent to the fake submission we are using
expect(user.first_name).to.equal("Sean");
//remove the entry from the database
model.destroy({
where: {
id: user.id
}
}).then(function() {
done();
})
})
});
// update
it(`should update the ${model.modelName} entry in the database`, function () {
model.create(modelData).then(function (user) {
//after user is created, then update a value
modelData.guest_count = 12
model.update(modelData, {
where: {
id: user.id
}
}).then(function(data) {
model.findOne({
where: {
id: user.id
}
}).then(function (data) {
expect(data.guest_count).to.equal(12);
}).then(function () {
model.destroy({
where: {
id: user.id
}
}).then(function() {
done()
})
})
})
})
})
#mcranston18 left a really nice detailed accepted answer.
What I'd like to add for others that find the question or for OP in the future, is the use of async/await:
describe('some test block', function() {
it('should do something', async function() { // notice async and no done
const users = await User.findAll()
// expect users.to (...)
})
})
Here's a really simple way to create then update using async/await:
describe('some test block', function () {
it('should do something', async function () {
const Joe = await User.create({ name: 'Jo' }) // oops
// assertions/expect/should
// ex: expect(Joe.name).to.equal('Jo')
await Joe.update({ name: 'Joe' }) // that's better
// assertions/expect/should
// ex: expect(Joe.name).to.equal('Joe')
})
})
I am following this graphql tutorial, everything was going ok until I try to use dataloaders.
My server.js is:
const start = async () => {
const mongo = await connectMongo();
const buildOptions = async req => {
const user = await authenticate(req, mongo.Users);
return {
context: {
dataloaders: buildDataloaders(mongo),
mongo,
user
},
schema
};
};
app.use('/graphql', bodyParser.json(), graphqlExpress(buildOptions));
app.use(
'/graphiql',
graphiqlExpress({
endpointURL: '/graphql',
passHeader: `'Authorization': 'bearer token-name#email.com'`
})
);
app.use('/', expressStaticGzip('dist'));
app.use('/attendance', expressStaticGzip('dist'));
app.use('/login', expressStaticGzip('dist'));
spdy.createServer(sslOptions, app).listen(process.env.PORT || 8080, error => {
if (error) {
console.error(error);
return process.exit(1);
} else {
console.info(
`App available at https://localhost:${process.env.PORT || 3000}`
);
}
});
};
My copy and paste dataloaders.js:
const DataLoader = require('dataloader');
async function batchUsers(Users, keys) {
return await Users.find({ _id: { $in: keys } }).toArray();
}
module.exports = ({ Users }) => ({
userLoader: new DataLoader(keys => batchUsers(Users, keys), {
cacheKeyFn: key => key.toString()
})
});
And my resolvers.js:
export default {
Query: {
allLinks: async (root, data, { mongo: { Links } }) =>
Links.find({}).toArray()
},
Mutation: {
createLink: async (root, data, { mongo: { Links }, user }) => {
const newLink = Object.assign({ postedById: user && user._id }, data);
const response = await Links.insert(newLink);
return Object.assign({ id: response.insertedIds[0] }, newLink);
},
createUser: async (root, data, { mongo: { Users } }) => {
const newUser = {
name: data.name,
email: data.authProvider.email.email,
password: data.authProvider.email.password
};
const response = await Users.insert(newUser);
return Object.assign({ id: response.insertedIds[0] }, newUser);
},
signinUser: async (root, data, { mongo: { Users } }) => {
const user = await Users.findOne({ email: data.email.email });
if (data.email.password === user.password) {
return { token: `token-${user.email}`, user };
}
}
},
Link: {
id: root => root._id || root.id,
postedBy: async ({ postedById }, data, { dataloaders: { userLoader } }) => {
return await userLoader.load(postedById);
}
},
User: {
id: root => root._id || root.id
}
};
When I try get my allLinks I got the error:
TypeError: The loader.load() function must be called with a value,but
got: undefined.
Can anyone help me?
So I was able to reproduce the error by creating a link with a user, deleting the user from the Mongo database, and then querying for the postedBy attribute of the Link.
I would suggest dropping all your links and recreating your user (register + sign in), creating a new link, then querying for the postedBy field.
I have the following structure. Every Account can have one security type.
So its a one-to-many from SecurityType to Account.
Everything works using the code
File: AccountSchema.js
const SecurityType = require('./LookupSchema').SecurityType;
console.log(Account);
const Account = new GraphQLObjectType({
name: 'Account',
description: 'Account access',
fields: () =>
({
id: {
type: GraphQLString
},
security_type:
{
type: SecurityType,
resolve(parent, args, ast){
return new Promise((resolve, reject) => {
const db = ast.db;
const parameters = [parent.security_type_id];
db.query(db.connection, `SELECT * FROM lookups.security_type WHERE id = $1`, parameters)
.then(result =>
{
resolve(result.entrys.rows[0]);
})
.catch(err =>
{
reject(err.message);
});
});
}
}
})
});
module.exports = {
Account : Account
}
File: LookupSchema.js
const Account = require('./AccountSchema').Account;
console.log(Account);
const SecurityType = new GraphQLObjectType({
name: 'SecurityType',
description: 'Used to for specifying security type',
fields: () =>
({
id: {
type: GraphQLString
}
})
});
module.exports = {
SecurityType: SecurityType
}
File: Query.js
const Query = new GraphQLObjectType({
name: 'Query',
description: 'Root query object',
fields: () => ({
accounts: {
type: new GraphQLList(Account),
resolve(root, args, ast) {
return new Promise((resolve, reject) => {
const db = ast.db;
const parameters = [];
db.query(db.connection, `SELECT * FROM accounts.account`, parameters)
.then(result =>
{
console.log(result);
resolve(result.entrys.rows);
})
.catch(err =>
{
console.log(err);
reject(err.message);
});
});
}
},
securityTypes: {
type: new GraphQLList(SecurityType),
resolve(root){
return new Promise((resolve, reject) => {
const db = ast.db;
const parameters = [];
db.query(db.connection, `SELECT * FROM lookups.security_type`, parameters)
.then(result =>
{
resolve(result.entrys.rows);
})
.catch(err =>
{
reject(err.message);
});
});
}
}
})
});
The problem I have is when I add to the file LookupSchema.js the accounts
const SecurityType = new GraphQLObjectType({
name: 'SecurityType',
description: 'Used to for specifying security type',
fields: () =>
({
id: {
type: GraphQLString
},
accounts: {
type: new GraphQLList(Account),
resolve(parent, args, ast){
return new Promise((resolve, reject) => {
const db = ast.db;
const parameters = [parent.id];
db.query(db.connection, `SELECT * FROM accounts.account WHERE security_type_id = $1`, parameters)
.then(result =>
{
resolve(result.entrys.rows);
})
.catch(err =>
{
reject(err.message);
});
});
}
}
})
});
I get the following error when I start the service
Error: Can only create List of a GraphQLType but got: undefined.
I put console.log for each Account and SecurityType to check for the import and I noticed in LookupSchema, Account is undefined. I did some research and this might be a circular issue but not quite sure a solution for it.
Any advise would be appreciated
To avoide the Cyclic Problem you can use the require function inside the fields() function.
So, Inside AccountSchema.js fields:() function will first import the SecurityType then only we will be using the the other fields with the return {}, same for other files.
AccountSchema.js
const {
GraphQLObjectType,
GraphQLString,
} = require('graphql');
const Account = new GraphQLObjectType({
name: 'Account',
description: 'Account access',
fields: () => {
const SecurityType = require('./LookUpSchema');
return {
id: {
type: GraphQLString,
},
security_type:
{
type: SecurityType,
resolve(parent, args, ast) {
return new Promise((resolve, reject) => {
const db = ast.db;
const parameters = [parent.security_type_id];
db.query(db.connection, 'SELECT * FROM lookups.security_type WHERE id = $1', parameters)
.then((result) => {
resolve(result.entrys.rows[0]);
})
.catch((err) => {
reject(err.message);
});
});
},
},
};
},
});
module.exports = Account;
LookUpSchema.js
const {
GraphQLObjectType,
GraphQLString,
GraphQLList,
} = require('graphql');
const SecurityType = new GraphQLObjectType({
name: 'SecurityType',
description: 'Used to for specifying security type',
fields: () => {
const Account = require('./AccountSchema');
return {
id: {
type: GraphQLString,
},
accounts: {
type: new GraphQLList(Account),
resolve(parent, args, ast) {
return new Promise((resolve, reject) => {
const db = ast.db;
const parameters = [parent.id];
db.query(db.connection, 'SELECT * FROM accounts.account WHERE security_type_id = $1', parameters)
.then((result) => {
resolve(result.entrys.rows);
})
.catch((err) => {
reject(err.message);
});
});
},
},
};
},
});
module.exports = SecurityType;
Query.js
const {
GraphQLList,
GraphQLObjectType,
GraphQLSchema,
} = require('graphql');
const Account = require('./AccountSchema');
const SecurityType = require('./LookUpSchema');
console.log('Account', Account);
const Query = new GraphQLObjectType({
name: 'Query',
description: 'Root query object',
fields: () => ({
accounts: {
type: new GraphQLList(Account),
resolve(root, args, ast) {
return new Promise((resolve, reject) => {
const db = ast.db;
const parameters = [];
db.query(db.connection, 'SELECT * FROM accounts.account', parameters)
.then((result) => {
console.log(result);
resolve(result.entrys.rows);
})
.catch((err) => {
console.log(err);
reject(err.message);
});
});
},
},
securityTypes: {
type: new GraphQLList(SecurityType),
resolve(root) {
return new Promise((resolve, reject) => {
const db = ast.db;
const parameters = [];
db.query(db.connection, 'SELECT * FROM lookups.security_type', parameters)
.then((result) => {
resolve(result.entrys.rows);
})
.catch((err) => {
reject(err.message);
});
});
},
},
}),
});
const schema = new GraphQLSchema({
query: Query,
// mutation: MutationType,
});
module.exports = schema;
GraphiQL