Node JS uploading file field as a field of an object in the request body using Formidable - node.js

I am building a Node JS application using Express JS. I need to implement the file upload logic for my application. I am using Formidable, https://www.npmjs.com/package/formidable for uploading file(s). I could upload the files using that library without any issue when the request body format is simple. Now, I am trying to upload the file which is a property/ field of an object in the request body. The following is the dummy request body structure.
{
users: [
{
name: `Test User 1`,
photoFile: {{ here I want to upload file for this user }}
},
{
name: `Test User 2`,
photoFile: {{ here I want to upload file for this user }}
},
// the list goes on
]
}
I am trying to send the test payload/ data in the Postman as in the screenshot below.
This is my code to parse the form
private getForm = async (
req: Request,
options: FormOptions = {
multiples: false
}
) => {
const tempUploadDir = this.getTempUploadDirectory(req);
if (!(await this.storage.exits(tempUploadDir))) {
await this.storage.mkdir(tempUploadDir);
}
return new IncomingForm({
multiples: options.multiples,
uploadDir: tempUploadDir,
maxFileSize: config.fileSystem.maxUploadLimit
});
};
public parseFormData = async <T>(
request: Request,
options: FormOptions = {
multiples: false
}
): Promise<T> => {
const form = await this.getForm(request, options);
return new Promise<T>((resolve) => {
form.parse(request, (err, fields, files) => {
if (err) {
if (err.code === FILE_TOO_LARGE_ERROR_CODE) {
// file too large
throw new UploadMaxFileSizeException(
`Upload file size limit exceeded.`
);
}
throw err;
}
let filesData: {
[key: string]: IUploadedFile[];
} = {};
for (let fileField in files) {
if (`length` in files[fileField]) {
filesData[fileField] = files[fileField] as IUploadedFile[];
} else {
filesData[fileField] = [files[fileField] as IUploadedFile];
}
}
return resolve({
...fields,
...files
} as T);
});
});
};
When I print out the result of parseFormData, I am getting the following result.
As you can see, the field field, 'users[0][photoFile]' is not parsed putting into the corresponding field of the request body object. Instead, the entire field name is string, 'users[0][photoFile]'. What is wrong with my code and how can I fix it?

I my project, file will upload to server when user upload files and get the link back
And when submit form, user avatar just is a link, not a file

Related

How to create new item in Nodejs?

Have a simply issue. Currently doing some pet-project and took nodejs api for it.
The API is written on nodejs and want to implements with my react-app.
But bumped into i can not create dummy date and to try testing the ui.
Here is method of creating:
.post('/', function (req, res) {
var directory = _.pick(req.body, [
'parentId',
'name'
]
)
, parent = _.find(store.directories, function (dir) {
return dir.id == directory.parentId
})
if (parent) {
_.assign(directory, { id: idGenerator.getNext() })
store.directories.push(directory)
res.send(directory)
} else {
res.status(500).send('no parent')
}
})
The problem is i can not even to make a new row in postman:
{
"parentId": 1,
"name": "some title"
}
Sending RAW => json and receiving: "Cannot POST /"

how to adjust my code to send data in json format in angular

