Koa/Node.js - Use multiple models within one controller function - javascript

I am attempting to create a new 'Vault' from a Post Request from my frontend (built in React) using Mongoose Schema.
When I hit the create button in my app, the Post Request initiates, but returns:
POST http://localhost:3000/vaults/create 500 (Internal Server Error)
When my controller function createVault() is initiated, it will successfully create a 'Vault' from the model (see below):
//Create a vault
module.exports.createVault = async (ctx, next) => {
if ('POST' != ctx.method) return await next();
try {
if (!ctx.request.body.name) {
ctx.status = 404
}
//Create new vault
const vault = await Vault.create({
name: ctx.request.body.name,
url: ctx.request.body.url,
description: ctx.request.body.description
});
await vault.save();
//Return vault
ctx.body = vault;
ctx.status = 201;
}
catch (error) {
if (error) {
console.log('Error in the createVault controller:', error);
ctx.status = error.response.status;
ctx.body = error.response.data;
}
}
}
However, my problem occurs when I try to add a second Schema model; I am attempting to create a 'Crypt' from each of the items in the ctx.request.body.crypts array (see below):
//Create a vault
module.exports.createVault = async (ctx, next) => {
if ('POST' != ctx.method) return await next();
try {
if (!ctx.request.body.name) {
ctx.status = 404
}
//Create new vault
const vault = await Vault.create({
name: ctx.request.body.name,
url: ctx.request.body.url,
description: ctx.request.body.description
});
//Create new crypts
const promises = await ctx.request.body.crypts.map(crypt => Crypt.create({
name: crypt
}));
//Add reference to vault
const crypts = await Promise.all(promises);
vault.crypts = crypts.map(crypt => crypt._id);
await vault.save();
//Return vault and crypts
ctx.body = [vault, crypts];
ctx.status = 201;
}
catch (error) {
if (error) {
console.log('Error in the createVault controller:', error);
ctx.status = error.response.status;
ctx.body = error.response.data;
}
}
};
The error I receive say I cannot map over an undefined object, although I am using const crypts = await Promise.all(promises);
Can anyone suggest a correct way around this problem? Many thanks.

I managed to fix my problem by creating a function called cleanBody(body) which manually parsed the data for me to use.
I logged typeof ctx.request.body which returned a string and revealed my problem. The cleanBody(body) function just check if the body was an object, then used JSON.parse() if it was a string (see below):
const cleanBody = body => {
return typeof body !== 'object' ? JSON.parse(body) : body;
};
My mistake was assuming APIs from Postman and APIs called in the app would pass the same data, even when everything looks the same

Related

500 - internal server error my API is not working

