Node.js JIMP restarting app after file was saved - javascript

I have such a small app where I am using JIMP. My goal here is to add a watermark on a jpeg file. The main part of my app is working fine, but I want to add a "Retry" function for users. Now, this part is not working. Retry part is called before my edited file was saved
const Jimp = require('jimp');
const inquirer = require('inquirer');
const fs = require('fs');
const addTextWatermarkToImage = async function(inputFile, outputFile, text) {
try {
const image = await Jimp.read(inputFile);
const font = await Jimp.loadFont(Jimp.FONT_SANS_32_BLACK);
const textData = {
text: text,
alignmentX: Jimp.HORIZONTAL_ALIGN_CENTER,
alignmentY: Jimp.VERTICAL_ALIGN_MIDDLE,
};
image.print(font, 10, 10, textData, image.getWidth(), image.getHeight());
image.quality(100).write(outputFile);
} catch (error) {
console.log('Error. Something went wrong');
}
};
const addImageWatermarkToImage = async function(
inputFile,
outputFile,
watermarkFile
) {
try {
const image = await Jimp.read(inputFile);
const watermark = await Jimp.read(watermarkFile);
const x = image.getWidth() / 2 - watermark.getWidth() / 2;
const y = image.getHeight() / 2 - watermark.getHeight() / 2;
image.composite(watermark, x, y, {
mode: Jimp.BLEND_SOURCE_OVER,
opacitySource: 0.5,
});
image.quality(100).write(outputFile);
} catch (error) {
console.log('Error. Something went wrong');
}
};
const prepareOutputFilename = filename => {
const [name, ext] = filename.split('.');
return `${name}-with-watermark.${ext}`;
};
const startApp = async () => {
//check if user is ready
const answer = await inquirer.prompt([
{
name: 'start',
message:
'Hi! This is "Watermar manager". Move your files to `/img` folder. Then you\'ll be able to use them in the app. Are you ready?',
type: 'confirm',
},
]);
// if no - exit app
if (!answer.start) process.exit();
// ask about input file and watermark type
const options = await inquirer.prompt([
{
name: 'inputImage',
type: 'input',
message: 'What file do you want to mark?',
default: 'test.jpg',
},
{
name: 'watermarkType',
type: 'list',
choices: ['Text watermark', 'Image watermark'],
},
]);
if (options.watermarkType === 'Text watermark') {
const text = await inquirer.prompt([
{
name: 'value',
type: 'input',
message: 'Type your watermark text:',
},
]);
options.watermarkText = text.value;
if (fs.existsSync('./img/' + options.inputImage) === true) {
addTextWatermarkToImage(
'./img/' + options.inputImage,
'./img/' + prepareOutputFilename(options.inputImage),
options.watermarkText
);
} else {
console.log('O_o Error!!!! No such file');
}
} else {
const image = await inquirer.prompt([
{
name: 'filename',
type: 'input',
message: 'Type your watermark name:',
default: 'logo.png',
},
]);
options.watermarkImage = image.filename;
if (
fs.existsSync('./img/' + options.inputImage) === true &&
fs.existsSync('./img/' + options.watermarkImage) === true
) {
addImageWatermarkToImage(
'./img/' + options.inputImage,
'./img/' + prepareOutputFilename(options.inputImage),
'./img/' + options.watermarkImage
);
} else {
console.log('O_o Error!!!! No such file');
}
}
console.log('File was saved - now');
const retry = await inquirer.prompt([
{
name: 'retry',
message: 'Do you want to try again',
type: 'confirm',
},
]);
if (!retry.retry) process.exit();
startApp();
};
startApp();
I want to re-run this app after my file was saved. Right now
const retry = await inquirer.prompt([
{
name: 'retry',
message: 'Do you want to try again',
type: 'confirm',
},
]);
if (!retry.retry) process.exit();
startApp();
this part of my code runs before my file was saved and this can casue some problems.
Any suggestions on how to deal with this issue?

