Singular query won’t return nested arrays - node.js

I have a query that tries to fetch a single document, here is the resolver for that query.
const singleDoc = async (_parent, args, context, info) => {
try {
return await context.prisma.doc({ id: args.docId },info )
} catch (error) {
console.log(error)
}
}
If I call the query in GraphQL, it returns this:
"data": {
"singleDoc": {
"name": "Sample doc",
"teams": null,
"description": "This holds doc description"
}
}
}
I queried for the teams field but they weren’t returned.
I feel like there is something wrong with the query resolver? What am I missing?

I was able to achieve by using graphQL fragments
Fragment
const docFragment = `
fragment DocWithDetails on Doc {
name
teams{
id
role
}
}`
Then I passed the graphQL fragment into the resolver. This was I was able to retrieve the nested relations
const singleDoc = async (_parent, args, context, info) => {
try {
return await context.prisma.doc({ id: args.docId }).$fragment(docFragment)
} catch (error) {
console.log(error)
}
}

Related

How can I am make sure these chain of functions in Node.js are performed in order (using promises)?

I have a set of functions in Node.js that I would like to load in a certain order. I will provide some mockup code abstracted and simplified:
function updateMyApp() {
loadDataToServer()
.then(() => useData())
.then(() => saveData())
.then(() => { console.log("updateMyApp done") })
}
function loadDataToServer() {
return new Promise( (resolve, reject) {
...preparing data and save file to cloud...
resolve()})
}
function handleDataItem(item) {
// Function that fetches data item from database and updates each data item
console.log("Name", item.name)
}
function saveData() {
// Saves the altered data to some place
}
useData is a bit more complex. In it I would like to, in order:
console.log('Starting alterData()')
Load data, as json, from the cloud data source
Iterate through every item in the json file and do handleDataItem(item) on it.
When #2 is done -> console.log('alterData() done')
Return a resolved promise back to updateMyApp
Go on with saveData() with all data altered.
I want the logs to show:
Starting useData()
Name: Adam
Name: Ben
Name: Casey
useData() done
my take on this is the following:
function useData() {
console.log('Starting useData()')
return new Promise( function(resolve, reject) {
readFromCloudFileserver()
.then(jsonListFromCloud) => {
jsonListFromCloud.forEach((item) => {
handleDataItem(item)
}
})
.then(() => {
resolve() // I put resolve here because it is not until everything is finished above that this function is finished
console.log('useData() done')
}).catch((error) => { console.error(error.message) })
})
}
which seems to work but, as far as I understand this is not how one is supposed to do it. Also, this seems to do the handleDataItem outside of this chain so the logs look like this:
Starting useData()
useData() done
Name: Adam
Name: Ben
Name: Casey
In other words. It doesn't seem like the handleDataItem() calls are finished when the chain has moved on to the next step (.then()). In other words, I can not be sure all items have been updated when it goes on to the saveData() function?
If this is not a good way to handle it, then how should these functions be written? How do I chain the functions properly to make sure everything is done in the right order (as well as making the log events appear in order)?
Edit: As per request, this is handleDataItem less abstracted.
function handleDataItem(data) {
return new Promise( async function (resolve) {
data['member'] = true
if (data['twitter']) {
const cleanedUsername = twitterApi.cleanUsername(data['twitter']).toLowerCase()
if (!data['twitter_numeric']) {
var twitterId = await twitterApi.getTwitterIdFromUsername(cleanedUsername)
if (twitterId) {
data['twitter_numeric'] = twitterId
}
}
if (data['twitter_numeric']) {
if (data['twitter_protected'] != undefined) {
var twitterInfo = await twitterApi.getTwitterGeneralInfoToDb(data['twitter_numeric'])
data['twitter_description'] = twitterInfo.description
data['twitter_protected'] = twitterInfo.protected
data['twitter_profile_pic'] = twitterInfo.profile_image_url.replace("_normal", '_bigger')
data['twitter_status'] = 2
console.log("Tweeter: ", data)
}
} else {
data['twitter_status'] = 1
}
}
resolve(data)
}).then( (data) => {
db.collection('people').doc(data.marker).set(data)
db.collection('people').doc(data.marker).collection('positions').doc(data['report_at']).set(
{
"lat":data['lat'],
"lon":data['lon'],
}
)
}).catch( (error) => { console.log(error) })
}
The twitterAPI functions called:
cleanUsername: function (givenUsername) {
return givenUsername.split('/').pop().replace('#', '').replace('#', '').split(" ").join("").split("?")[0].trim().toLowerCase()
},
getTwitterGeneralInfoToDb: async function (twitter_id) {
var endpointURL = "https://api.twitter.com/2/users/" + twitter_id
var params = {
"user.fields": "name,description,profile_image_url,protected"
}
// this is the HTTP header that adds bearer token authentication
return new Promise( (resolve,reject) => {
needle('get', endpointURL, params, {
headers: {
"User-Agent": "v2UserLookupJS",
"authorization": `Bearer ${TWITTER_TOKEN}`
}
}).then( (res) => {
console.log("result.body", res.body);
if (res.body['errors']) {
if (res.body['errors'][0]['title'] == undefined) {
reject("Twitter API returns undefined error for :'", cleanUsername, "'")
} else {
reject("Twitter API returns error:", res.body['errors'][0]['title'], res.body['errors'][0]['detail'])
}
} else {
resolve(res.body.data)
}
}).catch( (error) => { console.error(error.message) })
})
},
// Get unique id from Twitter user
// Twitter API
getTwitterIdFromUsername: async function (cleanUsername) {
const endpointURL = "https://api.twitter.com/2/users/by?usernames="
const params = {
usernames: cleanUsername, // Edit usernames to look up
}
// this is the HTTP header that adds bearer token authentication
const res = await needle('get', endpointURL, params, {
headers: {
"User-Agent": "v2UserLookupJS",
"authorization": `Bearer ${TWITTER_TOKEN}`
}
})
if (res.body['errors']) {
if (res.body['errors'][0]) {
if (res.body['errors'][0]['title'] == undefined) {
console.error("Twitter API returns undefined error for :'", cleanUsername, "'")
} else {
console.error("Twitter API returns error:", res.body['errors'][0]['title'], res.body['errors'][0]['detail'])
}
} else {
console.error("Twitter API special error:", res.body)
}
} else {
if (res.body['data']) {
return res.body['data'][0].id
} else {
//console.log("??? Could not return ID, despite no error. See: ", res.body)
}
}
},
You have 3 options to deal with your main issue of async methods in a loop.
Instead of forEach, use map and return promises. Then use Promise.all on the returned promises to wait for them to all complete.
Use a for/of loop in combination with async/await.
Use a for await loop.
It sounds like there's a problem in the implementation of handleDataItem() and the promise that it returns. To help you with that, we need to see the code for that function.
You also need to clean up useData() so that it properly returns a promise that propagates both completion and errors.
And, if handleDataItem() returns a promise that is accurate, then you need to change how you do that in a loop here also.
Change from this:
function useData() {
console.log('Starting useData()')
return new Promise( function(resolve, reject) {
readFromCloudFileserver()
.then(jsonListFromCloud) => {
jsonListFromCloud.forEach((item) => {
handleDataItem(item)
}
})
.then(() => {
resolve() // I put resolve here because it is not until everything is finished above that this function is finished
console.log('useData() done')
}).catch((error) => { console.error(error.message) })
})
}
to this:
async function useData() {
try {
console.log('Starting useData()')
const jsonListFromCloud = await readFromCloudFileserver();
for (let item of jsonListFromCloud) {
await handleDataItem(item);
}
console.log('useData() done');
} catch (error) {
// log error and rethrow so caller gets the error
console.error(error.message)
throw error;
}
}
The structural changes here are:
Switch to use async/await to more easily handle the asynchronous items in a loop
Remove the promise anti-pattern that wraps new Promise() around an existing promise - no need for that AND you weren't capturing or propagating rejections from readFromCloudFileServer() which is a common mistake when using that anti-pattern.
rethrow the error inside your catch after logging the error so the error gets propagated back to the caller

