Accessing Firestore via Cloud Function - node.js

So i have 2 Cloud Functions within the same file:
exports.Auth = functions.region('europe-west1').https.onRequest((req, res) =>
and
exports.IPN = functions.region('europe-west1').https.onRequest((req, res) =>
When adding the following code right at the start of my Auth function it adds a new document to the Firestore as expected, however, when i add the same code at the start of my IPN function, which is currently being called via Paypal's IPN Simulator, it does nothing, no errors.
let pin = RandomPIN(10, '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ');
var userRef = db.collection('Users').doc(pin);
var setWithOptions = userRef.set({ Activated: false }, { merge: true });
console.log("PIN: "+pin);
What on earth is going on, i must be missing something?
Thanks in advance.
Update:
Here are the logs, first with the 2 middle lines commented and then uncommented It seems to be silently failing, i'm just not sure what is causing it.
Update with Complete function:
exports.IPN = functions.region('europe-west1').https.onRequest((req, res) =>
{
console.log("IPN Notification Event Received");
let pin = RandomPIN(10, '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ');
var userRef = db.collection('Users').doc(pin);
var setWithOptions = userRef.set({ Activated: false }, { merge: true });
console.log("PIN: "+pin);
if (req.method !== "POST")
{
console.error("Request method not allowed.");
res.status(405).send("Method Not Allowed");
}
else
{
console.log("IPN Notification Event received successfully.");
res.status(200).end();
}
let ipnTransactionMessage = req.body;
// Convert JSON ipn data to a query string since Google Cloud Function does not expose raw request data.
let formUrlEncodedBody = querystring.stringify(ipnTransactionMessage);
// Build the body of the verification post message by prefixing 'cmd=_notify-validate'.
let verificationBody = `cmd=_notify-validate&${formUrlEncodedBody}`;
console.log(`Verifying IPN: ${verificationBody}`);
let options = {
method: "POST",
uri: getPaypalURI(),
body: verificationBody,
};
// POST verification IPN data to paypal to validate.
request(options, function callback(error, response, body)
{
if(!error && response.statusCode === 200)
{
if(body === "VERIFIED")
{
console.log(`Verified IPN: IPN message for Transaction ID: ${ipnTransactionMessage.txn_id} is verified.`);
SendPIN(ipnTransactionMessage.payer_email, pin);
}
else if(body === "INVALID")
console.error(`Invalid IPN: IPN message for Transaction ID: ${ipnTransactionMessage.txn_id} is invalid.`);
else
console.error("Unexpected reponse body.");
}
else
{
console.error(error);
console.log(body);
}
});
});

Indeed it is a problem of Promises chaining and also a problem due to the request library: request supports callback interfaces natively but does not return a promise, which is what you must do within a Cloud Function.
I would suggest that you watch these official Firebase videos from Doug : https://www.youtube.com/watch?v=7IkUgCLr5oA&t=28s and https://www.youtube.com/watch?v=652XeeKNHSk which explain this key concept.
You can use request-promise (https://github.com/request/request-promise) and the rp() method which "returns a regular Promises/A+ compliant promise".
It is not clear what SendPIN() is doing. Let's make the assumption it returns a Promise. If this is true, you could adapt your code along the following lines:
//....
const rp = require('request-promise');
//....
exports.IPN = functions.region('europe-west1').https.onRequest((req, res) => {
console.log('IPN Notification Event Received');
let pin = RandomPIN(
10,
'0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
);
var userRef = db.collection('Users').doc(pin);
if (req.method !== 'POST') {
console.error('Request method not allowed.');
res.status(405).send('Method Not Allowed');
} else {
let ipnTransactionMessage;
userRef
.set({ Activated: false }, { merge: true })
.then(() => {
console.log('PIN: ' + pin);
ipnTransactionMessage = req.body;
// Convert JSON ipn data to a query string since Google Cloud Function does not expose raw request data.
let formUrlEncodedBody = querystring.stringify(ipnTransactionMessage);
// Build the body of the verification post message by prefixing 'cmd=_notify-validate'.
let verificationBody = `cmd=_notify-validate&${formUrlEncodedBody}`;
console.log(`Verifying IPN: ${verificationBody}`);
let options = {
method: 'POST',
uri: getPaypalURI(),
body: verificationBody
};
// POST verification IPN data to paypal to validate.
return rp(options);
})
.then(response => {
//Not sure what you will get within the response object...
console.log(
`Verified IPN: IPN message for Transaction ID: ${
ipnTransactionMessage.txn_id
} is verified.`
);
return SendPIN(ipnTransactionMessage.payer_email, pin); //It is not clear what SendPIN is doing, let's make the assumption it returns a Promise...
})
.then(() => {
res.send('Success');
return null;
})
.catch(err => {
console.error(
`Invalid IPN: IPN message for Transaction ID: ${
ipnTransactionMessage.txn_id
} is invalid.`
);
res
.status(500)
.send(
'Error: ' +
err +
` - Invalid IPN: IPN message for Transaction ID: ${
ipnTransactionMessage.txn_id
} is invalid.`
);
return null;
});
}
});

Related

How do delegated routing nodes in IPFS save http responses to a remote requesting node's local directory?

I've been analyzing the js ipfs source codes to understand how exactly delegated routing works. It turns out that js ipfs uses delegated routing by default, which means whenever I call IPFS.cat(), I send an http request to a delegated routing node that will perform DHT querying and propagation on my behalf and return the contents back to me so I can display them. What confuses the heck out of me for a few weeks is how exactly those delegated routing nodes alter my node's local directory upon returning a response. I am positive that the cat method creates a new local directory with the returned contents, since while analyzing the source code for ipfs.cat(), I realized that it calls repo.blocks.get(CID, options), and during that process, fs-datastore.get(cid) is called, which uses the OS module to append my node's local directory to the CID of the file to be fetched. So it only seems logical that either the node-fetch module on my local node saves the file on my local directory so it can be accessed by fs-datastore.get() or that the delegated routing node somehow remotely saves the files on my local directory after figuring out its path.
function fetch(url, opts) {
//native-fetch method
//This method should fetch an ipfs file from other peers and then
//save it to the local directory
// allow custom promise
if (!fetch.Promise) {
throw new Error('native promise missing, set fetch.Promise to your favorite alternative');
}
Body.Promise = fetch.Promise;
// wrap http.request into fetch
return new fetch.Promise(function (resolve, reject) {
// build request object
const request = new Request(url, opts);
const options = getNodeRequestOptions(request);
const send = (options.protocol === 'https:' ? https : http).request;
const signal = request.signal;
let response = null;
const abort = function abort() {
let error = new AbortError('The user aborted a request.');
reject(error);
if (request.body && request.body instanceof Stream.Readable) {
destroyStream(request.body, error);
}
if (!response || !response.body) return;
response.body.emit('error', error);
};
if (signal && signal.aborted) {
abort();
return;
}
const abortAndFinalize = function abortAndFinalize() {
abort();
finalize();
};
// send request
const req = send(options);
console.log("node-fetch req(http.request() return value, send() return value): ", req)
let reqTimeout;
if (signal) {
signal.addEventListener('abort', abortAndFinalize);
}
function finalize() {
req.abort();
if (signal) signal.removeEventListener('abort', abortAndFinalize);
clearTimeout(reqTimeout);
}
if (request.timeout) {
req.once('socket', function (socket) {
reqTimeout = setTimeout(function () {
reject(new FetchError(`network timeout at: ${request.url}`, 'request-timeout'));
finalize();
}, request.timeout);
});
}
req.on('error', function (err) {
reject(new FetchError(`request to ${request.url} failed, reason: ${err.message}`, 'system', err));
if (response && response.body) {
destroyStream(response.body, err);
}
finalize();
});
fixResponseChunkedTransferBadEnding(req, function (err) {
if (signal && signal.aborted) {
return;
}
destroyStream(response.body, err);
});
/* c8 ignore next 18 */
if (parseInt(process.version.substring(1)) < 14) {
// Before Node.js 14, pipeline() does not fully support async iterators and does not always
// properly handle when the socket close/end events are out of order.
req.on('socket', function (s) {
s.addListener('close', function (hadError) {
// if a data listener is still present we didn't end cleanly
const hasDataListener = s.listenerCount('data') > 0;
// if end happened before close but the socket didn't emit an error, do it now
if (response && hasDataListener && !hadError && !(signal && signal.aborted)) {
const err = new Error('Premature close');
err.code = 'ERR_STREAM_PREMATURE_CLOSE';
response.body.emit('error', err);
}
});
});
}
req.on('response', function (res) {
clearTimeout(reqTimeout);
const headers = createHeadersLenient(res.headers);
// HTTP fetch step 5
if (fetch.isRedirect(res.statusCode)) {
// HTTP fetch step 5.2
const location = headers.get('Location');
// HTTP fetch step 5.3
const locationURL = location === null ? null : resolve_url(request.url, location);
// HTTP fetch step 5.5
switch (request.redirect) {
case 'error':
reject(new FetchError(`uri requested responds with a redirect, redirect mode is set to error: ${request.url}`, 'no-redirect'));
finalize();
return;
case 'manual':
// node-fetch-specific step: make manual redirect a bit easier to use by setting the Location header value to the resolved URL.
if (locationURL !== null) {
// handle corrupted header
try {
headers.set('Location', locationURL);
} catch (err) {
// istanbul ignore next: nodejs server prevent invalid response headers, we can't test this through normal request
reject(err);
}
}
break;
case 'follow':
// HTTP-redirect fetch step 2
if (locationURL === null) {
break;
}
// HTTP-redirect fetch step 5
if (request.counter >= request.follow) {
reject(new FetchError(`maximum redirect reached at: ${request.url}`, 'max-redirect'));
finalize();
return;
}
// HTTP-redirect fetch step 6 (counter increment)
// Create a new Request object.
const requestOpts = {
headers: new Headers(request.headers),
follow: request.follow,
counter: request.counter + 1,
agent: request.agent,
compress: request.compress,
method: request.method,
body: request.body,
signal: request.signal,
timeout: request.timeout,
size: request.size
};
// HTTP-redirect fetch step 9
if (res.statusCode !== 303 && request.body && getTotalBytes(request) === null) {
reject(new FetchError('Cannot follow redirect with body being a readable stream', 'unsupported-redirect'));
finalize();
return;
}
// HTTP-redirect fetch step 11
if (res.statusCode === 303 || (res.statusCode === 301 || res.statusCode === 302) && request.method === 'POST') {
requestOpts.method = 'GET';
requestOpts.body = undefined;
requestOpts.headers.delete('content-length');
}
// HTTP-redirect fetch step 15
resolve(fetch(new Request(locationURL, requestOpts)));
finalize();
return;
}
}
// prepare response
res.once('end', function () {
if (signal) signal.removeEventListener('abort', abortAndFinalize);
});
let body = res.pipe(new PassThrough$1());
const response_options = {
url: request.url,
status: res.statusCode,
statusText: res.statusMessage,
headers: headers,
size: request.size,
timeout: request.timeout,
counter: request.counter
};
// HTTP-network fetch step 12.1.1.3
const codings = headers.get('Content-Encoding');
// HTTP-network fetch step 12.1.1.4: handle content codings
// in following scenarios we ignore compression support
// 1. compression support is disabled
// 2. HEAD request
// 3. no Content-Encoding header
// 4. no content response (204)
// 5. content not modified response (304)
if (!request.compress || request.method === 'HEAD' || codings === null || res.statusCode === 204 || res.statusCode === 304) {
response = new Response(body, response_options);
resolve(response);
return;
}
// For Node v6+
// Be less strict when decoding compressed responses, since sometimes
// servers send slightly invalid responses that are still accepted
// by common browsers.
// Always using Z_SYNC_FLUSH is what cURL does.
const zlibOptions = {
flush: zlib.Z_SYNC_FLUSH,
finishFlush: zlib.Z_SYNC_FLUSH
};
// for gzip
if (codings == 'gzip' || codings == 'x-gzip') {
body = body.pipe(zlib.createGunzip(zlibOptions));
response = new Response(body, response_options);
resolve(response);
return;
}
// for deflate
if (codings == 'deflate' || codings == 'x-deflate') {
// handle the infamous raw deflate response from old servers
// a hack for old IIS and Apache servers
const raw = res.pipe(new PassThrough$1());
raw.once('data', function (chunk) {
// see http://stackoverflow.com/questions/37519828
if ((chunk[0] & 0x0F) === 0x08) {
body = body.pipe(zlib.createInflate());
} else {
body = body.pipe(zlib.createInflateRaw());
}
response = new Response(body, response_options);
resolve(response);
});
return;
}
// for br
if (codings == 'br' && typeof zlib.createBrotliDecompress === 'function') {
body = body.pipe(zlib.createBrotliDecompress());
response = new Response(body, response_options);
resolve(response);
return;
}
// otherwise, use response as-is
response = new Response(body, response_options);
resolve(response);
});
writeToStream(req, request);
});
}
In the node-fetch module's fetch source code, there doesn't seem to be anywhere that saves the response to my local directory, let alone figure out the local directory with the OS module. While it seems unlikely that the remote delegated routing node access my local directory and saves the file remotely, if it's possible method, I'd like to know how exactly this whole file saving process works that I've been struggling to understand for weeks.

Is my Paypal checkout flow Angular-Node secure?

I'm developing a paypal checkout using the 'basic Smart Payment Buttons integration' and integrating it with server Node installing the 'checkout-server-sdk'.
I followed the documentations:
https://developer.paypal.com/docs/checkout/reference/server-integration/set-up-transaction/
https://github.com/paypal/Checkout-NodeJS-SDK
where they suggest to:
'createOrder' starting from the client and calling the server
generating on the server an orderID and return it to the client
'onApprove' send to the server the orderID and approve it on the server
return back to the client the response
I don't think it is a good flow.
Someone could:
start the payment
so the app create the order on the server taking the shoppingcart from db and elaborate a totalPrice of 100euros.
generete the orderID and send it back to the client
instead of approve this order, a 'bad user' could, in some way, send to the server another orderID that could correspond to a lower price (2euros)
so he could approve the payment of 2 euros
So I don't understand why we need to make the checkout jumping more times from client to server.
Or maybe am i doing something wrong on my checkoutflow ?
unfortunately I feel the Paypal documentation so unclear.
checkout.component.html
<!-- * here there is a form where i get shipment info, invoice info and so on ->
<!-- * PAYPAL SMART BUTTONS -->
<div>
<div #paypal></div>
</div>
checkout.component.ts
onFormSubmit() {
this.isFormSubmitted = true;
// set paypal settings and show the paypal buttons
this.paypalSetting(this.shippmentInfo, this.invoiceRequired, this.invoice, this.addressInvoice);
}
async paypalSetting(shipment, invoiceRequired, invoice, addressInvoice) {
await paypal
.Buttons({
style: {
size: 'responsive',
label: 'pay',
},
experience: {
input_fields: {
no_shipping: 1,
},
},
createOrder: async (data, actions) => {
console.log('CREATE ORDER -->');
var paypalOrderId;
//generate new order
await this.apiService.newOrder().toPromise().then(
(res) => {
console.log('ON CREATE: SUCCESSFULLY CREATED')
paypalOrderId = res.order.paypalOrderId;
// ????? someone here could change 'paypalOrderId' with another value !!!!
//I also would like to return the 'paypalOrderId' only here !!
},
(err) => {
console.log('ON CREATE: ERROR: ' + err);
// how should i manage this error ? i should skip the flow to onError but HOW ?
}
);
return paypalOrderId;
},
onApprove: async (data, actions) => {
console.log('APPROVE ORDER -->');
var paypalOrderId = data.orderID;
console.log('ON APPROVE: save the order on server/DB')
await this.apiService.saveOrder(shipment, invoiceRequired, invoice, addressInvoice, paypalOrderId).toPromise().then(
(res) => {
console.log('ON APPROVE: ORDER APPROVED')
this.isPaid = true;
//if isPaid i can show a 'success page'
},
(err) => {
console.log('ON APPROVE: ERROR: ' + err);
this.isPaid = false;
}
);
},
onError: (err) => {
console.log('ON ERROR: ' + err);
},
})
.render(this.paypalElement.nativeElement);
}
Node api.js
//* paypal
const paypal = require('#paypal/checkout-server-sdk');
const payPalClient = require('../paypalManager');
router.post('/newOrder', tokenManager.verifyAccessToken, async function (req, res, next) {
const idUser = req.userId;
// I get the shoppingcart of the user 'idUser'
// i calculate the total price
var totalPrice;
//* Call PayPal to set up a transaction
let order;
const request = new paypal.orders.OrdersCreateRequest();
request.prefer("return=representation");
request.requestBody({
intent: 'CAPTURE',
purchase_units: [{
description: 'payment ecc..', /
amount: {
currency_code: 'EUR',
value: totalPrice
}
}],
application_context: {
brand_name: "brand",
shipping_preference: 'NO_SHIPPING',
},
});
let response = await payPalClient.client().execute(request);
order = response;
const paypalOrderId = order.result.id;
// return a successful response to the client with the order ID
return res.json({
status: 200,
order: {
paypalOrderId: paypalOrderId,
},
message: "Paypal order sucessfully created",
});
});
router.post('/saveOrder', tokenManager.verifyAccessToken, async function (req, res, next) {
const idUser = req.userId;
var paypalOrderId = req.body.paypalOrderId;
try {
connection.beginTransaction(async () => {
try {
// here i insert all the checkout infos in DB
// confirm the queries executions
connection.commit(async function (err) {
if (err) {
//return connection.rollback(function () {
connection.rollback(function () {
return next(createError.Unauthorized("Sql query error: " + err)); //! or error.message
});
}
//* here i send the Emails to confirm the checkout
//* capture/approve the order
console.log('CAPTURING THE ORDER')
var request = new paypal.orders.OrdersCaptureRequest(paypalOrderId);
request.requestBody({});
// Call API with your client and get a response for your call
let response = await payPalClient.client().execute(request);
//*response
return res.json({
status: 200,
message: "Paypal sucessfully approved",
});
});// end commit
} catch (error) {
connection.rollback(function () {
return next(createError.Unauthorized("Sql query error " + error)); //! or error.message
});
}
});// end transaction
} catch (error) {
return next(error);
}
});
Node paypalManager.js
'use strict';
/**
* PayPal Node JS SDK dependency
*/
const checkoutNodeJssdk = require('#paypal/checkout-server-sdk');
/**
* Returns PayPal HTTP client instance with environment that has access
* credentials context. Use this instance to invoke PayPal APIs, provided the
* credentials have access.
*/
function client() {
return new checkoutNodeJssdk.core.PayPalHttpClient(environment());
}
/**
* Set up and return PayPal JavaScript SDK environment with PayPal access credentials.
* This sample uses SandboxEnvironment. In production, use LiveEnvironment.
*/
function environment() {
let clientId = process.env.PAYPAL_CLIENT_ID;
let clientSecret = process.env.PAYPAL_CLIENT_SECRET;
return new checkoutNodeJssdk.core.SandboxEnvironment(
clientId, clientSecret
);
}
module.exports = {
client: client,
prettyPrint: prettyPrint
};
The reason you are "jumping" between the client and the server, is the approval by the payer has to happen on the client. The payer cannot give their approval on your server, they are not sitting on your server. They are using a client browser.
Regarding:
a 'bad user' could, in some way, send to the server another orderID that could correspond to a lower price (2euros)
If this happens, your server should reject the undesired transaction, and not proceed with it. That's the point of having a server. Nothing happens unless your server OKs it.

How to send a NODE.JS post request from an Angular Project?

I have a NODE.JS api using expressjs that connects to an SQL Server, and I want to use it in an angular project. I make use two files, a route file and a controllers file. My route file is as follows:
module.exports = (app) => {
const UsrContrllr = require('../Controllers/users.controllers');
//1. GET ALL USERS
app.get('/api/users', UsrContrllr.func1);
//2. POST NEW USER
app.post('/api/user/new', UsrContrllr.func2);
};
And my controllers file is given below:
const mssql = require('mssql');
exports.func1 = (req, res) =>
{
// Validate request
console.log(`Fetching RESPONSE`);
// create Request object
var request = new mssql.Request();
// query to the database and get the records
const queryStr = `SELECT * FROM USERS`;
request.query(queryStr, function (err, recordset) {
if (err) console.log(err)
else {
if (recordset.recordset.toString() === '') {
res.send('Oops!!! Required data not found...');
}
else {
// send records as a response
res.send(recordset);
}
};
});
};
exports.func2 = (req, res) =>
{
// Validate request
console.log(`INSERTING RECORD ${req}`);
// create Request object
var request = new mssql.Request();
// query to the database and get the records
const queryStr = `INSERT INTO GDUSERS (USERCODE, PASSWORD, LANGUAGE, USERCLASS, FIRSTNAME, LASTNAME, CONTACTNO) VALUES ('${req.body.usercode}', '${req.body.password}', 'EN', '0', '${req.body.firstname}', '${req.body.lastname}', '${req.body.contactno}');`;
request.query(queryStr, function (err, recordset) {
if (err) console.log(err)
else {
if (recordset.recordset.toString() == '') {
res.send('Oops!!! Required data not found...');
}
else {
// Send records as response
res.send(recordset);
}
};
});
};
The GET request works well, but when I try to run the POST request directly from the angular application, I get an error stating
Cannot GET URL/api/user/new
The angular code in my angular project is:
signup() {
let headers = new Headers({ 'Content-Type': 'application/json' });
let options = new RequestOptions({ headers: headers });
console.log(this.user); //User details come from a form
this.http.post(“URL", this.user, options)
.subscribe(
(err) => {
if(err) console.log(err);
console.log("Success");
});
}
I’m not sure whether the angular code I’m using, is right or not, and I don’t know where I’m going wrong. How does one exactly send a http POST request from an Angular project?
this i the way i handled my user signup with http.post calls. my approach is slightly different when signing up user because i am using a promise instead of observable (which i normally use for my servicecalls). but i will show you both ways.
createUser(user: User): Promise < string > {
const promise = new Promise < string > ((resolve, reject) => {
const userForPost = this.createUserForPost(user);
this.http.post(environment.backendUrl + '/api/user/signup', userForPost, this.config).toPromise < HttpConfig > ()
.then(createdUser => {
}).catch(error => {
console.log(error);
});
});
return promise;
}
here another example with an observable
createForumPost(forumPost: ForumPost) {
this.http.post < { message: string, forumPostId: string } > (environment.backendUrl + '/api/forumPosts', forumPost).subscribe((responseData) => {
const id = responseData.forumPostId;
forumPost.id = id;
});
}
i defined my URL somewhere else and then just use the environment.backedUrl + 'path' to define my path (the same as the path in your backend controller)
this is one of my first answers here on SO. i am sry if it is a bit messy
i hope i was able to help with my examples :)

Accessing correct fulfilment request with intent

I hope someone can help me with this.
I have two separate intents news and weather in my dialog flow agent.
I have different input parameters for both of them.
And I am using two separate API for each of them.
When I use both of them separately it works fine.
but when I try to combine both of them in one agent it does not show relevant output based on question. i.e if I ask question regarding weather it shows me news.
Is there any way I can fix it.right now it goes directly to the second request. Here is my code:
'use strict';
const functions = require('firebase-functions');
const {WebhookClient} = require('dialogflow-fulfillment');
const {dialogflow} = require('actions-on-google');
const {Card, Suggestion} = require('dialogflow-fulfillment');
const http = require('http');
const host = 'api.worldweatheronline.com';
const wwoApiKey = '0cb58ac2d82f484fa75185834191912';
const NewsAPI = require('newsapi');
const newsapi = new NewsAPI('63756dc5caca424fb3d0343406295021');
process.env.DEBUG = 'dialogflow:*';
exports.dialogflowFirebaseFulfillment = functions.https.onRequest((request, response) =>
{
var agent = new WebhookClient({ request, response });
// Get the city and date from the request
let city = request.body.queryResult.parameters['geo-city'];// city is a required param
// Get the date for the weather forecast (if present)
let date = 'date';
if (request.body.queryResult.parameters['date']) {
date = request.body.queryResult.parameters['date'];
console.log('Date: ' + date);
}
//Get the search criteria from the request
const search = request.body.queryResult.parameters['search'];
console.log(search);
//Map the correct intent
let intentMap = new Map();
intentMap.set('misty.weather',getweather );
intentMap.set('misty.news', getnews);
agent.handleRequest(intentMap);
});
//Weather reuest function
function getweather(agent,city,date)
{
callWeatherApi(city, date).then((output) => {
response.json({ 'fulfillmentText': output }); // Return the results of the weather API to Dialogflow
}).catch(() => {
response.json({ 'fulfillmentText': `I don't know the weather but I hope it's good!` });
});
}
// news request function
function getnews(search,agent)
{
callNewsApi(search).then((output) => {
console.log("Indide request");
response.json({ 'fulfillmentText': output }); // Return the results of the news API to Dialogflow
}).catch((error) => {
console.log(error);
response.json({ 'fulfillmentText': `I don't know the news but I hope it's good!` });
});
}
// API call for weather
function callWeatherApi (city, date) {
return new Promise((resolve, reject) => {
// Create the path for the HTTP request to get the weather
let path = '/premium/v1/weather.ashx?format=json&num_of_days=1' +
'&q=' + encodeURIComponent(city) + '&key=' + wwoApiKey + '&date=' + date;
console.log('API Request: ' + host + path);
// Make the HTTP request to get the weather
http.get({host: host, path: path}, (response) => {
let body = ''; // var to store the response chunks
response.on('data', (d) => { body += d; }); // store each response chunk
response.on('end', () => {
// After all the data has been received parse the JSON for desired data
let response = JSON.parse(body);
let forecast = response['data']['weather'][0];
let location = response['data']['request'][0];
let conditions = response['data']['current_condition'][0];
let currentConditions = conditions['weatherDesc'][0]['value'];
// Create response
let output = `Current conditions in the ${location['type']}
${location['query']} are ${currentConditions} with a projected high of
${forecast['maxtempC']}°C or ${forecast['maxtempF']}°F and a low of
${forecast['mintempC']}°C or ${forecast['mintempF']}°F on
${forecast['date']}.`;
// Resolve the promise with the output text
console.log(output);
resolve(output);
});
response.on('error', (error) => {
console.log(`Error calling the weather API: ${error}`);
reject();
});
});
});
}
//API call for news
function callNewsApi(search)
{
console.log("Inside api call");
console.log(search);
return newsapi.v2.topHeadlines
(
{
source:'CBC News',
q:search,
langauge: 'en',
country: 'ca',
}
).then (response => {
// var to store the response chunks
// store each response chunk
console.log(response);
var articles = response['articles'][0];
console.log(articles);
console.log("Inside responce call");
// Create response
var output = `Current news in the '${search}' with following title is ${articles['titile']} which says that ${articles['description']}`;
console.log(output);
return output;
});
}
It is giving me a reference error at line 51 and 42. I suspect I am missing a perameter to display the output.
function getnews(search,agent)
{
callNewsApi(search).then((output) => {
console.log("Indide request");
response.json({ 'fulfillmentText': output }); // Return the results of the news API to Dialogflow
}).catch((error) => {
console.log(error);
response.json({ 'fulfillmentText': `I don't know the news but I hope it's good!` });
});
}
it shows highlights near response.json and says that response not defined.
When I am reading your code, it looks like you're posting 2 Cloud Functions, while via the Dialogflow console, you can only set one webhook URL or Cloud Function. Therefore your approach should be that your webhook URL (pointing to a VM, Container etc..) or Cloud Function; contains code that behaves like a 'router'.
You will create the WebhookClient just once.
All parameters can be retrieved out of the queryResult object.
The intentMap could look something like this:
let intentMap = new Map();
intentMap.set('Default Welcome Intent', welcome);
intentMap.set('misty.weather', getWeather);
intentMap.set('misty.news', getNews);
function getNews() { .. }
function getWeather() {..}
I've an example Dialogflow / AOG project with a (Google Cloud, Cloud Function), you can have a look at:
https://github.com/savelee/dialogflow-aog-tvguide/
With this cloud function:
https://github.com/savelee/dialogflow-aog-tvguide/blob/master/cloudfunction/tvguide/index-old.js

Can't assign request.payload values in query

I'm developing a simple app with Node/Hapi/Mongodb, but running into a strange issue. Below is the route that handles adding/updating scores; when I send some data to this endpoint through Insomnia/Postman it works as expected. However, when this POST is coming from a different app I'm getting strange results; the value is always null for every field (again this only happens when the POST is coming from another site, but I've logged out the request payload and can see that the data is correct, just gets set to null when assigning to an object, or trying to use it a query)
server.route({
method: 'POST',
path: '/update-score',
handler: (request, h) => {
var scores = db.collection('scores');
var updateScore = new Promise((resp, rej) => {
console.log('payload ', request.payload);
scores.findOneAndUpdate({customerID: request.payload.customerID}, {$set: {customerID: request.payload.customerID, customerName: request.payload.customerName, highScore: request.payload.highScore}}, {upsert: true}, (err, res) => {
if (err) {
return rej(err);
}
else {
return resp(res);
}
})
});
return updateScore;
}
});
The console logs out the request payload correctly, but its null/undefined when the query tries to use it. I have also tried creating two objects, outside the mongo method call (like below), and after console logging these pre-defined objects out the value was null there as well; even though I can console.log the request.payload after defining these objects and the data is good.
server.route({
method: 'POST',
path: '/update-score',
handler: (request, h) => {
var scores = db.collection('scores');
var queryObj = {
customerID: request.payload.customerID
};
var updateObj = {
$set: {
customerName: request.payload.customerName,
highScore: request.payload.highScore
}
}
var updateScore = new Promise((resp, rej) => {
console.log('again ', request.payload);
scores.findOneAndUpdate(queryObj, updateObj, {upsert: true}, (err, res) => {
if (err) {
return rej(err);
}
else {
return resp(res);
}
})
});
return updateScore;
}
});
Logging the queryObj and valueObj would show the values are all null, even though I can log the request.payload and see the data correctly. Why can't I use the request.payload values anywhere?
Long story short, Insomnia/Postman sends an object as the POST body, but I was JSON encoding the POST from the app; just needed to parse that on the server!

Resources