Download file from internet and send it to S3 as stream - node.js

I am trying to download some PDFs from internet into my Amazon S3 bucket, so far i download the files on my server and then upload them from my server to S3 Bucket but i was curious if i can upload them while downloading them as stream.
private async download(url: string, path: string): Promise<void> {
const response = await fetch(url);
const fileStream = createWriteStream(path);
await new Promise((resolve, reject) => {
response.body.pipe(fileStream);
response.body.on('error', reject);
fileStream.on('finish', resolve);
});
}
and this is my upload file after i downloaded it
public async upload(path: string, name: string): Promise<string> {
const url = 'documents/${name}.pdf';
const params = {
Body: createReadStream(path),
Bucket: AWS_S3_BUCKET_NAME,
Key: url
}
const data = await s3.putObject(params).promise().then(data => { console.log(data); return url; }, err => { console.log(err); return err; });
return data;
}
I am looking for a way to merge these 2 functions into one and return the S3 bucket reply after finished or throw an error if download or upload gave an error.
Also i wanted to ask if it is possible to call this function multiple times in parallel and if it's possible, how many times is safe to not break the server.
Thank you in advance, Daniel!

Yes, you can. Working example for downloading and uploading at the same time using multipart upload for node environment:
import {
AbortMultipartUploadCommandOutput,
CompleteMultipartUploadCommandOutput,
S3Client,
} from '#aws-sdk/client-s3';
import { Upload } from '#aws-sdk/lib-storage';
import Axios, { AxiosResponse } from 'axios';
import mime from 'mime-types';
import { Logger } from 'pino';
import { PassThrough } from 'stream';
export class S3HandlerClient {
private readonly PART_SIZE = 1024 * 1024 * 5; // 5 MB
private readonly CONCURRENCY = 4;
private readonly logger: Logger;
private readonly client: S3Client;
constructor(props: { logger: Logger; sdkClient: S3Client }) {
this.logger = props.logger;
this.client = props.sdkClient;
}
async uploadVideo(props: {
input: {
videoUrl: string;
};
output: {
bucketName: string;
fileNameWithoutExtension: string;
};
}): Promise<string> {
try {
const inputStream = await this.getInputStream({ videoUrl: props.input.videoUrl });
const outputFileRelativePath = this.getFileNameWithExtension(
props.output.fileNameWithoutExtension,
inputStream,
);
await this.getOutputStream({
inputStream,
output: {
...props.output,
key: outputFileRelativePath,
},
});
return `s3://${props.output.bucketName}/${outputFileRelativePath}`;
} catch (error) {
this.logger.error({ error }, 'Error occurred while uploading/downloading file.');
throw error;
}
}
private getFileNameWithExtension(fileName: string, inputStream: AxiosResponse) {
this.logger.info({ headers: inputStream.headers });
return `${fileName}.${this.getFileExtensionFromContentType(
inputStream.headers['content-type'],
)}`;
}
private getFileExtensionFromContentType(contentType: string): string {
const extension = mime.extension(contentType);
if (extension) {
return extension;
} else {
throw new Error(`Failed to get extension from 'Content-Type' header': ${contentType}.`);
}
}
private async getInputStream(props: { videoUrl: string }): Promise<AxiosResponse> {
this.logger.info({ videoUrl: props.videoUrl }, 'Initiating download');
const response = await Axios({
method: 'get',
url: props.videoUrl,
responseType: 'stream',
});
this.logger.info({ headers: response.headers }, 'Input stream HTTP headers');
return response;
}
private async getOutputStream(props: {
inputStream: AxiosResponse;
output: {
bucketName: string;
key: string;
};
}): Promise<CompleteMultipartUploadCommandOutput | AbortMultipartUploadCommandOutput> {
this.logger.info({ output: props.output }, 'Initiating upload');
const output = props.output;
const passThrough = new PassThrough();
const upload = new Upload({
client: this.client,
params: { Bucket: output.bucketName, Key: output.key, Body: passThrough },
queueSize: this.CONCURRENCY,
partSize: this.PART_SIZE,
leavePartsOnError: false,
});
props.inputStream.data.pipe(passThrough);
if (this.logger.isLevelEnabled('debug')) {
upload.on('httpUploadProgress', (progress) => {
this.logger.debug({ progress }, 'Upload progress');
});
}
return await upload.done();
}
}
This is how you can initialize it:
import { S3Client } from '#aws-sdk/client-s3';
import pino from 'pino';
const sdkClient = new S3Client({ region: 'us-west-2' });
const client = new S3HandlerClient({ logger: pino(), sdkClient });
Example dependencies:
{
...
"dependencies": {
"#aws-sdk/client-s3": "^3.100.0",
"#aws-sdk/lib-storage": "^3.100.0",
"axios": "^0.27.2",
"mime-types": "^2.1.35",
"pino": "^7.11.0",
"pino-lambda": "^4.0.0",
"streaming-s3": "^0.4.5",
},
"devDependencies": {
"#types/aws-lambda": "^8.10.97",
"#types/mime-types": "^2.1.1",
"#types/pino": "^7.0.5",
}
}