I hope you can help me, I need to send some parameters in json format like this:
{
"InformationA": {
"str_id": 1,
"str_description": "message",
"str_email": "abcd#abcd.com.co"
},
"AddConfiguration": [
{
"int_code": 1,
"str_valor": "32201"
},
{
"int_code": 104,
"str_valor": "https://www.google.com.co/"
},
{
"int_code": 108,
"str_valor": "1"
}
]
}
I am trying to send the json through the angular service in this way but I don't know if it is correct?:
sendData(InformationA,AddConfiguration){
const params = 'InformationA=' +JSON.stringify(InformationA)+'AddConfiguration=' +
JSON.stringify(AddConfiguration);
return this.http.post<any>(`${this.route}/send-data`, params , { headers: this.headers });
}
also create a function in the nodejs backend to see how it would arrive:
#Post('send-data')
async receibeData(#Req() req, #Res() res) {
try {
const data = req.body;
res.status(HttpStatus.OK).json(data)
} catch (err) {
throw err;
}
}
and by console it is printed in this way:
{,…}
InformationA:"
[{"str_id":"1","str_description":"message","str_email":"abcd#abcd.com.co"}]Addconfiguration=
[{"int_code":1,"str_valor":"32201 "},{"int_code":104,"str_valor":"https://www.google.com.co
"},{"int_code":108,"str_valor":"1 "}]"
I am really very new to this and I would like to know how I adapt my data so that it can be sent as requested.
I think you should try to build the JSON object corresponding to your requirement. You should not use JSON.stringify for this purpose. I hope this will help you out.
sendData(InformationA,AddConfiguration) {
const params = {
InformationA: InformationA,
AddConfiguration: AddConfiguration
};
return this.http.post<any>(`${this.route}/send-data`, params , { headers: this.headers });
}

create theme on shopify using api

I am trying to create an app and within the app the user can install a theme, however, I can't seem to work out why the theme is not being created. It keeps pulling the themes already installed on my store to the console, my code doesn't seem to create a theme that would show up on my shopify store.
server.js
router.post('/api/theme', async (ctx) => {
try {
const results = await fetch("https://" + ctx.cookies.get('shopOrigin') + "/admin/themes.json", {
headers: {
'X-Shopify-Access-Token': ctx.cookies.get('accessToken')
},
})
.then(response => response.json())
.then(json => {
console.log("https://" + ctx.cookies.get('shopOrigin') + "/admin/api/2020-01/themes.json", json);
});
ctx.body = {
data: results
};
} catch (err) {
console.log(err)
}
});
frontend .js file
async function getUser() {
var url = `/api/theme`;
var method = 'post';
const theme = {
theme: {
name: "Lemongrass",
src: "https://codeload.github.com/Shopify/skeleton-theme/zip/master"
}
};
const data = JSON.stringify(theme);
fetch(url, { method: method, body: data})
}
In order to create a theme you need a zip archive of the theme you like to create.
The end point should be /admin/api/2020-01/themes.json and the body should be something like this:
{
"theme": {
"name": "Theme name",
"src": "http://themes.shopify.com/theme.zip",
"role": "unpublished"
}
}
Please refer to https://shopify.dev/docs/admin-api/rest/reference/online-store/theme#create-2020-01 for more information.
At the moment from your code I don't see neither the correct POST request, neither the archive file.

Download File with loopback 4

I want to download a file from a loopback 4 based server. My current situation is, that i can access the file with fs.readFileSync, but it's only working for text-files. If i want to download pdf or zip files it's not working.
This is what i have so far:
export class FileController
{
constructor(
#repository(FileRepository) public fileRepository: FileRepository
){}
#get('/files/download/{id}')
async download(#param.path.number('id') id: number): Promise<string>
{
const file = await this.fileRepository.findById(id);
const filepath = file.FilePath;
if(!fs.existsSync(filepath))
{
throw new HttpErrors.NotFound(`The File #${id} can not be delivered, because the file is missing.`);
}
else
{
// #todo set headers for content type, length and caching
return fs.readFileSync(filepath,'utf8');
}
}
}
If i inject RestBindings.Http.RESPONSE into the constructor, I'm able to access the response object and might edit the headers using the setHeader-Method, but with no affect.
What do i have to do to:
pass the file content correctly to the client
set the headers to tell the browser the correct file meta data
Using this.response.download():
return await new Promise((resolve: any, reject: any) => {
// your logic ...
this.response.download(filepath, (err: any) => {
if (err) reject(err);
resolve();
});
});

Permission(ACL) middleware for Multer that acts based on multipart/form-data parameters

In my Express.js REST API, I'm using multer to upload images into server's static folder with diskStorage. I have wanted to build a generic file upload page for both users and admins. Detailed use case is as follows:
choose a category from a drop-down list
write an valid id in the form field,
choose a valid image(file) and submit.
if regular user (with isAdmin==false) selects category different than users , then the API responds with a 403 and won't upload the image at all.
What I want to achieve is to allow only users with isAdmin==true to upload images for the all categories. But, within the same route, regular users can only upload images for the users category.
The question is how to get access to the req.body.category inside authentication & authorization middleware and reject upload process pre-flight?
I need to check category and isAdmin fields at the same time to make decision about giving permission for upload process or not.
React-side
const formData = new FormData()
formData.append('id', this.state.id)
formData.append('category', this.state.category)
formData.append('photo', this.state.file)
this.props.fileUpload(formData)
Express-side
Router
router.post('/upload', AuthController.role('admin'), UploadController.uploadPhoto);
AuthController
public static role(role: any) {
return (req: Request, res: Response, next: NextFunction) => {
if (role === 'admin') {
console.dir(req.body); // req.body prints { user_id: 1, isAdmin: true }
// cannot decide whether give access to the route or not over here because we don't know the req.body.category yet.
if (req.body && req.body.isAdmin) {
next();
} else {
return res.status(403).send(new Unauthorized());
}
}
};
}
UploadController
const imageFilter = (req, file, cb) => {
console.dir(req.body); // prints { id: '1', category: 'users' } cannot access req.body.isAdmin over here
if (!file.originalname.match(/\.(jpg|jpeg|png|gif)$/)) {
return cb(new Error('Only image files are allowed!'), false);
}
cb(null, true);
};
const upload = util.promisify(multer({ storage, fileFilter: imageFilter, limits: {fileSize: MAX_SIZE} })
.single('photo'));
export class UploadController {
public static async uploadPhoto(req: any, res: Response, next: NextFunction) {
// req.body.isAdmin is available over here
try {
await upload(req, res);
imageUrl = `${HOST}/static/${req.file.filename}`;
} catch (e) {
return res.status(400).send(new BadRequest('Image upload operation is not successful.'));
}
}
UPDATE: My current workaround solution is to save the image temporarily and then check the category and isAdmin at the same time when they become available.
try {
await upload(req, res);
....
..
// Now req.body.category and req.body.isAdmin are both available here
}

Resources