node js express file upload is corrupted - node.js

Uploaded files are the same size 16 bytes and corrupted. What am I doing wrong?
looks like the server code is working as it should, most likely a client side issue. But I could be wrong, so I added everything.
server side code:
import path from 'path'
import fs from 'fs'
import { fileURLToPath } from 'url'
class authController {
downloadFile(req, res) {
console.log(req.query)
try {
const parentFolder = req.query.parentFolder
const folderId = req.query.folderId
const fileName = req.query.fileName
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
const file = path.join(__dirname, `../files/${parentFolder}/${folderId}/${fileName}`)
res.download(file, fileName)
} catch (e) {
console.log(e)
res.status(500).json({ message: 'Something went wrong, please try again' })
}
}
}
export default new authController()
client side code:
onClick={async function (e) {
e.stopPropagation()
const request = 'downloadFile'
const response = await fetch(`/api/auth/${request}?parentFolder=${parentFolder}&folderId=${folderId}&fileName=${item}`, {
headers: {
Authorization: 'Bearer ' + auth.token,
},
})
if (response.status === 200) {
console.log(response)
const blob = response.blob()
const downloadUrl = window.URL.createObjectURL(new Blob([blob], { type: 'image/png' }))
const link = document.createElement('a')
link.href = downloadUrl
link.download = item
document.body.appendChild(link)
link.click()
link.remove()
}
}}

I managed to solve the problem. Replaced
const blob = response.blob()
with const blob = await response.blob() and everything worked.

Related

Vue - How to display image received from backend API?

I'm building a webApp in MEVN stack (Mongo, Express, Vue, Node).
In my backend, I have a controller (./backend/controllers/screenshots.controller.js) downloading an image from an external REST API. The image (PNG) is downloaded in a directory called 'images' placed in the controllers directory.
screenshots.controller.js:
const path = require('path');
const axios = require('axios');
const fs = require('fs');
const downloadScreenshot = async(screenshotPath) => {
let isDownloaded = false;
const fileUrl = `https://myexternalapi.com/screenshot/${screenshotPath}`;
const fileName = screenshotPath.split('/')[1]
const downloadFolder = './images'
if(!fs.existsSync(downloadFolder)){
fs.mkdirSync(downloadFolder);
console.log('Images directory created successfully.');
}
const localFilePath = path.resolve(__dirname, downloadFolder, fileName);
try {
const response = await axios({
method: 'GET',
url: fileUrl,
responseType: 'stream',
});
if(response.status === 200){
isDownloaded = true;
}
await response.data.pipe(fs.createWriteStream(localFilePath));
} catch (error) {
console.log('Error occured while downloading screenshot... : ', error);
}
return { isDownloaded, fileName };
}
const readScreenshot = async(req, res) => {
try {
const {isDownloaded, fileName} = await downloadScreenshot(req.body.temp);
if(isDownloaded){
console.log('__dirname + /images/ + fileName : ', __dirname + '/images/' + fileName )
res
.status(200)
.sendFile(fileName, {root : __dirname + '/images/'} );
} else {
res
.status(500)
.send({
message: 'No screenshot for this offer...'
})
}
} catch (error) {
console.log('Error occured while retrieving screenshot...', error)
res
.status(500)
.send({ message: error });
}
}
module.exports = {
readScreenshot: readScreenshot,
}
I would like to display the required image in my Vue app. Thus, I created the following view: ReadScreenshot.vue
<template>
<div>
<img :src="img">
</div>
</template>
<script>
import Screenshots from '../../services/screenshots.service'
export default {
props: ['id'],
data(){
return {
img: '',
}
},
async mounted(){
console.log(this.id)
const temp = await Screenshots.readScreenshot({ temp: this.id });
console.log(temp)
this.img = temp.data
}
}
</script>
Here is my screenshots.service.js script:
import api from '../../http-common';
export default new class ScreenshotsService {
//Read Screenshot
readScreenshot(screenshotName){
return api.post('read/screenshot', screenshotName)
}
}
Console.log(temp) is returning empty data.
In the screenshots.controller.js file, if I'm forcing the fileName with an existing one in the sendFile function, e.g. '2882LsgIXHOiXiOQ5MSv3R6v1hDijAdG5i756CdG5o7v527i5sS1XZgiXR6i1sSGj.png', I'm receiving a non empty data in my ReadScreenshot.vue .
Even if I'm receiving the data, the image is still not displayed...
How should I proceed, to get this right?
thks for your help