Related

I would like to send multiple images to the S3 from amazon. This is my code so far, sending just one image

I'm using TYPESCRIPT and NODEJS. In addition to sending the results to the database in POSTGRESSQL.
ROUTER.TS
router.post(
"/image",
isAuthenticated,
upload.single("file"),
async (req, res) => {
const { file } = req;
const product_id = req.query.product_id as string;
const uploadImagesService = new UploadImagesService();
await uploadImagesService.execute(file);
const createImage = await prismaClient.uploadImage.create({
data: {
url: `https://upload-joias.s3.amazonaws.com/${file.filename}`,
id: file.filename,
product_id: product_id,
},
});
return res.send(createImage);
}
);
SERVICE.TS
import S3Storage from "../../utils/S3Storage";
class UploadImagesService {
async execute(file: Express.Multer.File): Promise<void> {
const s3Storage = new S3Storage();
await s3Storage.saveFile(file.filename);
}
}
export { UploadImagesService };
S3Storage.ts
async saveFile(filename: string): Promise<void> {
const originalPath = path.resolve(uploadConfig.diretory, filename);
const contentType = mime.getType(originalPath);
if (!contentType) {
throw new Error("File not found");
}
const fileContent = await fs.promises.readFile(originalPath);
this.client
.putObject({
Bucket: "upload-joias",
Key: filename,
ACL: "public-read",
Body: fileContent,
ContentType: contentType,
})
.promise();
await fs.promises.unlink(originalPath);
}
I'm having a hard time dealing with this, I'm new to node js and typescript. I'm grateful for any help.

Problem to upload a pdf to Google Cloud storage with Node.js

I'm using PDFkit to upload a pdf but when i go to stream the pdf i received an error
This is the code of the uploadService:
import { format } from 'util';
import stream from 'stream';
import { Storage } from '#google-cloud/storage';
const storage = new Storage();
const bucket = storage.bucket(process.env.GOOGLE_STORAGE_BUCKET_NAME);
export const uploadStreamToGcs = async (data) => {
const dataStream = new stream.PassThrough();
dataStream.push(Buffer.from(data.toString(), 'base64'));
dataStream.push(null);
return new Promise((resolve, reject) => {
const blob = bucket.file('test_pdf_folder/testPDF.pdf');
const blobStream = dataStream.pipe(
blob.createWriteStream({
metadata: {
'Content-Type': 'application/pdf',
'Content-disposition': `attachment; filename=test.pdf`,
},
resumable: false,
public: true,
}),
);
blobStream
.on('finish', () => {
const publicUrl = format(
`https://storage.googleapis.com/${bucket.name}/${blob.name}`,
);
resolve(publicUrl);
})
.on('error', (error) => {
reject(error);
});
});
};
And this is the code of the controller :
import PDFDocument from 'pdfkit';
import { uploadStreamToGcs } from '../services/upload/upload.service';
import { ApplicationError } from '../errors/Application.error';
export default {
getPdfLocation: async (req, res) => {
try {
const doc = new PDFDocument();
doc.fontSize(12);
doc.text('TEST CREZIONE PDF', 10, 30, {
align: 'center',
width: 200,
});
doc.end();
const streamToGCloud = await uploadStreamToGcs(doc);
res.send({
status: 'success',
data: { streamToGCloud },
});
} catch (error) {
throw new ApplicationError(error.statusCode, error.message);
}
},
};
When I go on the route I created to create the pdf, the folder with the pdf inside is created but the pdf cannot be opened as I receive an error.
PLEASE HELP ME TO RESOLVE THIS PROBLEM.