How to send firebase notifications using nodejs?

I want to implement push notifications automatically and I've used javascript (node.js) but I got this error
Function returned undefined, expected Promise or value
I am not node js developer I am a flutter developer and I don't know what is promises.
this is my code:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
var notificationMessageData;
exports.fcmTester = functions.firestore.document('posts/{postID}').onCreate((snapshot , context) => {
notificationMessageData = snapshot.data();
admin.firestore().collection('pushTokens').get().then(async (snapshot) => {
var tokens = [];
if (snapshot.empty) {
console.log('No Devices');
} else {
for (var token of snapshot.docs) {
tokens.push(token.data().tokenID);
}
var payload = {
"notification": {
"title": "from" + notificationMessageData.writer,
"body": "from" + notificationMessageData.name,
"sound": "default"
},
"data": {
"sendername": notificationMessageData.writer,
"message": notificationMessageData.name
}
}
return await admin.messaging().sendToDevice(tokens , payload).then((response) => {
console.log('nice');
}).catch((err) => {
console.log(err);
})
}
})
})
Everything is going okay and I upload it without any problem but when adding a document to the posts collection it outputs in the logs the above error.
I have created a user registration form and I've registered the users and put their token id in a collection called pushTokens and then sending a notification for each user inside that collection but this didn't work.
There are two problems in your code:
You don't return the promises returned by the asynchronous Firebase methods (get() and sendToDevice());
You mix-up the use of async/await with the then() method.
I would suggest you watch the 3 official videos about "JavaScript Promises" from the Firebase video series, and then that you first try with using the then() method to correctly chain your Promises and return the chain.
The following code should work.
exports.fcmTester = functions.firestore.document('posts/{postID}').onCreate((snapshot, context) => {
const notificationMessageData = snapshot.data();
return admin.firestore().collection('pushTokens').get()
.then(snapshot => {
var tokens = [];
if (snapshot.empty) {
console.log('No Devices');
throw new Error('No Devices');
} else {
for (var token of snapshot.docs) {
tokens.push(token.data().tokenID);
}
var payload = {
"notification": {
"title": "from" + notificationMessageData.writer,
"body": "from" + notificationMessageData.name,
"sound": "default"
},
"data": {
"sendername": notificationMessageData.writer,
"message": notificationMessageData.name
}
}
return admin.messaging().sendToDevice(tokens, payload)
}
})
.catch((err) => {
console.log(err);
return null;
})
});
Then, after having experienced the "management" of asynchronous methods with then() (and catch()), you may give a try with async/await: again, this official video from Doug Stevenson will be of great help.
It says:
Function returned undefined, expected Promise or value
Have you tried returning null, or true or false, etc. from your functions?
A promise is something you pass around your program while you wait for it to become a real value.
It seems like the error message is focused on wanting you to return something from your function. Without knowing more, this is the best advice I can give.