I make a crud with products
I send an http request to the /api/deleteProduct route with the product id to retrieve it on the server side and delete the product by its id
To create a product it works only the delete does not work
pages/newProduct.js :
useEffect(() => {
async function fetchData() {
const res = await axios.get('/api/products');
setProducts(res.data);
}
fetchData();
}, []);
const handleSubmit = async (event) => {
event.preventDefault();
const formData = new FormData();
formData.append('picture', picture);
formData.append('name', name);
formData.append('price', price);
formData.append('category', category);
formData.append('description', description);
try {
const res = await axios.post('/api/createProduct', formData);
console.log(res.data);
} catch (error) {
console.log(error);
}
};
const handleDelete = async (id) => {
try {
await axios.delete(`/api/deleteProduct?id=${id}`);
setProducts(products.filter(product => product._id !== id));
} catch (error) {
console.log(error);
}
};
api/deleteProduct.js :
import Product from '../../models/Products';
import { initMongoose } from '../../lib/mongoose';
initMongoose();
export const handleDelete = async (req, res) => {
if (req.method === 'DELETE'){
try {
const { id } = req.params
const product = await Product.findByIdAndRemove(id);
if (!product) {
return res.status(404).json({ message: 'Product not found' });
}
return res.status(200).json({ message: 'Product deleted successfully' });
} catch (error) {
console.log(error);
return res.status(500).json({ message: 'Database error' });
}
}};
I have a 500 error but no error in the server side console and the console.log is not showing like the file was not read
Based on the code you've shared, it seems that the problem may be with the way that the delete request is being handled on the frontend. Specifically, in this line:
await axios.delete("/api/deleteProduct", { params: { id } });
The delete request is supposed to receive the id of the product that should be deleted as a query parameter, but it is being passed as a request body.
Instead of passing it as a parameter, you should pass it as a query parameter by changing it to
await axios.delete(`/api/deleteProduct?id=${id}`);
Also, in your api/deleteProduct.js, you should change the following line:
const { id } = req.query;
to
const { id } = req.params;
Also, you should make sure that the server is running and that the api endpoint '/api/deleteProduct' is accessible and handling the request correctly.
For the last, make sure that the product model is imported and initialized correctly and the database connection is established.
Hope that it solves your problem or, at least, helps :))
I succeeded, I put this (server side):
const { id } = req. query;
and (client side):
await axios.delete(/api/deleteProduct?id=${id});
and I exported my function like this:
export default async function handleDelete(req, res) {

Unhandled Rejection (SyntaxError): Unexpected end of JSON input with Get request fetch

I am trying to do a MVC architecture tutorial from Codecademy's website: https://www.codecademy.com/article/mvc-architecture-for-full-stack-app
I finished the tutorial but when I run everything, I get this error:
It seems that what I'm returning is not valid JSON. So I think the problem is that the endpoint may be causing the error. But I'm not too sure. Here is the code where the error is triggered:
src/utils/index.js:
export const fetchExpenses = async (date) => {
const selectDate = new Date(date).getTime() || new Date().getTime();
const res = await fetch(`/api/expense/list/${selectDate}`);
console.log('result',res);
return res.json();
};
Here is the code from app.js in the "view" portion of my code:
useEffect(() => {
// update view from model w/ controller
fetchExpenses().then((res) => setExpenses(res));
}, []);
It seems the problem is the communication between the view and the controller. When I create an expense, it actually is updated in the database:
Any ideas why this error is happening?
Edit 1:
Here is the network response when I try to create a new expense in my application. So it seems that when I create a new expense, the fetchExpenses() is automatically called to display a list of current expenses.
this the raw response I get from fetchExpenses() :
Edit 2:
Here is what the header shows from the response:
The endpoint is causing the error, but I'm not sure why. Here is the endpoint:
export const createExpense = async (data) => {
const res = await fetch(`/api/expense/create`, {
method: 'POST',
body: data,
});
return resHandler(res, 201);
};
and here is resHandler() which createExpense() returns:
export const resHandler = async (res, status) => {
if (res.status === status) {
return null;
}
const data = await res.json();
if (data && data.emptyFields) {
return data.emptyFields;
}
return null;
};
Here is the code from the controller when an expense is created:
exports.create = (req, res) => {
const form = new formidable.IncomingForm();
form.keepExtensions = true;
form.parse(req, async (err, fields) => {
const { title, price, category, essential, created_at } = fields;
// check for all fields
if (fieldValidator(fields)) {
return res.status(400).json(fieldValidator(fields));
}
try {
const newExpense = await pool.query(
'INSERT INTO expenses (title, price, category, essential, created_at) VALUES ($1, $2, $3, $4, $5)',
[title, price, category, essential, created_at]
);
return res.status(201).send(`User added: ${newExpense.rows}`);
} catch (error) {
return res.status(400).json({
error,
});
}
});
};
Edit 3
Here is the route /api/expense/list/{dateTime}:
const express = require('express');
const router = express.Router();
const { create, expenseById,
read, update, remove, expenseByDate } = require('../controllers');
router.get('/expense/list/:expenseDate', expenseByDate, read);
module.exports = router;
And here is my controllers.js that deal with the route above:
exports.expenseByDate = async (req, res, next, date) => {
try {
const expenseQuery = await pool.query(
'SELECT * FROM expenses WHERE created_at BETWEEN $1 AND $2',
[
startOfDay(new Date(Number(date))).toISOString(),
endOfDay(new Date(Number(date))).toISOString(),
]
);
const expenseList = expenseQuery.rows;
req.expense =
expenseList.length > 0
? expenseList
: `No expenses were found on this date.`;
return next();
} catch (error) {
return res.status(400).json({
error,
});
}
};
exports.read = (req, res) => res.json(req.expense);
The reason you are getting an Unhandled Rejection (SyntaxError): Unexpected end of JSON input error is because your client app is expecting a JSON response and the express app /api/expense/list/{dateTime} route is not returning valid JSON.
The app is not returning valid JSON because the expenseByDate controller callback has an incorrect function signature so it is not getting called.
exports.expenseByDate = async (req, res, next, date) => <-- "date" is not a valid parameter.
This leads the read controller to return an undefined value to the json response.
exports.read = (req, res) => res.json(req.expense); <-- req.expense is undefined.
res.json(undefined) ultimately returns an empty response to the client which can't be parsed and thus an error is thrown.
Solution
You can fix this error by correcting the expenseByDate controller to have a valid function signature by removing the fourth method parameter. To access a route parameter you should use req.params.
exports.expenseByDate = async (req, res, next, date) => {
const date = req.params.expenseDate;
...
}

Send Map in express res.send

I'm working on a game with a friend and we need to send a Map with some stuff in it, but express only sends the user {} instead of the actual Map. The problem is at sending it and not the code itself, console.log'ging it does return the Map.
Code:
router.get("/list", async (req, res) => {
try {
const users = await userCollection.find();
accessedListEmbed(req);
let userData = new Map();
users.forEach((user) => userData.set(user.userName, user.status));
res.send(userData);
console.log(userData);
} catch (error) {
res.send("unknown");
}
});
Generally, you can only send serializable values over the network. Maps aren't serializable:
const map = new Map();
map.set('key', 'value');
console.log(JSON.stringify(map));
Either send an array of arrays that can be converted into a Map on the client side, or use another data structure, like a plain object. For example:
router.get("/list", async (req, res) => {
try {
const users = await userCollection.find();
accessedListEmbed(req);
const userDataArr = [];
users.forEach((user) => {
userDataArr.push([user.userName, user.status]);
});
res.json(userDataArr); // make sure to use .json
} catch (error) {
// send JSON in the case of an error too so it can be predictably parsed
res.json({ error: error.message });
}
});
Then on the client-side:
fetch(..)
.then(res => res.json())
.then((result) => {
if ('error' in result) {
// do something with result.error and return
}
const userDataMap = new Map(result);
// ...
Or something along those lines.

Making a distinction between file not present and access denied while accessing s3 object via Javascript

I have inherited the following code. This is part of CICD pipeline. It tries to get an object called "changes" from a bucket and does something with it. If it is able to grab the object, it sends a success message back to pipeline. If it fails to grab the file for whatever reason, it sends a failure message back to codepipeline.
This "changes" file is made in previous step of the codepipeline. However, sometimes it is valid for this file NOT to exist (i.e. when there IS no change).
Currently, the following code makes no distinction if file simply does not exist OR some reason code failed to get it (access denied etc.)
Desired:
I would like to send a success message back to codepipeline if file is simply not there.
If there is access issue , then the current outcome of "failure' would still be valid.
Any help is greatly appreciated. Unfortunately I am not good enough with Javascript to have any ideas to try.
RELEVANT PARTS OF THE CODE
const AWS = require("aws-sdk");
const s3 = new AWS.S3();
const lambda = new AWS.Lambda();
const codePipeline = new AWS.CodePipeline();
// GET THESE FROM ENV Variables
const {
API_SOURCE_S3_BUCKET: s3Bucket,
ENV: env
} = process.env;
const jobSuccess = (CodePipeline, params) => {
return new Promise((resolve, reject) => {
CodePipeline.putJobSuccessResult(params, (err, data) => {
if (err) { reject(err); }
else { resolve(data); }
});
});
};
const jobFailure = (CodePipeline, params) => {
return new Promise((resolve, reject) => {
CodePipeline.putJobFailureResult(params, (err, data) => {
if (err) { reject(err); }
else { resolve(data); }
});
});
};
// MAIN CALLER FUNCTION. STARTING POINT
exports.handler = async (event, context, callback) => {
try {
// WHAT IS IN changes file in S3
let changesFile = await getObject(s3, s3Bucket, `lambda/${version}/changes`);
let changes = changesFile.trim().split("\n");
console.log("List of Changes");
console.log(changes);
let params = { jobId };
let jobSuccessResponse = await jobSuccess(codePipeline, params);
context.succeed("Job Success");
}
catch (exception) {
let message = "Job Failure (General)";
let failureParams = {
jobId,
failureDetails: {
message: JSON.stringify(message),
type: "JobFailed",
externalExecutionId: context.invokeid
}
};
let jobFailureResponse = await jobFailure(codePipeline, failureParams);
console.log(message, exception);
context.fail(`${message}: ${exception}`);
}
};
S3 should return an error code in the exception:
The ones you care about are below:
AccessDenied - Access Denied
NoSuchKey - The specified key does not exist.
So in your catch block you should be able to validate exception.code to check if it matches one of these 2.

How do you receive Whatsapp messages from Twilio using Node.JS?

I am trying to build a Whatsapp chatbot using Node.JS and am running into a bit of trouble in receiving the Whatsapp message from Twilio. On checking the debugger, I get a Bad Gateway error, ie. Error 11200: HTTP Retrieval Failure. The message is getting sent, and ngrok shows the post request, however, dialogflow does not receive the request. On terminal, the error is showing UnhandledPromiseRejectionWarning: Error: 3 INVALID ARGUMENT: Input text not set. I'm not sure if it's because the message is not in JSON format. Please help!
This is the app.post function:
app.post('/api/whatsapp_query', async (req, res) =>{
message = req.body;
chatbot.textQuery(message.body, message.parameters).then(result => {
twilio.sendMessage(message.from, message.to, result.fulfillmentText).then(result => {
console.log(result);
}).catch(error => {
console.error("Error is: ", error);
});
return response.status(200).send("Success");
})
});
And this is the sendMessage function I've imported:
const config = require('./config/keys');
const twilioAccountID = config.twilioAccountID;
const twilioAuthToken = config.twilioAuthToken;
const myPhoneNumber = config.myPhoneNumber;
const client = require('twilio')(twilioAccountID,twilioAuthToken);
module.exports = {
sendMessage: async function(to, from, body) {
return new Promise((resolve, reject) => {
client.messages.create({
to,
from,
body
}).then(message => {
resolve(message.sid);
}).catch(error => {
reject(error);
});
});
}
}
And this is the textQuery function I've imported:
textQuery: async function(text, parameters = {}) {
let self = module.exports;
const request = {
session: sessionPath,
queryInput: {
text: {
text: text,
languageCode: config.dialogFlowSessionLanguageCode
},
},
queryParams: {
payload: {
date: parameters
}
}
};
let responses = await sessionClient.detectIntent(request);
responses = await self.handleAction(responses)
return responses[0].queryResult;
},
Twilio developer evangelist here.
The issue is that you are not passing the correct message body from the incoming WhatsApp message to your textQuery function.
First, you should make sure that you are treating the incoming webhook from Twilio as application/x-www-form-urlencoded. If you are using body-parser, ensure you have urlencoded parsing turned on.
app.use(bodyParser.urlencoded());
Secondly, the parameters that Twilio sends start with a capital letter. So your code currently gets message = req.body and then uses message.body. But it should be message.Body.
Those two points should sort you out.
One final thing though. The Twilio Node.js library will return a Promise if you do not pass a callback function. So you don't need to create a Promise here:
module.exports = {
sendMessage: async function(to, from, body) {
return new Promise((resolve, reject) => {
client.messages.create({
to,
from,
body
}).then(message => {
resolve(message.sid);
}).catch(error => {
reject(error);
});
});
}
}
You can just return the result of the call to client.messages.create
module.exports = {
sendMessage: async function(to, from, body) {
return client.messages.create({ to, from, body });
}
}
Hope this helps.

Categories