issues with sinon testing azure containerclient.listblobsbyhierarchy

I have the following rest endpoint code "/files/lookup". This will receive a query parameter folderPath, and will return a list of files with details (including metadata) but not content.
I am including the content of the rest endpoint. This connects to azure blob storage.
#get('/files/lookup', { ... })
...
const blobServiceClient: BlobServiceClient = BlobServiceClient.fromConnectionString(
this.azureStorageConnectionString,
);
const containerClient: ContainerClient = blobServiceClient.getContainerClient(container);
const filesPropertiesList: FileProps[] = [];
try {
for await (const item of containerClient.listBlobsByHierarchy('/', {
prefix: decodedAzureFolderPath,
includeMetadata: true,
})) {
if (item.kind !== 'prefix') {
const blobitem: BlobItem = item;
const blobProperties: BlobProperties = blobitem.properties;
const blobMetadata: Record<string, string> | undefined = blobitem.metadata;
const aFileProperties: FileProps = {
name: item?.name,
uploadedDate:
blobProperties.lastModified?.toISOString() ?? blobProperties.createdOn?.toISOString(),
size: blobProperties.contentLength,
contentType: blobProperties.contentType,
metadata: blobMetadata,
};
filesPropertiesList.push(aFileProperties);
}
}
} catch (error) {
if (error.statusCode === 404) {
throw new HttpErrors.NotFound('Retrieval of list of files has failed');
}
throw error;
}
return filesPropertiesList;
I am working on sinon test. I am new to sinon. I could not get to effectively use mocks/stubs/etc. to test the endpoint returning a list of files with properties. Couldn't get my head around mocking/stubbing the listBlobsByHierarchy method of the container client
describe('GET /files/lookup', () => {
let blobServiceClientStub: sinon.SinonStubbedInstance<BlobServiceClient>;
let fromConnectionStringStub: sinon.SinonStub<[string, StoragePipelineOptions?], BlobServiceClient>;
let containerStub: sinon.SinonStubbedInstance<ContainerClient>;
beforeEach(async () => {
blobServiceClientStub = sinon.createStubInstance(BlobServiceClient);
fromConnectionStringStub = sinon
.stub(BlobServiceClient, 'fromConnectionString')
.returns((blobServiceClientStub as unknown) as BlobServiceClient);
containerStub = sinon.createStubInstance(ContainerClient);
blobServiceClientStub.getContainerClient.returns((containerStub as unknown) as ContainerClient);
});
afterEach(async () => {
fromConnectionStringStub.restore();
});
it('lookup for files from storage', async () => {
/* let items: PagedAsyncIterableIterator<({ kind: "prefix"; } & BlobPrefix) | ({ kind: "blob"; } & BlobItem), ContainerListBlobHierarchySegmentResponse>;
sinon.stub(containerStub, "listBlobsByHierarchy").withArgs('/', { prefix: "myf/entity/172/", includeMetadata: true }).returns(items);
const response = await client.get(`/files/lookup?folderpath=myf%2Fentity%2F172%2F`).expect(200); */
});
});
Since I did not find any ways to mock the return of this method with the same type, I went with type "any". As I am a novice on this, it was really challenging to get my head to do this!
it('lookup for files from storage', async () => {
/* eslint-disable #typescript-eslint/naming-convention */
const obj: any = [
{
kind: 'blob',
name: 'myf/entity/172/0670fdf8-db47-11eb-8d19-0242ac13000.docx',
properties: {
createdOn: new Date('2020-01-03T16:27:32Z'),
lastModified: new Date('2020-01-03T16:27:32Z'),
contentLength: 11980,
contentType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
},
metadata: {
object_name: 'Testing.docx',
category: 'entity',
reference: '172',
object_id: '0670fdf8-db47-11eb-8d19-0242ac13000',
},
},
];
containerStub.listBlobsByHierarchy.returns(obj);
const actualResponse = await (await client.get('/files/lookup?folderpath=myf/entity/172')).body;
const expectedResponse: any[] = [ WHATEVER ]
expect(actualResponse).deepEqual(expectedResponse);
});