You are calling few async method with out using await statement (addImageWatermarkToImage, addImageWatermarkToImage). use await and check the same.
//Consider the following example. Execute the test method with and with out awit, it may help you to understood
function timeout(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function sleep(fn, ...args) {
await timeout(3000);
return fn(...args);
}
async function test() {
//add and remove the below await and execute this function
await sleep(()=>{console.log("Run first")},{});
console.log("Run Second");
}
test();

Related

Firestore : why using serverTimestamp gives different results?

I am having a hard time understanding serverTimestamp in firestore.
When I save a document in database in a firebase function using Fieldvalue.serverTimestamp() or in a javascript client code using serverTimestamp() it sometimes doesn't save the same thing in the database.
See screenshots below :
Sometime I get an object with {nanoseconds: xxx, seconds: xxx} and sometimes I get a timestamp formatted date...
The problem is when I try to query my orders using query(collectionRefOrders, orderBy('createdAt', 'desc'), limit(10)).
The orders with the object appears before the others ones even if they are created after...
Any clue why this happens ? What am I doing wrong ?
Thanks a lot.
EDIT :
Here is the code I use to add documents in the my firebase function (it is a request function I call in a website) :
const { getFirestore, FieldValue } = require('firebase-admin/firestore');
const firebaseDB = getFirestore();
exports.createOrderFromTunnel = functions.region('europe-west3')
.runWith({
timeoutSeconds: 10,
memory: "4GB",
})
.https
.onRequest(async (req, res) => {
cors(req, res, async () => {
try {
const { apiKey } = req.body;
const project = await getProjectFromApiKey(apiKey);
if (!project) {
return res.json({
success: false,
error: 'Unauthorized: invalid or missing api key'
});
}
const contactData = {
address: {},
createdAt: FieldValue.serverTimestamp()
};
const orderData = {
accounting: {
totalHT: 0,
totalTTC: 0,
totalTVA: 0,
},
createdAt: FieldValue.serverTimestamp(),
status: 'NEW',
};
const refProject = firebaseDB
.collection('projects')
.doc(project.id);
const colOrder = firebaseDB.collection(`projects/${project.id}/orders`)
const refOrder = colOrder.doc();
const colContact = firebaseDB.collection(`projects/${project.id}/contacts`)
const refContact = colContact.doc();
await firebaseDB.runTransaction(async transaction => {
const snapProject = await transaction.get(refProject);
const dataProject = snapProject.data();
const sequenceContact = dataProject.sequenceContact;
const sequenceOrder = dataProject.sequenceOrder;
contactData.sequence = sequenceContact;
orderData.sequenceNumber = sequenceOrder;
await transaction.set(refContact, contactData);
orderData.customer.id = refContact.id;
orderData.customer.sequence = sequenceContact;
await transaction.set(refOrder, orderData);
await transaction.update(refProject, {
sequenceContact: sequenceContact + 1,
sequenceOrder: sequenceOrder + 1,
totalContacts: dataProject.totalContacts + 1,
totalOrders: dataProject.totalOrders + 1,
});
return refOrder.id;
});
return res.json({
success: true
});
} catch (err) {
functions.logger.error(err);
return res.json({
success: false,
err
});
}
});
});
Here is the code I use to add documents in my client code (it is a web app in javascript) :
const createOrder = async (projectId) => {
try {
const orderData = {
accounting: {
totalHT: 0,
totalTTC: 0,
totalTVA: 0,
},
createdAt: serverTimestamp(),
status: 'NEW',
surface: 0,
};
const refProject = doc(firebaseDB, 'projects', projectId);
const colOrder = collection(firebaseDB, `projects/${projectId}/orders`)
const refOrder = doc(colOrder);
return await runTransaction(firebaseDB, async (transaction) => {
const snapProject = await transaction.get(refProject);
if (!snapProject.exists()) {
throw "Document does not exist!";
}
const dataProject = snapProject.data();
const sequence = dataProject.sequenceOrder;
orderData.sequenceNumber = sequence;
transaction.set(refOrder, orderData);
transaction.update(refProject, { sequenceOrder: sequence + 1, totalOrders: dataProject.totalOrders + 1 });
return refOrder.id;
});
} catch (e) {
console.error(e);
return null;
}
};

Broadcasting to all clients with Deno websocket

I want to add notifications to an application I've developed.
Unfortunately, Deno has removed the ws package.(https://deno.land/std#0.110.0/ws/mod.ts)
That's why I'm using the websocket inside the denon itself. Since it doesn't have many functions, I have to add some things myself.
For example, sending all messages to open clients.
What I want to do is when the pdf is created, a (data, message) comes from the socket and update the notifications on the page according to the incoming data.
I keep all open clients in a Map. and when the pdf is created, I return this Map and send it to all sockets (data, message).
However, this works for one time.
server conf...
import {
path,
paths,
ctid,
} from "../deps.ts";
const users = new Map();
const sockets = new Map()
const userArr = [];
export const startNotif = (socket,req) => {
const claims = req.get("claims");
const org = req.get("org");
claims.org = org;
console.log("connected")
users.set(claims.sub, {"username":claims.sub,"socket":socket})
users.forEach((user)=>{
if(userArr.length === 0){
userArr.push(user)
}
else if(userArr.every((w)=> w.username !== user.username) )
userArr.push(user)
})
sockets.set(org, userArr)
function broadcastMessage(message) {
sockets.get(org).map((u)=>{
console.log(u.socket.readyState)
u.socket.send(message)
})
}
if (socket.readyState === 3) {
sockets.delete(uid)
return
}
const init = (msg) => {
socket.send(
JSON.stringify({
status: "creating",
})
);
};
const ondata = async (msg) => {
const upfilepath = path.join(paths.work, `CT_${msg.sid}_report.pdf`);
try {
const s=await Deno.readTextFile(upfilepath);
if(s){
socket.send(
JSON.stringify({
status: "end",
})
);
} else {
socket.send(
JSON.stringify({
status: "creating",
})
);
}
} catch(e) {
if(e instanceof Deno.errors.NotFound)
console.error('file does not exists');
}
};
const end = () => {
try {
const endTime = Date.now()
const msg = "Your PDF has been created"
const id = ctid(12) // random id create
broadcastMessage(
JSON.stringify({
id: id,
date: endTime,
status: "done",
message: msg,
read: 'negative',
action: 'pdf'
})
);
} catch (e) {
console.log(400, "Cannot send.", e);
}
}
socket.onmessage = async (e) => {
const cmd = JSON.parse(e.data);
if(cmd.bid === 'start'){
await init(cmd)
}
if(!cmd.bid && cmd.sid){
await ondata(cmd)
}
if(cmd.bid === 'end'){
await end();
}
}
socket.onerror = (e) => {
console.log(e);
};
}
client conf...
export const webSocketHandler = (request) =>
new Promise((res, rej) => {
let url;
if (!process.env.NODE_ENV || process.env.NODE_ENV === 'development') {
url = `http://localhost:8080/api/notifications/ws`.replace('http', 'ws');
} else {
url = `${window.location.origin}/api/notifications/ws`.replace('http', 'ws');
}
const token = JSON.parse(sessionStorage.getItem('token'));
const orgname = localStorage.getItem('orgname');
const protocol = `${token}_org_${orgname}`;
const socket = new WebSocket(url, protocol);
const response = Object.create({});
socket.onopen = function () {
socket.send(
JSON.stringify({
bid: 'start',
})
);
};
socket.onmessage = function (event) {
response.data = JSON.parse(event.data);
if (response.data.status === 'creating') {
socket.send(
JSON.stringify({
sid: request.sid,
})
);
} else if (response.data.status === 'end') {
socket.send(
JSON.stringify({
bid: 'end',
})
);
} else if (response.data.status === 'done') {
try {
res(response);
} catch (err) {
rej(err);
}
}
};
socket.onclose = function (event) {
response.state = event.returnValue;
};
socket.onerror = function (error) {
rej(error);
};
});
onclick function of button I use in component...
const donwloadReport = async (type) => {
const query = `?sid=${sid}&reportType=${type}`;
const fileName = `CT_${sid}_report.${type}`;
try {
type === 'pdf' && setLoading(true);
const response = await getScanReportAction(query);
const request = {
sid,
};
webSocketHandler(request)
.then((data) => {
console.log(data);
dispatch({
type: 'update',
data: {
id: data.data.id,
date: data.data.date,
message: data.data.message,
action: data.data.action,
read: data.data.read,
},
});
})
.catch((err) => {
console.log(err);
});
if (type === 'html') {
downloadText(response.data, fileName);
} else {
const blobUrl = await readStream(response.data);
setLoading(false);
downloadURL(blobUrl, fileName);
}
} catch (err) {
displayMessage(err.message);
}
};
Everything works perfectly the first time. When I press the download button for the pdf, the socket works, then a data is returned and I update the notification count with the context I applied according to this data.
Later I realized that this works in a single tab. When I open a new client in the side tab, my notification count does not increase. For this, I wanted to keep all sockets in Map and return them all and send a message to each socket separately. But in this case, when I press the download button for the second time, no data comes from the socket.
Actually, I think that I should do the socket initialization process on the client in the context. When you do this, it starts the socket 2 times in a meaningless way.
In summary, consider an application with organizations and users belonging to those organizations. If the clients of A, B, C users belonging to X organization are open at the same time and user A pressed a pdf download button, I want A, B, C users to be notified when the pdf is downloaded.
I would be very grateful if someone could show me a way around this issue.
Have you looked at the BroadcastChannel API? Maybe that could solve your issue. See for example:
Deno specific: https://medium.com/deno-the-complete-reference/broadcast-channel-in-deno-f76a0b8893f5
Web/Browser API: https://developer.mozilla.org/en-US/docs/Web/API/Broadcast_Channel_API

Return call reached before return statement in callback

I'm trying to add a image upload feature to react-draft-wysiwyg editor.
As per the editor documentation,
image: uploadCallback: This is image upload callBack. It should return a promise that resolves to give image src. Default value is true.
Both above options of uploadEnabled and uploadCallback should be present for upload to be enabled.
Promise should resolve to return an object { data: { link: <THE_URL>}}.
Source : https://jpuri.github.io/react-draft-wysiwyg/#/docs
The problem I'm facing is that, while uploading the image to firebase, and the resultant url, I'm trying to use it to create this object for returning from this callback.
Here, in code, firstly image is being uploaded using uploadBytes method of firebase, then using getDownloadURL, we are getting the url of the file. There is a bug that the object with undefined url link is being returned from the callback.
const uploadImageCallBack = (file) => {
let linkImg = "";
let arr = [];
const saveImagePromise = new Promise((resolve, reject) => {
const fileNameParts = file.name.split(".");
const storage = getStorage(app);
const storageRef = ref(
storage,
"Random/" +
title +
"/editorImage." +
uuidv4() +
fileNameParts[fileNameParts.length - 1]
);
debugger;
const metadata = {
contentType: file.type,
};
try {
const uploadTask = uploadBytes(storageRef, file, metadata);
debugger;
uploadTask.then((snapshot) => {
debugger;
const downloadURLPromise = getDownloadURL(storageRef);
downloadURLPromise.then((url) => {
linkImg = url;
debugger;
});
arr.push(downloadURLPromise);
});
arr.push(uploadTask);
} catch (error) {
console.log(error);
reject(error);
}
});
arr.push(uploadBytes, saveImagePromise);
console.log(Infinity);
Promise.all(arr).then((res) => {
console.log(res);
console.log(Infinity);
return new Promise((resolve, reject) => {
resolve({ data: { link: linkImg } });
});
});
};
and the code for editor is
<Editor
toolbar={{
inline: { inDropdown: true },
list: { inDropdown: true },
textAlign: { inDropdown: true },
link: { inDropdown: true },
history: { inDropdown: true },
image: {
urlEnabled: true,
uploadEnabled: true,
alignmentEnabled: true,
uploadCallback: uploadImageCallBack,
previewImage: true,
inputAccept:
"image/gif,image/jpeg,image/jpg,image/png,image/svg",
alt: { present: true, mandatory: false },
defaultSize: {
height: "auto",
width: "auto",
},
},
}}
onContentStateChange={(data) => {
let res = convertToPlain(draftToHtml(data));
console.log(data);
setReasonProgress(
remainigchar >= 100 ? 100 : remainigchar
);
}}
wrapperClassName="wrapper-class"
editorClassName="editor-class"
toolbarClassName="toolbar-class"
/>```
Please help me to create a correct return statement.
I solved this problem by using async function and adding wait statement for the url.
const uploadImageCallBack = async (file) => {
const fileNameParts = file.name.split(".");
const storage = getStorage(app);
const storageRef = ref(
storage,
uuidv4() +
fileNameParts[fileNameParts.length - 1]
);
let imageObject = {
file: file,
localSrc: URL.createObjectURL(file),
};
const metadata = {
contentType: file.type,
};
const snapshot = await uploadBytes(storageRef, file, metadata);
const url = await getDownloadURL(storageRef);
console.log(url, snapshot);
return new Promise((resolve, reject) => {
resolve({ data: { link: url } });
});
};

Gatsby createPages lifecycle method running before onCreateNode method finishes adding needed node fields

I am working on a Gatsby site that uses gatsby-source-wordpress. In my gatbsy-node.js file, I use the onCreateNote lifecycle method to determine if the node is a certain WordPress custom post type, then I reach out to a separate API to get related information for the post type, use createNodeField to add it as a field, and sometimes also use createRemoteFileNode to add images sourced from the API to a field on the new node.
Now this works great most of the time, but occasionally the createPages lifecycle method runs while the image/node code is still happening (I believe). This means that the image fields don't exist yet, and the page creation fails. Then after it fails, I see a console message in the log that I set up where it notifies me that the new field has successfully been added to the node.
How can I make sure that all of those nodes are finished and the data is complete, BEFORE the createPages lifecycle runs? It seems when the client uploads a larger image, this is more likely to fail... which makes sense if I'm understanding this correctly. Here is the code from my gatsby-node.js file:
const path = require(`path`);
const slash = require(`slash`);
const fetch = require('node-fetch');
const { createRemoteFileNode } = require(`gatsby-source-filesystem`)
exports.onCreateNode = ({ node, actions, store, cache,createNodeId, }) => {
const { createNode, createNodeField } = actions;
function getData(url) {
return new Promise((resolve, reject) => {
fetch(url)
.then((response) => response.json())
.then((data) => {
resolve(data);
});
})
}
if( node.internal.type === "wordpress__wp_location"){
const yextID = node.acf.yext_entity_id;
const yextOrthos = node.acf.location_orthodontists;
try {
const getLocation = async () => {
const data = await fetch("https://api.yext.com/v2/accounts/me/entities?api_key=" + process.env.YEXT_API_KEY + "&v=20191114&filter=%7B%22%24anywhere%22%3A%20%22" + yextID + "%22%7D&entityTypes=healthcareFacility")
.then(response => response.json());
// Transform the data into json
if( data && data.response && data.response.count === 1 ){
createNodeField({
node,
name: `yextLocation`,
value: data.response.entities[0]
});
} else {
console.log("NO LOCATIONS FOUND");
}
};
function getOrthos(){
let orthodontists = [];
yextOrthos.forEach( (ortho, i) => {
orthodontists.push(getData("https://api.yext.com/v2/accounts/me/entities?api_key=" + process.env.YEXT_API_KEY + "&v=20191114&filter=%7B%22%24anywhere%22%3A%20%22" + ortho.acf.yext_entity_ortho_id + "%22%7D&entityTypes=healthcareProfessional"));
});
Promise.all(orthodontists).then( (orthoData) => {
if( orthoData.length ){
let finalOrthos = [];
orthoData.forEach( (finalOrtho, x) => {
finalOrthos.push(finalOrtho.response.entities[0]);
});
createNodeField({
node,
name: `yextOrthos`,
value: finalOrthos
});
} else {
console.log("NO DOCTORS FOUND");
}
});
}
getLocation();
getOrthos();
} catch (error) {
console.log(error);
}
}
if( node.internal.type === "wordpress__wp_orthodontist"){
const yextID = node.acf.yext_entity_ortho_id;
const wpID = node.wordpress_id;
try {
const getTextOrtho = async () => {
const data = await fetch("https://api.yext.com/v2/accounts/me/entities?api_key=" + process.env.YEXT_API_KEY + "&v=20191114&filter=%7B%22%24anywhere%22%3A%20%22" + yextID + "%22%7D&entityTypes=healthcareProfessional")
.then(response => response.json());
// Transform the data into json
if( data && data.response && data.response.count === 1 ){
googleProfilePhoto = data.response.entities[0].googleProfilePhoto.url;
createNodeField({
node,
name: `yextOrthodontist`,
value: data.response.entities[0]
});
if( data.response.entities[0].googleProfilePhoto && data.response.entities[0].googleProfilePhoto.url){
createNodeField({
node,
name: `yextProfilePicture`,
value: data.response.entities[0].googleProfilePhoto.url
});
let fileNode = await createRemoteFileNode({
url: data.response.entities[0].googleProfilePhoto.url, // string that points to the URL of the image
parentNodeId: node.id, // id of the parent node of the fileNode you are going to create
createNode, // helper function in gatsby-node to generate the node
createNodeId, // helper function in gatsby-node to generate the node id
cache, // Gatsby's cache
store, // Gatsby's redux store
})
// if the file was created, attach the new node to the parent node
if (fileNode) {
console.log("GOOGLE PROFILE NODE CREATED!")
node.featuredImg___NODE = fileNode.id
} else {
console.log("ERROR! fileNode not Created!");
}
} else {
console.log("NO GOOGLE PROFILE PHOTO FOUND");
}
} else {
console.log("NO ORTHODONTISTS FOUND");
}
}
const getWpLocations = async () => {
const data = await fetch(process.env.GATSBY_WP_BASEURL+ "/wp-json/custom_endpoint/v1/locations_by_orthodontist?orthodontist_id=" + wpID).then(response => response.json());
if( data ){
createNodeField({
node,
name: `wpLocations`,
value: data
});
} else {
console.log("NO ORTHODONTISTS FOUND");
}
}
getTextOrtho();
getWpLocations();
} catch (error) {
console.log(error);
}
}
}
exports.createPages = async ({ graphql, actions }) => {
const { createPage } = actions;
const result = await graphql(`
{
locations: allWordpressWpLocation(filter: {status: {eq: "publish"}}) {
nodes {
id
path
acf {
location_orthodontists {
acf {
yext_entity_ortho_id
}
}
yext_entity_id
}
}
}
pages: allWordpressPage(
filter: {
wordpress_id: {nin: [177, 183, 8, 42, 44, 185, 46]}
status: {eq: "publish"}
}) {
nodes {
id
wordpress_id
path
}
}
orthodontists: allWordpressWpOrthodontist(filter: {status: {eq: "publish"}}) {
nodes {
id
path
}
}
posts: allWordpressPost(filter: {status: {eq: "publish"}}) {
nodes {
slug
id
}
}
}
`);
// Check for any errors
if (result.errors) {
throw new Error(result.errors);
}
const { locations, pages, orthodontists, posts } = result.data;
const locationTemplate = path.resolve(`./src/templates/location.js`);
const pageTemplate = path.resolve(`./src/templates/page.js`);
const orthoTemplate = path.resolve(`./src/templates/orthodontist.js`);
const postTemplate = path.resolve(`./src/templates/post.js`);
const blogTemplate = path.resolve(`./src/templates/blog.js`);
locations.nodes.forEach(node => {
let orthodontists = [];
node.acf.location_orthodontists.forEach(ortho => {
orthodontists.push(ortho.acf.yext_entity_ortho_id);
});
let orthodontistList = orthodontists.join();
createPage({
path: `${node.path}`,
component: slash(locationTemplate),
context: {
id: node.id,
yextId: node.acf.yext_entity_id,
yextOrthoIds: orthodontists
},
});
});
pages.nodes.forEach(node => {
createPage({
path: `${node.path}`,
component: slash(pageTemplate),
context: {
id: node.id,
},
});
});
orthodontists.nodes.forEach(node => {
createPage({
path: `${node.path}`,
component: slash(orthoTemplate),
context: {
id: node.id,
},
});
});
posts.nodes.forEach(node => {
createPage({
path: `${node.slug}`,
component: slash(postTemplate),
context: {
id: node.id,
},
});
});
const postsPerPage = 12;
const numPages = Math.ceil(posts.nodes.length / postsPerPage);
Array.from({ length: numPages }).forEach((_, i) => {
createPage({
path: i === 0 ? `/blog` : `/blog/page/${i + 1}`,
component: slash(blogTemplate),
context: {
limit: postsPerPage,
skip: i * postsPerPage,
numPages,
currentPage: i + 1,
},
})
})
};
Thanks for any information you can provide! I imagine this is probably due to me still learning to use asynchronous behavior in JS, but I just can't seem to find information on how to make this happen.
Please let me know if I can explain the situation any better!
After a rewrite, this seems to have solved the issue I was having. I'll be honest, I'm still working on completely understanding the ins and outs on async/await/promises functionality in JS, but hopefully if someone encounters a similar problem, viewing this rewrite may help:
const path = require(`path`);
const slash = require(`slash`);
const fetch = require('node-fetch');
const { createRemoteFileNode } = require(`gatsby-source-filesystem`)
exports.onCreateNode = async ({ node, actions, store, cache,createNodeId, }) => {
const { createNode, createNodeField } = actions;
const getData = async (url) => {
return new Promise((resolve, reject) => {
fetch(url)
.then((response) => response.json())
.then((data) => {
resolve(data);
});
})
}
const getLocation = async (yextID) => {
const data = await getData("https://api.yext.com/v2/accounts/me/entities?api_key=" + process.env.YEXT_API_KEY + "&v=20191114&filter=%7B%22%24anywhere%22%3A%20%22" + yextID + "%22%7D&entityTypes=healthcareFacility");
// Transform the data into json
if( data && data.response && data.response.count === 1 ){
createNodeField({
node,
name: `yextLocation`,
value: data.response.entities[0]
});
} else {
console.log("NO LOCATIONS FOUND");
}
};
const getOrthos = async (yextOrthos) => {
let orthodontists = [];
yextOrthos.forEach( (ortho, i) => {
orthodontists.push(getData("https://api.yext.com/v2/accounts/me/entities?api_key=" + process.env.YEXT_API_KEY + "&v=20191114&filter=%7B%22%24anywhere%22%3A%20%22" + ortho.acf.yext_entity_ortho_id + "%22%7D&entityTypes=healthcareProfessional"));
});
Promise.all(orthodontists).then( (orthoData) => {
if( orthoData.length ){
let finalOrthos = [];
orthoData.forEach( (finalOrtho, x) => {
finalOrthos.push(finalOrtho.response.entities[0]);
});
createNodeField({
node,
name: `yextOrthos`,
value: finalOrthos
});
} else {
console.log("NO DOCTORS FOUND");
}
});
};
const getTextOrtho = async (yextID) => {
const data = await getData("https://api.yext.com/v2/accounts/me/entities?api_key=" + process.env.YEXT_API_KEY + "&v=20191114&filter=%7B%22%24anywhere%22%3A%20%22" + yextID + "%22%7D&entityTypes=healthcareProfessional");
if( data && data.response && data.response.count === 1 ){
createNodeField({
node,
name: `yextOrthodontist`,
value: data.response.entities[0]
});
if( data.response.entities[0].googleProfilePhoto && data.response.entities[0].googleProfilePhoto.url){
createNodeField({
node,
name: `yextProfilePicture`,
value: data.response.entities[0].googleProfilePhoto.url
});
let fileNode = await createRemoteFileNode({
url: data.response.entities[0].googleProfilePhoto.url, // string that points to the URL of the image
parentNodeId: node.id, // id of the parent node of the fileNode you are going to create
createNode, // helper function in gatsby-node to generate the node
createNodeId, // helper function in gatsby-node to generate the node id
cache, // Gatsby's cache
store, // Gatsby's redux store
});
// if the file was created, attach the new node to the parent node
if (fileNode) {
node.featuredImg___NODE = fileNode.id;
console.log("GOOGLE PROFILE NODE CREATED!")
} else {
console.log("ERROR! fileNode not Created!");
}
} else {
console.log("NO GOOGLE PROFILE PHOTO FOUND");
}
} else {
console.log("NO ORTHODONTISTS FOUND");
}
};
const getWpLocations = async (wpID) => {
const data = await getData(process.env.GATSBY_WP_BASEURL+ "/wp-json/perch_endpoint/v1/locations_by_orthodontist?orthodontist_id=" + wpID);
if( data ){
createNodeField({
node,
name: `wpLocations`,
value: data
});
} else {
console.log("NO ORTHODONTISTS FOUND");
}
}
if( node.internal.type === "wordpress__wp_location"){
const yextID = node.acf.yext_entity_id;
const yextOrthos = node.acf.location_orthodontists;
try {
await getLocation(yextID);
await getOrthos(yextOrthos);
} catch (error) {
console.log(error);
}
}
if( node.internal.type === "wordpress__wp_orthodontist"){
const yextID = node.acf.yext_entity_ortho_id;
const wpID = node.wordpress_id;
try {
await getTextOrtho(yextID);
await getWpLocations(wpID);
} catch (error) {
console.log(error);
}
}
}
exports.createPages = async ({ graphql, actions }) => {
const { createPage } = actions;
const result = await graphql(`
{
locations: allWordpressWpLocation(filter: {status: {eq: "publish"}}) {
nodes {
id
path
acf {
location_orthodontists {
acf {
yext_entity_ortho_id
}
}
yext_entity_id
}
}
}
pages: allWordpressPage(
filter: {
wordpress_id: {nin: [177, 183, 8, 42, 44, 185, 46]}
status: {eq: "publish"}
}) {
nodes {
id
wordpress_id
path
}
}
orthodontists: allWordpressWpOrthodontist(filter: {status: {eq: "publish"}}) {
nodes {
id
path
}
}
posts: allWordpressPost(filter: {status: {eq: "publish"}}) {
nodes {
slug
id
}
}
}
`);
// Check for any errors
if (result.errors) {
throw new Error(result.errors);
}
const { locations, pages, orthodontists, posts } = result.data;
const locationTemplate = path.resolve(`./src/templates/location.js`);
const pageTemplate = path.resolve(`./src/templates/page.js`);
const orthoTemplate = path.resolve(`./src/templates/orthodontist.js`);
const postTemplate = path.resolve(`./src/templates/post.js`);
const blogTemplate = path.resolve(`./src/templates/blog.js`);
locations.nodes.forEach(node => {
let orthodontists = [];
node.acf.location_orthodontists.forEach(ortho => {
orthodontists.push(ortho.acf.yext_entity_ortho_id);
});
let orthodontistList = orthodontists.join();
createPage({
path: `${node.path}`,
component: slash(locationTemplate),
context: {
id: node.id,
yextId: node.acf.yext_entity_id,
yextOrthoIds: orthodontists
},
});
});
pages.nodes.forEach(node => {
createPage({
path: `${node.path}`,
component: slash(pageTemplate),
context: {
id: node.id,
},
});
});
orthodontists.nodes.forEach(node => {
createPage({
path: `${node.path}`,
component: slash(orthoTemplate),
context: {
id: node.id,
},
});
});
posts.nodes.forEach(node => {
createPage({
path: `${node.slug}`,
component: slash(postTemplate),
context: {
id: node.id,
},
});
});
const postsPerPage = 12;
const numPages = Math.ceil(posts.nodes.length / postsPerPage);
Array.from({ length: numPages }).forEach((_, i) => {
createPage({
path: i === 0 ? `/blog` : `/blog/page/${i + 1}`,
component: slash(blogTemplate),
context: {
limit: postsPerPage,
skip: i * postsPerPage,
numPages,
currentPage: i + 1,
},
})
})
};

Async/await and promises [duplicate]

This question already has answers here:
Using async/await with a forEach loop
(33 answers)
Closed 4 years ago.
I am having an issue with using async/await using node v8.1. It appears my issue is that I am not returning a promise from my async functions. This is causing the flow of the program to run out of order. I thought by making a function async that the function automatically returns a promise, but that is not the case I am running into.
I expect the program below to output:
Validating xlsx file...
(text from validateParsedXlsx)
Adding users to cognito...
(text from addUsersToCognito)
Adding users to dynamodb...
(text from addUsersToDynamodb)
Instead, I get:
Validating xlsx file...
Adding users to cognito...
Adding users to dynamodb...
(text from validateParsedXlsx)
(text from addUsersToCognito)
(text from addUsersToDynamodb)
The issue seems to be pretty obvious, that validateParsedXlsx() addUsersToCognito() and addUsersToDynamodb() are not returning promises. Again, I thought that by using the async keyword, the function automatically took care of this.
Thanks for the help.
Here is my script:
const xlsx = require('xlsx');
const AWS = require('aws-sdk');
AWS.config.update({region: 'us-west-2'});
const documentClient = new AWS.DynamoDB.DocumentClient({convertEmptyValues: true});
async function main(){
if (!process.argv[2]) {
console.log('\nAbsolute filepath missing. Pass the absolute filepath in as command line argument.\n')
process.exit(1);
}
const xlsxFilePath = process.argv[2];
let parsedXlsx = [];
try {
parsedXlsx = parseXlsx(xlsxFilePath);
} catch (error) {
if(error.code === 'ENOENT') {
console.log(`\nThe file path: ${process.argv[2]} cannot be resolved\n`)
} else {
console.log(error);
}
}
console.log('\n\nValidating xlsx file...\n');
await validateParsedXlsx(parsedXlsx);
console.log('\n\nAdding users to cognito...\n');
await addUsersToCognito(parsedXlsx);
console.log('\n\nAdding users to dynamodb...\n');
await addUsersToDynamodb(parsedXlsx);
}
function parseXlsx(filePath) {
const workbook = xlsx.readFile(filePath);
const sheetNameList = workbook.SheetNames;
const parsedXlsxSheets = sheetNameList.map(function (y) {
const worksheet = workbook.Sheets[y];
const headers = {};
const data = [];
for (z in worksheet) {
if(z[0] === '!') continue;
//parse out the column, row, and value
const col = z.substring(0,1);
const row = parseInt(z.substring(1));
const value = worksheet[z].v;
//store header names
if(row == 1) {
headers[col] = value;
continue;
}
if(!data[row]) data[row] = {};
data[row][headers[col]] = value;
}
//drop those first two rows which are empty
data.shift();
data.shift();
return data;
});
return parsedXlsxSheets[0]
}
async function validateParsedXlsx(users) {
let error = false;
users.forEach(async (user, index) => {
if (!user.email) {
console.log(`User at row ${index + 2} doesn't have 'email' entry in xlsx file.`);
error = true;
}
if (!user.displayName) {
console.log(`User at row ${index + 2} doesn't have 'displayName' entry in xlsx file.`);
error = true;
}
if (!user.serviceProviderId) {
console.log(`Userat row ${index + 2} doesn't have 'displayName' entry in xlsx file.`);
error = true;
} else {
const params = {
TableName: 'service-providers',
Key: {
serviceProviderId: user.serviceProviderId
}
}
const response = await documentClient.get(params).promise();
if (!response.Item) {
console.log(`User at row ${index +2} does not have a valid serviceProviderId.`);
error = true;
} else {
console.log(`User ${user.email} is valid, assigned to service provider: ${response.Item.displayName}`);
}
}
if (error) {
console.log(`Every user in xlsx file must have these attributes, spelled correctly: email, displayName, and serviceProviderId\n\nIn addition, make sure the serviceProviderId is correct by checking the service-providers dynanomdb table.`);
process.exit(1);
}
});
}
async function addUsersToCognito(users) {
const cognitoIdentityServiceProvider = new AWS.CognitoIdentityServiceProvider();
const results = await cognitoIdentityServiceProvider.listUserPools({MaxResults: 10}).promise();
let serviceProviderUserPoolId = '';
results.UserPools.forEach((userPool) => {
if(userPool.Name === 'service-provider-users') {
serviceProviderUserPoolId = userPool.Id;
}
});
users.forEach(async (user) => {
const params = {
UserPoolId: serviceProviderUserPoolId,
Username: user.email,
DesiredDeliveryMediums: ['EMAIL'],
TemporaryPassword: 'New_User1',
UserAttributes: [
{
Name: 'email',
Value: user.email
},
{
Name: 'custom:service_provider_id',
Value: user.serviceProviderId
}
]
}
try {
await cognitoIdentityServiceProvider.adminCreateUser(params).promise();
console.log(`Added user ${user.email} to cognito user pool`);
} catch (error) {
if (error.code === 'UsernameExistsException') {
console.log(`Username: ${user.email} already exists. No action taken.`);
}
else {
console.log(error);
}
}
});
}
async function addUsersToDynamodb(users) {
users.forEach(async (user) => {
const params = {
TableName: 'service-provider-users',
Item: {
serviceProviderId: user.serviceProviderId,
userId: user.email,
displayName: user.displayName,
isActive: false,
role: 'BASIC'
},
ConditionExpression: 'attribute_not_exists(userId)'
}
try {
await documentClient.put(params).promise();
console.log(`Added user ${user.email} to dynamodb user table`);
} catch (error) {
if (error.code === 'ConditionalCheckFailedException') {
console.log(`User ${user.email} already in the dynamodb table service-provider-users`);
} else {
console.log(error);
}
}
});
}
main();
users.forEach(async (user, index) => {
That starts a few promising actions but never awaits them. May do:
await Promise.all(users.map(async (user, index) => {
... to execute them in parallel or do this:
await users.reduce((chain, user, index) => async (user, index) => {
await chain;
//...
}, Promise.resolve());
To execute them one after another.
PS: Using process.exit should be the very last option to end your program

Categories

Resources