nodejs mongodb - how to return the inserted document using insertOne

I am using "mongodb": "^3.1.6",.
I have a method using the drivers insertOne method (shops is my mongoDb database collection):
/**
* Adds a new shop to the shops collection
* #param {Shop} doc - the new shop to add
*/
static async addShop(shop) {
try {
return await shops.insertOne(shop, {}, (err, result) => {
if (err) {
throw e
}
return result
})
// TODO this should return the new shop
} catch (e) {
console.error(`Something went wrong in addShop : ${e}`)
throw e
}
}
Now the method inserts the document into the collection as expected, but does not return the insert result. How do I return the result value of the callback function?
For reference - I wrote this unit test that I want to get to pass:
test("addShop returns the added shop", async () => {
const testShop = {
name: "Test shop for jest unit tests",
}
const newShop = await ShopsDAO.addShop(testShop)
const shoppingCart = global.DBClient.db(process.env.NS)
const collection = shoppingCart.collection("shops")
expect(newShop.name).toEqual(testShop.name)
await collection.remove({ name: testShop.name })
})
Thanks for the help.
I suggest you not to mix promises and callbacks as it is a bad way to organize your code. According to docs, if you do not pass callback insertOne will return Promise. So I suggest you to rewrite function smth like that:
static async addShop(shop) {
try {
return shops.insertOne(shop)
} catch (insertError) {
console.error(`Something went wrong in addShop : ${insertError}`)
throw insertError
}
}
This addShop will return promise, use await to retrieve data from it:
const newShop = await ShopsDAO.addShop(testShop)
P.S. You can also omit await with return statement

