Hi all I'm new to web development so I'm developing a simple online recipe book to allow me to also learn node and angular in the process.
How ever I'm struggling with what type of responses I should send back to the client for success and fails, in the case of post requests, or delete requests.
Here's an example of an api POST access point:
app.post('/recipe/',(req,res)=>{
res.set({
'Access-Control-Allow-Origin': 'http://localhost:4201'
})
console.log("Recieved req at /recipe/")
if(req.body.recipe === undefined){
res.send("Request not filled. No recipe found");
}
if(req.body.ingredients === undefined){
res.send("Request not filled. No ingredients found");
}
//var r = new Recipe(req.body.recipe,req.body.ingredients);
mongo.addRecipe(req.body.recipe,req.body.ingredients).then((promise)=>{
mongo.getAllRecipeByName().then(promise=>{
console.log(promise);
});
res.send(promise);
})
})
Here's the code for mongo.addRecipe() so you know what's being returned in that promise:
async addRecipe(recipeName,ingredients){
if(ingredients == null){
return null;
}
if(recipeName == null){
return null;
}
var recipe = new Recipe(recipeName,ingredients);
if(recipe.getIngredients().length <= 0){
return null;
}
try{
const client = new MongoClient(this.uriString);
await client.connect();
const db = client.db(this.databaseString);
const recipeCollection = db.collection(this.collectionString);
var status = await recipeCollection.insertOne(recipe);
client.close();
if(status.acknowledge == true){
return status
}
else{
return status
}
//console.log(await recipeCollection.find({recipeName:recipeName}).toArray());
}
catch(e){
console.error("Failed addRecipe() params: "+recipeName+" "+ingredients+e);
return null;
}
//String recipeName List<String> ingredients
//creates connection with mongo database.
//add {name:'recipeName',ingredients:'ingredients'} to mongoDB
}
So as you can see some of my verification statements return null and then I'm sending null back to the client. Is this a mistake? In the case of success I am returning the massive object that's returned from collection.insertOne();
What should I be sending to the client in the case of success and failure that fits with the industry standards?
Related
I have a NodeJS API running which uses Node Schedule to call a function every month.
This function gets a list of clients from my MYSQL db.
The clients are looped through and then each is charged for SMS's they have sent this month.
My issue is that although it is running, most clients were charged between 2 and 5 times (none only once).
This leads me to think I have issues with my stripe call or perhaps the NodeJS loop... or the Async/Await properties.
App.js:
schedule.scheduleJob("0 0 1 * *",async () => {
console.log("SMS Charging Schedule Running");
await stripePayments.chargeCustomers();
})
stripePayments.js:
module.exports = {
chargeCustomers : async function chargeCustomers() {
{
var stripeCusID;
var success = false;
try{
//I get the list of clients here. I have checked this query and it is 100% correct, with only one record per client.
var sqlString = "CALL chemPayments();";
var countCharged = 0;
authConnection.query(sqlString,async(err,rows,fields)=>{
let chemRows = rows[0];
if(!err){
success = true;
for (let i=0; i < chemRows.length; i++){
stripeCusID = chemRows[i].stripeCusID;
chargeValue = chemRows[i].chargeValue;
chemistName = chemRows[i].chemistName;
chemistID = chemRows[i].chemistID;
countSMS = chemRows[i].countSMS;
//console.log(stripeCusID);
try{
await chargeSMS(stripeCusID,chargeValue,chemistName,chemistID,countSMS);
countCharged++;
}catch (e) {
// this catches any exeption in this scope or await rejection
console.log(e);
}
}
if (countCharged == 0){
console.log("No SMS Charges to perform - " + Date(Date.now()).toString());
}
}
else
{
console.log("Error calling DB");
}
})
} catch (e) {
// this catches any exeption in this scope or await rejection
console.log(e);
//return res.status(500).json({ Result: e });
}
}
}
}
I then charge each client.
async function chargeSMS(stripeCusID,chargeValue,chemistName,chemistID,countSMS){
let customerObject;
let payMethodID;
let secondaryPayMethodID;
let payMethodToUse;
customerObject = await stripe.customers.retrieve(
stripeCusID
);
payMethodID = customerObject.invoice_settings.default_payment_method;
secondaryPayMethodID = customerObject.default_source;
if(payMethodID != null){
payMethodToUse = payMethodID;
}else{
payMethodToUse = secondaryPayMethodID;
}
await stripe.paymentIntents.create({
amount: chargeValue,
currency: 'aud',
customer: stripeCusID,
description: `SMS Charges: ${countSMS} sent - ${chemistName}`,
payment_method: payMethodToUse,
confirm: true
},
function(err, response) {
if (err) {
console.log(chemistName + ": " + err.message + " " + stripeCusID + " " + chargeValue );
return;
}else{
console.log(`Successful SMS Charge: ${response.id} ${chemistName}`);
chemCharged(chemistID);
}
})
}
This final step then updates teh database and tags each sms as "charged" = true, thus they are no longer on the initial select query.
async function chemCharged(chemistID){
try{
var sqlString = "SET #chemistID = ?;CALL chemCharged(#chemistID);";
authConnection.query(sqlString,chemistID,async(err,rows,fields)=>{
//console.log(rows);
if(err){
console.error("Error marking chemist as charged");
}else{
console.log(chemistID + "updated on SMS DB");
}
})
} catch (e) {
// this catches any exeption in this scope or await rejection
console.log(e);
//return res.status(500).json({ Result: e });
}
}
My largest issue is that when I copy this code and run it with the stripe TEST key... I can't replicate the problem!! The code runs fine and each client is only charged once, but when I leave my code for the cron to run at the start of each month.. I get heaps of charges per client. Sometimes 1 or 2, but up to 5 of the same charge goes through!
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.
I have a function that should update a token, based on an user's email. The thing is, the following code returns a token even if there isn't any document with the specified email in the mongoDB database and the function return the response code 200 to my server function. I'd like to prevent the updating of the document (and any further actions) when the specified e-mail isn't in the database or i'd like to return some information (regardless of the response code) to prevent further code from executing.
const vnosZetona = (req,res) =>{
if(!req.body.ePosta ){
return res.status(400).json({
"sporočilo": "Epošta uporabnika manjka! Parameter je obvezen"
});
}
if(!(new RegExp("[a-z]{2}[0-9]{4}#student.uni-lj.si").test(req.body.ePosta))){
return res.status(400).json({
"sporočilo": "Izgleda da nisi študent UL! Hm, "
});
}
var generiranZeton = generirajObnovitveniZeton();
User
.updateOne( {email: req.body.ePosta},
{ $set: {zetonZaObnavljanjeGesla:generiranZeton}},
(napaka) => {
if(napaka){
return res.status(400).json(napaka);
}else{
return res.status(200).json({
zeton : generiranZeton,
"sporočilo" : "Žeton uspešno dodan."
});
}
}
)
};
So after a series of trials & errors I finally figured out what's really wrong. When I issued the query (whith an email which was not in any document) direcly into the mongoDB shell I got the following response: { "acknowledged" : true, "matchedCount" : 0, "modifiedCount" : 0 }
The result clearly says that the wasn't any update ( modifiedCount is 0) but the query was still executing without any errors, so I had to "collect" that text and then continue the execution based on the "modifiedCount" value.
const vnosZetona = (req,res) =>{
//check for errors and other stuff
var generiranZeton = generirajObnovitveniZeton(); //generate random token
User
.updateOne( {email: req.body.ePosta},
{ $set: {zetonZaObnavljanjeGesla:generiranZeton}},
(napaka, sporociloQueryja) => {
if(napaka){
return res.status(400).json(napaka);
}else{
return res.status(200).json({
zeton : generiranZeton,
status: JSON.stringify(sporociloQueryja),
//the field "status" returns the result mentioned above
//although slightly different than in the mongoDB shell:
//{"n":0,"nModified":0,"ok":1}
"sporočilo" : "Žeton uspešno dodan."
});
}
}
);
};
//The following code calls the API above when we complete the form on the page and hit submit
const posljiZahtevoZaObnovoGesla = async (req,res,next) =>{
//check for a valid email address
try{
let odgovor = await axios.put(apiParametri.streznik + "/api/uporabniki/vnosZetona",{
ePosta : req.body.ePosta
});
if(odgovor.status == 200){
//If the query had no errors,regardless if anything was updated
// we read the data that was returned from the API ->
// with a nonexistent mail, the "odgovor.data.status" equals "{"n":0,"nModified":0,"ok":1}"
var o = JSON.parse(odgovor.data.status);
if(o.nModified == 0){
//nothing was modified, the no document with the specified email exists
// the user gets redirected to registration form
return res.redirect("/registracija");
}
//the code sends the instructions to the specified mail that exists in database
//using nodemailer and redirects to user sign in
res.redirect("/prijava");
}
else if (odgovor.status >= 400){
res.status(odgovor.status).json(
{"sporocilo": "Nekaj si zafrknil! Rollbackaj na začetno stanje!"}
);
}
}catch(napaka){
next(napaka);
}
};
Hi I am trying trying to get data from my nodejs backend to angular. And i get the below error message in nodejs
Cannot read property 'friends' of null
at UserModel.findOne.select.lean.exec
The Api Works fine, if i try to use in postman. But when used in angular i get the error message in nodejs
The server side code is:
let findFriends = ()=>{
return new Promise((resolve, reject)=>{
UserModel.findOne({userId: req.body.userId || req.query.userId})
.select('-__v -_id -password')
.lean()
.exec((err, userFriends)=>{
console.log(userFriends.friends.length)
if(err){
logger.error(err.message, ' friendsController: getAllNonFriends, findFriends', 5)
let apiResponse = response.generate(true, `Failed to Find Friends`, 500, null)
reject(apiResponse)
}else {
if(userFriends.friends.length !== 0){
resolve(userFriends.friends)
}else {
resolve(userFriends)
}
}
})
})
} // end find requests
the code in angular:
public allNonFriends: any = ()=>{
this.http.getNonFriendsList(this.currentUser).subscribe(
(apiResponse)=>{
console.log(apiResponse)
if(apiResponse.status === 200){
console.log(apiResponse.data)
} else {
console.log(apiResponse.message)
}
},
(err)=>{
console.log(err)
}
)
} // end all non friends
I found the solution. I change the parameter from body to query and it worked
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;
});
}
});