File upload from React Native ( expo ) to Node ( multer )

How can I upload a file( pdf, docs etc) from React Native using expo to the server using node. I've seen many examples for images using the expo image-picker api but I've come across none that uses document-picker or filesystem apis from expo. The expo file system documentation was a little hard to interpret for a beginner like me.
Thanks for the help. I was able to come up with a solution and I'll post it below so it can be of some use to whoever comes here in the future.
React Native
import React, { useState } from 'react';
import { Button, View } from 'react-native';
import * as DocumentPicker from 'expo-document-picker';
import * as FileSystem from 'expo-file-system';
const DocPicker = () => {
const [ doc, setDoc ] = useState();
const pickDocument = async () => {
let result = await DocumentPicker.getDocumentAsync({ type: "*/*", copyToCacheDirectory: true }).then(response => {
if (response.type == 'success') {
let { name, size, uri } = response;
let nameParts = name.split('.');
let fileType = nameParts[nameParts.length - 1];
var fileToUpload = {
name: name,
size: size,
uri: uri,
type: "application/" + fileType
};
console.log(fileToUpload, '...............file')
setDoc(fileToUpload);
}
});
// console.log(result);
console.log("Doc: " + doc.uri);
}
const postDocument = () => {
const url = "http://192.168.10.107:8000/upload";
const fileUri = doc.uri;
const formData = new FormData();
formData.append('document', doc);
const options = {
method: 'POST',
body: formData,
headers: {
Accept: 'application/json',
'Content-Type': 'multipart/form-data',
},
};
console.log(formData);
fetch(url, options).catch((error) => console.log(error));
}
return (
<View>
<Button title="Select Document" onPress={pickDocument} />
<Button title="Upload" onPress={postDocument} />
</View>
)
};
export default DocPicker;
Node.js
const express = require('express')
const bodyParser = require('body-parser')
var multer = require('multer')
var upload = multer({ dest: 'uploads/' })
const app = express()
const fs = require('fs')
const http = require('http')
const port = 8000
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.get('/', (req,res) => {
res.json({
success: true
})
})
app.post('/', (req, res) => {
console.log(req.body)
res.status(200)
})
app.post('/upload', upload.single('document'),(req , res) => {
console.log(req.file, req.body)
});
app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`)
})
Cheers!!!
If the solution given by #Anandhu doesn't work then try the above code like this.
import React, { useState } from 'react';
import { Button, View } from 'react-native';
import * as DocumentPicker from 'expo-document-picker';
import * as FileSystem from 'expo-file-system';
const DocPicker = () => {
const [ doc, setDoc ] = useState();
const pickDocument = async () => {
let result = await DocumentPicker.getDocumentAsync({
type: "*/*",
copyToCacheDirectory: true })
.then(response => {
if (response.type == 'success') {
let { name, size, uri } = response;
/ ------------------------/
if (Platform.OS === "android" && uri[0] === "/") {
uri = `file://${uri}`;
uri = uri.replace(/%/g, "%25");
}
/ ------------------------/
let nameParts = name.split('.');
let fileType = nameParts[nameParts.length - 1];
var fileToUpload = {
name: name,
size: size,
uri: uri,
type: "application/" + fileType
};
console.log(fileToUpload, '...............file')
setDoc(fileToUpload);
}
});
// console.log(result);
console.log("Doc: " + doc.uri);
}
const postDocument = () => {
const url = "http://192.168.10.107:8000/upload";
const fileUri = doc.uri;
const formData = new FormData();
formData.append('document', doc);
const options = {
method: 'POST',
body: formData,
headers: {
Accept: 'application/json',
'Content-Type': 'multipart/form-data',
},
};
console.log(formData);
fetch(url, options).catch((error) => console.log(error));
}
return (
<View>
<Button title="Select Document" onPress={pickDocument} />
<Button title="Upload" onPress={postDocument} />
</View>
)
};
export default DocPicker;
There is a bug in the way the path was encoded, and the file:// scheme is missing.
This bug may be fixed in next release.
Try this Uploading pictures,documents and videos from your phone in your app with React Native, Expo
Here is an example, which also uses multer and express on the backend: https://github.com/expo/examples/tree/master/with-formdata-image-upload
That said, I'd recommend using FileSystem.uploadAsync instead of fetch and the background sessionType in order to support uploads while the app is backgrounded on iOS.

