Sails avoid action exit on exception in loop - javascript

An Action is calling the helper function inside the loop. If the helper function raise some error then it exits with a specific code queryFailed like follows:
helpers/a/execute.js
module.exports = {
friendlyName: '',
description: '',
inputs: {},
exits: {
queryError: {
description: 'Query error'
},
success: {
description: 'yayyy!! success!'
}
},
fn: async function ({ conditions }, exits) {
let records = [],
MYSQL_QUERY = `SELECT * FROM model WHERE COLUMN = $1`;
try {
records = await Model.getDatastore().sendNativeQuery(MYSQL_QUERY, [['true']]);
}
catch (error) {
return exits.queryFailed(error);
}
return exits.success(records);
}
};
I have an action as follows that calls the above mentioned helper function.
controllers/action.js:
module.exports = {
friendlyName: 'Action',
description: 'Performs some action',
inputs: {
param1: {
description: 'param 1',
type: 'string'
},
param2: {
description: 'param 2',
type: 'ref'
}
},
exits: {
invalid: {
description: 'Invalid request',
responseType: 'invalid',
statusCode: 400
},
unexpected: {
description: 'Unexpected error',
responseType: 'unexpected',
statusCode: 500
},
success: {
description: 'success',
statusCode: 200,
outputType: 'ref'
}
},
fn: async function (inputs, exits) {
// Helper Ids
const arr = ['a', 'b'];
let response = [];
for (const element of arr) {
try {
records = await sails.helpers[element].execute.with({
conditions: conditions
});
}
catch (err) {
if (err.code === 'queryError') {
LOGGER.error('Database Error', err);
return exits.unexpected();
}
return exits.unexpected();
}
response.push(records);
}
return exits.success(response);
}
};
The issue with this is in case of an invalid query the helper function exits with queryError code as follows:
return exits.queryFailed(error);
Assuming helper a is executed successfully, if there is an error in helper b then ideally the action should not exit itself. It should continue executing and show the error in the final response for that block.
Expected Response:
{
"rows": [
{
"value": {
"id": "a",
"data": {},
"meta": {},
}
},
{
"error": {
"name": "serverError",
"statusCode": 500,
"message": "Internal server error.",
"id": 2
}
},
Current Behaviour: It's catching the queryError in the action and doing an exit with the error response:
{
"trace": "",
"error": {
"name": "serverError",
"statusCode": 500,
"message": "Internal server error"
}
}
Thank you in advance!

Related

Mongoose mixed Type object array, can findOneAndUpdate

I have a controller edit card which updates the fields of cardsArray object.
cardsArray is mixed type object as fileds of each card object is different so i am storing mixed.type
Althought pushing new card using addCard controller works perfectly
But when edit card controller is called, it gives type error
When edit Controlller is callled is gives following error:
TypeError: Cannot read properties of null (reading 'cardsArray')
// Schema
const userSchema = new mongoose.Schema(
{
name: {
type: String,
required: true,
},
email: {
type: String,
required: true,
},
password: {
type: String,
required: true,
},
cardsArray: [{ type: mongoose.Schema.Types.Mixed }],
}
);
//__Mongodb Data
{
"_id": {
"$oid": "63b43ab32fc8d3c100cafecc"
},
"name": "usern_name",
"email": "pr****#gmail.com",
"password": "$2b$12$3nwifHakrBu94BwLXAC4Nu16Kw0.xyW8vAIPTMSgY7cYttVklDIZq",
"cardsArray": [
{
"title": "some_title",
"category": "Bank",
"cardHolder": "some_name",
"cardNumber": "54545454",
"expiry": "23/01",
"cvv": "***",
"logoIndex": 72,
"isFavourite": false,
"_id": {
"$oid": "63b83cc77288d277ef359533"
}
}
],
"loginIdsArray": [],
"docsArray": [],
"activitiesArray": [],
"__v": 0
}
// Add Card Controller.js___
addCard: async (req, res) => {
console.log(req.body.data, req.body.user_id)
// console.log('obj_id', newObjectId)
req.body.data._id = newObjectId;
try {
const response = await UserDatabase.findOneAndUpdate(
{ _id: req.body.user_id },
{
$push: {
// cardsArray: req.body.data,
cardsArray: { $each: [req.body.data] },
},
},
{ returnOriginal: false }
);
res.status(200).send(response);
} catch (error) {
console.log(error)
res.status(404).json({ message: error.message });
}
},
// edit card controller.js
editCard: async (req, res) => {
const id = req.params.id;
const { category, title, cardHolder, cardNumber, expiry, cvv, logoIndex, isFavourite } = req.body;
console.log(req.params.id)
try {
const response = await UserDatabase.findOneAndUpdate(
{ _id: "63b43ab32fc8d3c100cafecc", 'cardsArray._id': "63b709fc69a1cfa6fccd645c" },
{
$set: {
"cardsArray.$.title": req.body.title,
"cardsArray.$.category": req.body.category,
"cardsArray.$.cardHolder": req.body.cardHolder,
"cardsArray.$.cardNumber": req.body.cardNumber,
"cardsArray.$.expiry": req.body.expiry,
"cardsArray.$.cvv": req.body.cvv,
"cardsArray.$.logoIndex": req.body.logoIndex,
"cardsArray.$.isFavourite": req.body.isFavourite
}
},
);
console.log(response)
res.status(201).json(response.cardsArray);
} catch (error) {
console.log(error)
res.status(404).json({ message: error.message });
}
}
it means that there is no data matching the following _id and cardsArray._id
i thinks so its fails to find the feild 'cardsArray._id',first try finding the value by just findOne
await UserDatabase.findOneAndUpdate(
{ _id: "63b43ab32fc8d3c100cafecc", 'cardsArray._id': "63b709fc69a1cfa6fccd645c" })
if your find doesn't work try below methord not sure but it may work you have to either find the id and loop through the cardsArray of use $in or $match
await UserDatabase.findOneAndUpdate(
{ _id: "63b43ab32fc8d3c100cafecc", 'cardsArray':{$in:[ {_id:"63b709fc69a1cfa6fccd645c" }]})

Promise pending even after its resolved

I want to get the array of rooms and assign it to each property wrt their property_id, but the value returned is a pending promise. Not sure what's wrong. Although when I log the rooms inside the then it does log the value correctly. The result of console.log(property) is given below.
const vendorProfile = catchAsync(async (req, res, next) => {
await passport.authenticate("vendor-jwt", { session: false }, (err, user, info) => {
if (err) {
res.error = err || info.message;
return next(401);
}
if (!user) {
res.error = info.message;
return next(401);
}
return Promise.resolve(
getVendorProfileInfo(user._id)
.then((result) => {
if (result == "error") {
res.error = "Failed to fetch Vendor Profile";
next(500);
}
return getPropertyByVendorId(result._id).then((prop) => {
for (const property of prop) {
property._doc.property_rooms = getAllRooms(property._id).then((rooms) => rooms);
console.log(property);
}
res.message = "Vendor Profile fetched successfully";
res.data = {
vendor_info: result,
vendor_properties: prop,
};
return next(200);
});
})
.catch((err) => {
Logger.error(err);
res.error = "Failed to get vendor profile";
return next(500);
})
).catch((err) => {
Logger.error(err);
res.error = "Failed to get vendor profile";
return next(500);
});
})(req, res, next);
});
This is the function to get all the rooms for that property_id:
const getAllRooms = (propertyId) => {
return Promise.resolve(Room.find({ property_id: propertyId }).then((result) => result)).catch((err) => {
Logger.error(err);
return "error";
});
};
Here is my console.log(property):
{
property_basic_info: {
property_name: 'Welcome',
property_star_rating: 1,
property_booking_since: 2021,
property_channel_manager: ''
},
property_location: {
property_geo_loc: { coordinates: [Array], type: 'Point' },
property_locality: 'bhandup',
property_address: 'MAHAVIR UNIVERSE',
property_country: 'India',
property_state: 'Maharashtra',
property_city: 'Mumbai',
property_zip_code: '400078'
},
property_contact_details: { phone_no: '7059462868', email: 'roy.srijan#outlook.com' },
property_amenities: {
basic_facilities: [ 'Electricity', 'Air Conditioning', 'Elevator/ Lift', 'Bathroom' ],
general_services: [ 'Food', 'Bellboy service' ],
outdoor_activities_sports: [],
common_area: [],
food_drink: [],
health_wellness: [],
business_center_conference: [],
beauty_spa: [],
security: []
},
property_policies: {
checkin_time: '10:06',
checkout_time: '22:06',
cancellation_policy: 'Free cancellation upto 48 hrs'
},
property_rules: {
id_proof: {
acceptable_identity_proofs: 'Adhaar',
unacceptable_identity_proofs: 'Adhaar',
allow_same_id: true
},
guest_profile: [
[Object], [Object],
[Object], [Object],
[Object], [Object],
[Object]
],
general_safety_hygiene_guidelines: [],
room_safety_hygiene: [],
social_distancing: [],
food_drinks_hygiene: [],
property_restrictions: [],
pet_policy: [],
guest_suitabilty: [],
checkin_checkout_policy: [],
extra_bed_policy: [ [Object] ],
custom_policy: []
},
property_finance_legal: { gst_details: '29AAACR4849R2ZG' },
property_status: 1,
property_photo_id: [],
_id: 61607791b1af193c7b8b9f08,
vendor_id: 61607775b1af193c7b8b9f07,
createdAt: 2021-10-08T16:53:37.734Z,
updatedAt: 2021-10-08T16:53:37.734Z,
__v: 0,
property_rooms: Promise { <pending> }
}
Thanks in advance.
That's because you are logging the promise outside the then method.
The promise is resolved async so outside then it is not resolved yet.
you have to change this line:
property._doc.property_rooms = getAllRooms(property._id).then((rooms) => rooms);
console.log(property);
to
property._doc.property_rooms = getAllRooms(property._id).then((rooms) => console.log(rooms));
or use async/await to work with it like sync values

Given this model, how to delete a nested array item with updateOne (mongoose)?

I am trying to remove an array item with "updateOne" method but my query is not matching the right record in the model structure that I have. Given an email, I would like to find the array item with the provided email and pulls it out, remove it. (There is no array item with the same email)
My model is like so:
var mongoose = require('mongoose');
var teamMemberModelSchema = new mongoose.Schema({
_id: false,
"email": {
"type": String,
"required": true,
"minlenght": 5,
"maxheight": 50
},
"name": {
"type": String,
"required": true,
"minlenght": 5,
"maxheight": 256
},
"role": {
"type": String,
"required": true,
"minlenght": 20,
"maxheight": 256
},
"twitter": {
"type": String,
"required": true,
"minlenght": 1,
"maxheight": 100
},
"facebook": {
"type": String,
"required": true,
"minlenght": 1,
"maxheight": 100
},
"linkedin": {
"type": String,
"required": true,
"minlenght": 1,
"maxheight": 100
},
});
var teamModelSchema = new mongoose.Schema({
"title": {
"type": String,
"required": true,
"minlenght": 5,
"maxheight": 20
},
"headline": {
"type": String,
"required": true,
"minlenght": 5,
"maxheight": 30
},
"description": {
"type": String,
"required": true,
"minlenght": 5,
"maxheight": 80
},
"members": [teamMemberModelSchema]
}, { collection: 'team' });
teamModelSchema.set('collection', 'team');
mongoose.model('team', teamModelSchema)
And the approach that I am trying is the following:
module.exports.removeMember = function (req, res) {
const email = req.params.email;
const query = { "members.email": email };
const pull = { $pull: { "members.$.email": email } };
try {
var message = teamMsg.teamMemberRemoveSuccess;
TeamModel.updateOne(query, pull);
responseUtilities.sendJSON(res, false, { message: message });
} catch (err) {
console.log(err.message);
responseUtilities.sendJSON(res, err, { message: err.message });
}
};
It executes with no errors, but nothing is updated.
I have tried some others alternatives with "FindOneAndUpdate" and "FindOneAndRemove" but, I could not find a solution.
Any ideas?
You can use findOneAndUpdate with $pull operator for this task.
For removing items from an array of documents you can check MongoDb docs
You need to use await or then block to query. I used await, and made the function asynchronous by adding async keyword. We also need empty query object.
I also added the new: true option, to return the updated object to check if the item is deleted.
You need to handle the case where no document matches, I added a TODO for you.
module.exports.removeMember = async function(req, res) {
const email = req.params.email;
const query = {};
const pull = {
$pull: {
members: {
email: email
}
}
};
const options = {
new: true
};
try {
var message = teamMsg.teamMemberRemoveSuccess;
const result = await TeamModel.updateOne(query, pull, options);
console.log(result);
if (!result) {
//TODO: return 400-Bad Request or 404 - Not Found
} else {
responseUtilities.sendJSON(res, false, { message: message });
}
} catch (err) {
console.log(err.message);
responseUtilities.sendJSON(res, err, { message: err.message });
}
};
give this query a try:
db.collection.update(
{ 'members.email': 'email#address' },
{ $pull: { members: { email: 'email#address' } } },
{ multi: true }
)
Try using the update() method and async/await:
module.exports.removeMember = async function (req, res) {
const email = req.params.email;
console.log(`email = ${email}`) // Make sure correct value is coming thru
const query = { "members.email": email };
const pull = { $pull: { "members.$.email": email } };
const options = { multi: true }
try {
var message = teamMsg.teamMemberRemoveSuccess;
await TeamModel.update( query, pull, options );
responseUtilities.sendJSON(res, false, { "message": message });
} catch (err) {
console.log(err.message);
responseUtilities.sendJSON(res, err, { "message": err.message });
}
};

Fastify schema validation isn't working. Do I have something configured the wrong way?

I'm trying to figure out why the schema validation is not working in Fastify. I have the following code:
const postOptions = {
schema: {
body: {
type: 'object',
properties: {
name: { type: 'string' },
parentId: { type: 'number' },
requiredKey: { foo: { type: 'string'} }
}
},
response: {
201: {
type: 'object',
properties: {
id: { type: 'number'},
name: { type: 'string'},
parentId: { type: 'number' }
}
}
}
}
}
fastify.post('/sponsor', postOptions, async (request, reply) => {
console.log(`POST /sponsor called`)
return { id: 2, name: 'Zenotis', parentId: 1 }
})
When I use postman to test it out, I can send any keys and values with the body and it goes through fine. It seems like it's not checking at all. Same thing with response. I'm using Fastify version 2.11.0
Edit: here is the json body I'm sending:
{
"name": "Test",
"parentId": 5555555,
"foo": "bar"
}
Here's what I would expect to fail:
{
"myName": "the field is not name",
"parentID": "The D is capitalized and this is a string",
"bar": "where did this field come from, it's not foo"
}
If I send this body, it goes through fine. How do I configure it to fail in all these cases?
Your schema use has a few fixes to apply:
if you don't set the status code 201, the response schema you set will not work. Use '2xx' or set the right code in the reply object
to remove the field that are not in the schema you need to add additionalProperties
if you don't set the required field in the schema, all the fields are optionals
Here a blocking example:
const fastify = require('fastify')()
const postOptions = {
schema: {
body: {
type: 'object',
additionalProperties: false, // it will remove all the field that is NOT in the JSON schema
required: [
'name',
'parentId',
'requiredKey'
],
properties: {
name: { type: 'string' },
parentId: { type: 'number' },
requiredKey: { foo: { type: 'string' } }
}
},
response: {
201: {
type: 'object',
properties: {
id: { type: 'number' },
name: { type: 'string' },
parentId: { type: 'number' }
}
}
}
}
}
fastify.post('/sponsor', postOptions, async (request, reply) => {
console.log('POST /sponsor called')
reply.code(201) // if you don't set the code 201, the response schema you set will not work
return request.body
})
fastify.inject({
method: 'POST',
url: '/sponsor',
payload: {
name: 'Test',
parentId: 5555555,
foo: 'bar'
}
}, (_, res) => {
console.log(res.json())
/* it will print
{
statusCode: 400,
error: 'Bad Request',
message: "body should have required property 'requiredKey'"
}
*/
})

Alexa Node js Lambda Function is giving response null

I'm returning a response from Nodejs Lambda function and is giving a blank response like this :
{
"version": "1.0",
"sessionAttributes": {
"outputSpeech": {
"type": "PlainText",
"text": "Welcome to Alexa Skill"
},
"shouldEndSession": true
},
"response": {}
}
The session attribute should be blank and response should contain the content of the session attribute but it is happening exactly reverse. Here is the code to generate the response.
context.succeed(
buildResponse(
buildSpeechletResponse("Welcome to Alexa Skill", true),
{}
)
)
and these are the helper functions:
function buildSpeechletResponse(outputText, shouldEndSession) {
return {
outputSpeech: {
type: "PlainText",
text: outputText
},
// card: {
// type: "Simple",
// title: title,
// content: output
// },
// reprompt: {
// outputSpeech: {
// type: "PlainText",
// text: repromptText
// }
// },
shouldEndSession: shouldEndSession
};
}
function buildResponse(sessionAttributes, speechletResponse) {
return {
version: "1.0",
sessionAttributes: sessionAttributes,
response: speechletResponse
};
Just watch the argument order in buildResponse helper method. You are passing it in reverse. Just change as follows.
context.succeed(
buildResponse({},
buildSpeechletResponse("Welcome to Alexa Skill", true)
)
)

Categories