How to use mongoose aggregate well

I am using this query search with Mongodb to get only '_id' value matched from 'clientID'. It works well at roto3T.
db.getCollection('orders').find({"clientID":"1234"}, {$_id:true});
But I want to use this at mongoose. So I put like below.
But It doesn't work well. How can I make the query search well in Mongoose?
Thank you so much!
await Order.find({ clientID: { $_id: true },},
(error, order) => {
if (error) {
return next(error);
}
return res.send({order,});
},);
if you want to use Async Await,
try {
let order = await Order.find({"clientID":"1234"}, "_id");
return res.send({order});
} catch(error) {
return next(error);
}
or otherwise if you want to use callback
return Order.find({clientID:"1234" },{ '_id': 1 },(error,order) => {
if (error) return next(error);
return res.send({order});
});
You can't use both to retrieve your data

access state data from store into a string vue.js

Hey guys so I am trying to add state data from my store into a string in one of my axios calls within actions. Here is my store:
export const store = new Vuex.Store
({
state: {
participants: [],
filterTags: ["foo", "swag"],
page: 1,
perPage: 2,
filterTagName: "",
}
}
Here is my action call:
actions: {
async loadParticipants ({commit}) {
try {
console.log(this.state.page);
await axios
.get('/dash/participant?perPage=2&page=1&string=f')
.then(r => r.data)
.then(participants => {
console.log(participants.docs);
console.log("Hit me");
commit('setParticipants', participants)
})
}
catch (e) {
if (e.response)
console.log(e.response);
throw e
}
}
I want to add the store's state data where it says { INSERT DATA HERE } within the axios call:
.get('/dash/participant?perPage={INSERT DATA HERE }&page={ INSERT DATA HERE }&string=f')
Any input appreciated thank you!
In your action, you have access to the whole store, so instead to just getting only the commit declaring the param as ({commit}), you can add the state too:
async loadParticipants ({commit, state}) {
So you can use the state variable in your method body:
actions: {
async loadParticipants ({commit, state}) {
try {
console.log(this.state.page);
await axios
.get(`/dash/participant?perPage=${state.perPage}&page=${state.page}&string=${state.filterTagName}`)
.then(r => r.data)
.then(participants => {
console.log(participants.docs);
console.log("Hit me");
commit('setParticipants', participants)
})
}
catch (e) {
if (e.response)
console.log(e.response);
throw e
}
}
}
So you just want to fill your query params with the values from your vuex store?
Just pass the state into your action. And then you can add the state to your query params with a little help of template iterals. ${some-js-variable}
You can also directly destruct the response and grab the data.
Not sure why you make promise like then() statements if you use async and await.
actions: {
async loadParticipants ({commit, state}) {
try {
const {data} = await axios.get(`/dash/participant?perPage=${state.perPage}&page=${state.page}&string=f`)
console.log(data)
}
catch (e) {
if (e.response)
console.log(e.response);
throw e
}
}

Resources