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
Related
I am creating unit tests, but I can find a way to mock firebase functions and spesify the return type when I call them. Below I posted the what I want to mock(account.service.ts) and what test I currenlty have. I want to mock and specify what is to be returned with admin... (aka set the resp object value).
import * as admin from 'firebase-admin';
account.service.ts
const resp = admin
.auth()
.createUser({
email: registerDto.email,
emailVerified: false,
password: registerDto.password,
displayName: registerDto.displayName,
disabled: false,
})
.then(
(userCredential): Account => ({
uid: userCredential.uid,
email: userCredential.email,
emailVerified: userCredential.emailVerified,
displayName: userCredential.displayName,
message: 'User is successfully registered!',
}),
)
.catch((error) => {
// eslint-disable-next-line max-len
throw new HttpException(`${'Bad Request Error creating new user: '}${error.message}`, HttpStatus.BAD_REQUEST);
});
account.service.spec.ts
describe('61396089', () => {
afterEach(() => {
jest.restoreAllMocks();
});
const obj = {
uid: 'uid',
email: 'email',
emailVerified: 'emailVerified',
displayName: 'displayName',
message: 'User is successfully registered!',
};
const jestAdminMock = {
admin: () => ({
auth: () => ({
createUser: () => ({
then: () => ({
catch: () => obj,
}),
}),
}),
}),
};
it('should pass', () => {
const mockDataTable = {
admin: jest.fn().mockReturnThis(),
auth: jest.fn().mockReturnThis(),
createUser: jest.fn().mockReturnThis(),
then: jest.fn().mockReturnThis(),
catch: jest.fn().mockReturnValueOnce(obj),
};
jest.spyOn(jestAdminMock, 'admin').mockImplementationOnce(() => mockDataTable);
const actual = service.registerUser(registerDTO);
expect(actual).toBe(obj);
});
});
});
I managed to make a test like this example
This is the function
import admin from 'firebase-admin'
admin.initializeApp({
credential: admin.credential.cert(credentials),
})
const createClosure = (admin) => {
if (!admin) {
throw new Error(Errors.FIREBASE_ADMIN_SDK_NOT_PROVIDED)
}
return (data) => {
if (
data &&
!Array.isArray(data) &&
typeof data === 'object' &&
Object.keys(data).length > 0
) {
const { firstName, lastName } = data
const displayName = `${firstName} ${lastName}`
return admin.auth().createUser({ ...data, displayName })
}
throw new Error(Errors.INVALID_DATA)
}
}
/*
.....
*/
const create = createClosure(admin)
export { create, createClosure }
This is a test example
import { createClosure } from "../path/to/function"
describe('createClosure', () => {
it('should be a function', () => {
expect(typeof createClosure).toBe('function')
})
describe('when admin is not provided', () => {
it('should throw "Firebase Admin SDK not provided"', () => {
const expected = Errors.FIREBASE_ADMIN_SDK_NOT_PROVIDED
expect(() => createClosure()).toThrow(expected)
})
})
describe('when admin is provided', () => {
describe('when data is invalid', () => {
const createUser = jest.fn()
const admin = {
auth: () => ({
createUser,
}),
}
const data1 = 123
const data2 = 'hello'
const data3 = ['a', 'b', 'c']
const data4 = {}
it('should throw "Invalid data"', () => {
expect(() => createClosure(admin)()).toThrow(Errors.INVALID_DATA)
expect(() => createClosure(admin)(data1)).toThrow(Errors.INVALID_DATA)
expect(() => createClosure(admin)(data2)).toThrow(Errors.INVALID_DATA)
expect(() => createClosure(admin)(data3)).toThrow(Errors.INVALID_DATA)
expect(() => createClosure(admin)(data4)).toThrow(Errors.INVALID_DATA)
})
})
describe('when data is valid', () => {
const data = {
firstName: 'Alice',
lastName: 'Alley',
foo: 'bar',
baz: {
boo: 'bii',
buu: 'byy',
},
}
describe('when createUser rejects', () => {
const e = new Error('Error happened!')
const createUser = jest.fn().mockRejectedValue(e)
const admin = {
auth: () => ({
createUser,
}),
}
const create = createClosure(admin)
it('should call createUser once', async () => {
try {
await createUser(data)
} catch (error) {}
expect(createUser).toBeCalledTimes(1)
expect(createUser).toBeCalledWith({ ...data })
})
it('should reject', async () => {
await expect(create(data)).rejects.toEqual(e)
})
})
describe('when save resolves', () => {
const expected = {
baz: { boo: 'bii', buu: 'byy' },
displayName: 'Alice Alley',
lastName: 'Alley',
}
const displayName = `${data.firstName} ${data.lastName}`
const createUser = jest.fn().mockResolvedValue(expected)
const admin = {
auth: () => ({
createUser,
}),
}
const create = createClosure(admin)
it('should call save once', async () => {
try {
await create(data)
} catch (error) {}
expect(createUser).toBeCalledTimes(1)
expect(createUser).toBeCalledWith({ ...data, displayName })
})
it('should resolve', async () => {
const result = await create(data)
expect(result).toMatchObject(expected)
})
})
})
})
})
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()
})
I'm working on a Node.JS backend and I basically need to do these basic things:
Save multiple files to Google Cloud Storage
Save the array of GCS public urls to MongoDB
Save "post" datas to Mongo DB along with the array of public urls
This is my code. At the end it works ans save all the things in Mongo DB but it seems messy. How can I fix these line of codes?
model/Post.js:
const mongoose = require('mongoose');
const {Schema} = mongoose;
const postSchema = new Schema({
title: {type: String},
subtitle: {type: String},
description: {type: String},
imagesUrl: [{type: String}],
_user: {
type: Schema.Types.ObjectId,
ref: 'User',
},
});
const Post = mongoose.model('Post', postSchema, 'posts');
module.exports = {
Post,
};
controllers/postController.js
const {Storage} = require('#google-cloud/storage');
const storage = new Storage();
const bucket = storage.bucket(process.env.GCLOUD_STORAGE_BUCKET);
//Func to save data into Mongo DB
const createPost = async (data) => {
try {
const post = await new Post({
title: data.title,
subtitle: data.subtitle,
description: data.description,
_user: data.userId,
});
await _.map(data.imagesUrl, (value, key) => {
post.imagesUrl.push(value);
});
post.save();
const userById = await User.findOne({_id: data.userId});
userById._posts.push(post);
await userById.save();
return post;
} catch (e) {
return e.stack;
}
};
//Func to upload files to GCS
const uploadFileTGcs = async (file) => {
let promises = [];
_.forEach(file, (value, key) => {
const {originalname, buffer} = value;
const blob = bucket.file(originalname.replace(/ /g, '_'));
const promise = new Promise((resolve, reject) => {
const blobStream = blob.createWriteStream({
resumable: false,
public: true,
});
blobStream.on('error', () => {
reject(`Unable to upload image, something went wrong`);
}).on('finish', async () => {
const publicUrl = format(
`https://storage.googleapis.com/${bucket.name}/${blob.name}`,
);
resolve(publicUrl);
}).end(buffer);
});
promises.push(promise);
});
const urls = Promise.all(promises).then(promises => {
return promises;
});
return urls;
};
routes/postRoute.js
router.post('/create', async (req, res, next) => {
try {
if (!req.files) {
res.status(400).json({
messages: 'No file uploaded',
});
return;
}
const imagesUrl = await postsController.uploadFileTGcs(req.files);
const postCreated = await postsController.createPost({
title: req.body.title,
subtitle: req.body.subtitle,
description: req.body.description,
imagesUrl: imagesUrl,
userId: req.user._id,
});
res.status(postCreated ? 200 : 404).json({
result: postCreated,
message: 'Post created',
});
} catch (e) {
res.status(500).json({
result: e.toString(),
});
}
});
I working on Mongoose and Express project where by I have 3 models: User, Album and Purchase. The purchase model references the user and album. I am creating a POST endpoint where by I can make a purchase and then retrieve the data as well as the user and album relations which should be populated with their data, but I am stuck.
var app = express();
var bodyParser = require('body-parser');
var mongoose = require('mongoose');
var userSchema = mongoose.Schema({
// TODO: Define Schema
name: {
type: String,
required: true
}
})
var User = mongoose.model('User', userSchema)
var albumSchema = mongoose.Schema({
// TODO: Define Schema
title: {
type: String,
required: true
},
performer: {
type: String,
required: true
},
cost: {
type: Number,
required: true
}
})
var Album = mongoose.model('Album', albumSchema);
var puchaseSchema = mongoose.Schema({
// TODO: Define Schema
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Album'
},
album: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
}
})
var Purchase = mongoose.model('Purchase', puchaseSchema);
app.use(bodyParser.json());
app.listen(3000);
// TODO: GET /albums
app.get('/albums', (req,res) => {
Album.find()
.then((response) => {
res.json({data: response})
})
.catch(err => {
res.json({error: err})
})
})
// TODO: GET /albums/:id
app.get('/albums/:id', (req,res) => {
Album.findById(req.params.id)
.then(response => {
res.json({data: response})
.catch(err => {
res.json({Error: err})
})
})
})
// TODO: POST /albums
app.post('/albums', (req,res) => {
const newPost = Album({
title: req.body.title,
performer: req.body.performer,
cost: req.body.cost
})
newPost.save(err => {
if(err)
res.json({error: err})
})
.then(data => {
res.json({data: data})
})
})
// TODO: PUT /albums/:id
app.put('/albums/:id', (req,res) => {
Album.findByIdAndUpdate(req.params.id, req.body, {new: true},
(err, album) =>{
if(err) return res.status(500).send(err)
return res.json({data: album})
})
})
// TODO: DELETE /albums/:id
app.delete('/albums/:id', (req,res) => {
const id = req.params.id
Album.findById(id)
.then(docs => {
docs.remove()
res.status(204)
.json({data:docs})
})
})
// TODO: POST /purchases
app.post('/purchases', (req,res) => {
})```
This might help you. Look into mongoose's populate method.
app.post('/purchases', (req,res) => {
const user = req.body.userId;
const album = req.body.albumId;
const newPurchase = new Purchase({
user: user,
album: album
});
newPurchase.save().then((purchase) => {
Purchase.findById(purchase.id).populate('user').populate('album').then((purchaseData) => {
return res.json({purchaseData});
}).catch(e => {
console.log(e);
});
}).catch(e => {
console.log(e);
});
})
Here's an alternative for populating after saving the document.
app.post('/purchases', (req,res) => {
const user = req.body.userId;
const album = req.body.albumId;
const newPurchase = new Purchase({
user: user,
album: album
});
newPurchase.save().then((purchase) => {
Purchase.populate(purchase, [{path: 'user'}, {path: 'album'}], (err, data) => {
if(err) {
return res.json(e);
}
return res.json(data);
});
}).catch(e => {
console.log(e);
});
}
)
As mentioned here: https://mongoosejs.com/docs/api.html#model_Model.populate
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.