i am trying to upload an image in MEAN stack application but when I click on submit button the page goes on for continous loading

here is my code
The screen just keeps loading whenever I click form submit button. No error is displayed however.Someone please help me with this. Thanks for the reply. I am using multer at backend and mime type validator for filtering mages of only jpeg,jpg and png images.
frontend:
import { Component, OnInit } from '#angular/core';
import { FormGroup, FormControl, Validators } from "#angular/forms";
import{AdminteacherService} from '../Services/adminteacher.service';
import { MatSpinner } from '#angular/material';
import { HttpClient } from "#angular/common/http";
import { mimeType } from "./mime-type.validator";
import { ActivatedRoute, ParamMap } from "#angular/router";
import {TeacherModel} from '../../../backend/model/teacher';
#Component({
selector: 'app-adminteacher',
templateUrl: './adminteacher.component.html',
styleUrls: ['./adminteacher.component.css']
})
export class AdminteacherComponent implements OnInit {
enteredName = "";
enteredSSN = "";
enteredBranch = "";
enteredPassword = "";
post: TeacherModel;
isLoading = false;
form: FormGroup;
imagePreview: string;
private mode = "create";
private postId: string;
constructor(public postsService: AdminteacherService,
public route: ActivatedRoute,
private http:HttpClient) { }
ngOnInit() {
this.form = new FormGroup({
name: new FormControl(null, {
validators: [Validators.required, Validators.minLength(3)]
}),
ssn: new FormControl(null, { validators: [Validators.required] }),
branch: new FormControl(null, { validators: [Validators.required] }),
password: new FormControl(null, { validators: [Validators.required] }),
image: new FormControl(null, {
validators: [Validators.required],
asyncValidators: [mimeType]
})
});
}
onImagePicked(event:Event) {
const file = (event.target as HTMLInputElement).files[0];
this.form.patchValue({ image: file });
this.form.get("image").updateValueAndValidity();
const reader = new FileReader();
reader.onload = () => {
this.imagePreview = reader.result as string;
};
reader.readAsDataURL(file);
}
onSavePost() {
if (this.form.invalid) {
return;
}
this.isLoading = true;
if (this.mode === "create") {
this.postsService.addPost(
this.form.value.name,
this.form.value.image,
this.form.value.ssn,
this.form.value.branch,
this.form.value.password
);
}
postForm.value.ssn,postForm.value.password,postForm.value.branch,postForm.value.image);
this.form.reset();
}
}
SERVICE FILE:
import { HttpClient } from "#angular/common/http";
import { Subject } from "rxjs";
import { map } from "rxjs/operators";
import { Router } from "#angular/router";
import {TeacherModel} from '../../../backend/model/teacher';
#Injectable({
providedIn: 'root'
})
export class AdminteacherService {
constructor(private http:HttpClient) { }
postData:TeacherModel;
addPost(name: string,image:File,ssn: string,branch:string,password:string) {
const postData=new FormData();
postData.append("name",name);
postData.append("image",image,name);
postData.append("ssn",ssn);
postData.append("branch",branch);
postData.append("password",password);
console.log("I am here");
this.http
.post<{message: string; postId: string}>(
"http://localhost:3000/admin/addteacher",
postData
)
console.log("I am not here");
}
}
BACKEND:
const express=require("express");
const multer=require("multer");
const Post=require("../model/teacher");
const router=express.Router();
const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost:27017/MyMINIPROJECT', { useNewUrlParser: true , useUnifiedTopology: true});
const MIME_TYPE_MAP={
'image/png' : 'png',
'image/jpeg' : 'jpg',
'image/jpg' : 'jpg'
};
const storage = multer.diskStorage({
destination: (req, file, cb) => {
const isValid = MIME_TYPE_MAP[file.mimetype];
let error = new Error("Invalid mime type");
if (isValid) {
error = null;
}
cb(error, "./images");
},
filename: (req, file, cb) => {
const name = file.originalname
.toLowerCase()
.split(" ")
.join("-");
const ext = MIME_TYPE_MAP[file.mimetype];
cb(null, name + "-" + Date.now() + "." + ext);
}
});
router.post('/admin/addteacher', multer({ storage: storage }).single("image"),
(req, res, next)=>{
const url = req.protocol + "://" + req.get("host");
console.log("reached backend");
const post=new Post({
name:req.body.name,
img_link: url + "/images/" + req.file.filename,
ssn:req.body.ssn,
branch:req.body.branch,
password:req.body.password
});
post.save().then(createdPost => {
res.status(201).json({
message: "Post added successfully",
post: {
...createdPost,
id: createdPost._id
}
});
});
});
module.exports = router;