NodeJS create and POST file with GQL

I've been unable to figure out in NodeJS how to:
create a "file" in memory from a raw string; and,
how to POST that data to another server that expects a multipart/form-data payload.
Seems you cannot use the Blob or File classes in NodeJS.
I've read the pattern should be to use the Buffer class.
I still cannot get it to work with Buffers.
My GQL Datasoruce class looks something like:
const { RESTDataSource } = require('apollo-datasource-rest');
const FormData = require('form-data');
export default class MyDatasource extends RESTDataSource {
async postFileToServer({ string }) {
const inMemoryFile = Buffer.from(string, 'utf-8');
const myForm = new FormData();
myForm.append('file', inMemoryFile, 'file.txt');
const url = 'http://examnple.com';
const opts = { headers: { 'Content-Type': 'multipart/form-data' } };
return await this.post(url, myForm, opts);
}
}
The endpoint I want to hit works fine when I use Postman to make the API call with a file from my local machine. However, I need the GQL server to create the file from a raw string to afterwards call the example.com endpoint that is expecting a multipart/form-data.
The above example code always gives me an error of Status 400 and SyntaxError: Unexpected token - in JSON at position 0
File upload works for me using the apollo-datasource-rest package. Here is an example:
server.ts:
import { ApolloServer, gql } from 'apollo-server';
import MyDatasource from './datasource';
const typeDefs = gql`
type Query {
dummy: String
}
type Mutation {
upload: String
}
`;
const resolvers = {
Mutation: {
upload(_, __, { dataSources }) {
return dataSources.uploadAPI.postFileToServer({ str: '1234' });
},
},
};
const server = new ApolloServer({
typeDefs,
resolvers,
dataSources: () => {
return {
uploadAPI: new MyDatasource(),
};
},
});
const port = 3001;
server.listen(port).then(({ url }) => console.log(`🚀 Server ready at ${url}`));
datasource.ts:
import { RESTDataSource } from 'apollo-datasource-rest';
import FormData from 'form-data';
export default class MyDatasource extends RESTDataSource {
public async postFileToServer({ str }) {
const inMemoryFile = Buffer.from(str, 'utf-8');
const myForm = new FormData();
myForm.append('file', inMemoryFile, 'file.txt');
const url = 'http://localhost:3000/upload';
return this.post(url, myForm);
}
}
uploadServer.ts:
import multer from 'multer';
import express from 'express';
import path from 'path';
const upload = multer({ dest: path.resolve(__dirname, 'uploads/') });
const app = express();
const port = 3000;
app.post('/upload', upload.single('file'), (req, res) => {
console.log(req.file);
console.log(req.body);
res.sendStatus(200);
});
app.listen(port, () => {
console.log(`upload server is listening on http://localhost:${port}`);
});
The logs printed in the controller of /upload API:
{
fieldname: 'file',
originalname: 'file.txt',
encoding: '7bit',
mimetype: 'text/plain',
destination: '/Users/ldu020/workspace/github.com/mrdulin/apollo-graphql-tutorial/src/stackoverflow/63181608/uploads',
filename: '3cba4dded6089479ad495e2fb2daac21',
path: '/Users/ldu020/workspace/github.com/mrdulin/apollo-graphql-tutorial/src/stackoverflow/63181608/uploads/3cba4dded6089479ad495e2fb2daac21',
size: 4
}
[Object: null prototype] {}
source code: https://github.com/mrdulin/apollo-graphql-tutorial/tree/master/src/stackoverflow/63181608

Renamed picture in Firebase not showing after opening url