Angular 5 - Node/Express - not able to download pdf

am trying to download pdf file from local folder that structures like
assets/test.pdf.
server.js
app.get('/ePoint', (req,res)=>{
// some dumb code :P
});
demo.ts
import { HttpClient, HttpHeaders } from '#angular/common/http';
import { Headers } from '#angular/http';
import {Observable} from 'rxjs';
fileDownload() {
const headers = new HttpHeaders();
headers.append('Accept', 'application/pdf');
this._http.get('http://localhost:3000/ePoint', { headers: headers })
.toPromise()
.then(response => this.saveItToClient(response));
}
private saveItToClient(response: any) {
const contentDispositionHeader: string = response.headers.get('Content-Disposition');
const parts: string[] = contentDispositionHeader.split(';');
const filename = parts[1].split('=')[1];
const blob = new Blob([response._body], { type: 'application/pdf' });
saveAs(blob, filename);
}
i dont know where i did mistake. in browser network console. its shows 200 ok. but in normal browser console shows as below attachment
Note: i referred for ts file from here
helps much appreciated
try this...
component.ts
downloadDocument(documentId: string) {
this.downloadDocumentSubscription = this.getService.downloadScannedDocument(documentId).subscribe(
data => {
this.createImageFromBlob(data);
},
error => {
console.log("no image found");
$("#errorModal").modal('show'); //show download err modal
});
}
createImageFromBlob(image: Blob) {
console.log("mylog", image);
if (window.navigator.msSaveOrOpenBlob) // IE10+
window.navigator.msSaveOrOpenBlob(image, "download." + (image.type.substr(image.type.lastIndexOf('/') + 1)));
else {
var url = window.URL.createObjectURL(image);
window.open(url);
}
}
service.ts
downloadScannedDocument(documentId: string): Observable<any> {
let params = new HttpParams();
if (documentTypeParam == false)
params = params.set('onlyActive', 'false');
let fileResult: Observable<any> = this.http.get(`${this.apiBaseUrl}/${documentId}`, { responseType: "blob", params: params });
return fileResult;
}

Resources