I am trying to implement an upload profile image feature for a users collection. I implement an async function as such
exports.async_upload_image = async function(req, res) {
const path = require("path");
const os = require("os");
const fs = require("fs");
const { Storage } = require('#google-cloud/storage');// end up no need
let gcs = new Storage({
projectId: config.projectId
});
try {
const newFilePath = req.filepath;
const baseName = newFilePath.split("/").pop();
if (req.mimetype !== "image/jpeg" && req.mimetype !== "image/png") {
console.log("Wrong file type submitted");
return null;
}
// my.image.png => ['my', 'image', 'png']
const image_extension = newFilePath.split(".")[newFilePath.split(".").length - 1];
// 32756238461724837.png
let generated_token = uuid();
let image_filename = `${generated_token}.${image_extension}`;
const processed_path = path.join(os.tmpdir(), image_filename);
//creates a copy of image file inside the temporary path
const input_file = fs.createReadStream(newFilePath);
const output_file = fs.createWriteStream(processed_path);
input_file.pipe(output_file);
//upload to the firebase storage from the temporary path
await gcs.bucket(config.storageBucket).upload(processed_path, {
gzip: true,
metadata: {
cacheControl: "no-cache",
contentType: req.mimetype,
firebaseStorageDownloadTokens: generated_token
}
})
const imageUrl = `https://firebasestorage.googleapis.com/v0/b/${config.storageBucket}/o/${image_filename}?alt=media&token=${generated_token}`;
// await gcs.bucket(config.storageBucket).upload(newFilePath, {
// gzip: true,
// metadata: {
// cacheControl: "no-cache",
// contentType: req.mimetype,
// firebaseStorageDownloadTokens: generated_token
// }
// })
// const imageUrl = `https://firebasestorage.googleapis.com/v0/b/${config.storageBucket}/o/${baseName}?alt=media&token=${generated_token}`;
await db.collection(USERS_PUBLIC_COLLECTION).doc(req.user_id).update({
profile_image:
{
uid: generated_token,
url: imageUrl
}
})
console.log(`Update profile to uploaded image ${generated_token} successfully`);
return success_response();
} catch (error) {
console.log(error);
}
}
And wrote this at the bottom and ran with node file.js in the same file.
const req = {
filepath: some_file_path,
mimetype: "image/png",
user_id: "valid_user_id"
}
exports.async_upload_image(req);
The picture does get uploaded to storage as well as updating my document in the Firestore, but it was intended that accessing URL under the url in the profile_image map will allow me to see the picture. This works for the unprocessed picture, whose code segment is commented out, but not for the changed image. I also noted that the file size is incredibly small, around 20B. Can someone tell me why and what might be a better way to upload images with firebase? Feel free to clarify if more info is required to solve the problem.

Nock and google maps client

I'm trying to test a service that uses the #google/maps client for getting directions data.
Here is a simplified version of the service:
'use strict'
const dotenv = require('dotenv')
const GoogleMaps = require('#google/maps')
dotenv.config()
const {GOOGLE_API_KEY: key} = process.env
const client = GoogleMaps.createClient({key, Promise})
const getData = exports.getData = async function({origin, destination}) {
try {
const options = {
origin,
destination,
mode: 'transit',
transit_mode: ['bus', 'rail']
}
const res = await client.directions(options).asPromise()
return res
} catch (err) {
throw err
}
}
And here is a test file to show the case:
'use strict'
const dotenv = require('dotenv')
const nock = require('nock')
const gdService = require('./gd.service')
dotenv.config()
const {GOOGLE_API_KEY: key} = process.env
const response = {json: {name: 'custom'}}
const origin = {lat: 51.5187516, lng: -0.0836314}
const destination = {lat: 51.52018, lng: -0.0998361}
const opts = {origin, destination}
nock('https://maps.googleapis.com')
.get('/maps/api/directions/json')
.query({
origin: `${origin.lat},${origin.lng}`,
destination: `${destination.lat},${destination.lng}`,
mode: 'transit',
transit_mode: 'bus|rail',
key
})
.reply(200, response)
gdService.getData(opts)
.then(res => {
console.log(res.json) // it's undefined!
})
.catch(err => {
console.error(err)
})
What I expect is to get the defined response as a response of the service method invocation. But I get undefined. Why is that?
After reading the source of #google/maps client, I figured out that I had to provide nock with the following reply header:
...
nock('https://maps.googleapis.com')
.defaultReplyHeaders({
'Content-Type': 'application/json; charset=UTF-8'
})
.get('/maps/api/directions/json')
...

Resources