Download image from url by nestjs - nestjs

I want to download user profile picture from telegram account and store it in local storage with nestjs framework.

#Controller()
export class Controller {
constructor(
private readonly httpService: HttpService,
) {
}
#Get()
async downloadImage(#Res() res) {
const writer = fs.createWriteStream('./image.png');
const response = await this.httpService.axiosRef({
url: 'https://example.com/image.png',
method: 'GET',
responseType: 'stream',
});
response.data.pipe(writer);
return new Promise((resolve, reject) => {
writer.on('finish', resolve);
writer.on('error', reject);
});
}
}

Related

How to mock a Promise in Jest inside a class (not imported from a library)

I am using the AWS SDK to send emails. I have the following implementation:
import { Injectable } from '#nestjs/common';
import * as AWS from 'aws-sdk';
import { SendEmailDTO } from 'src/Messages/application/DTO/Inputs';
#Injectable()
export class MailSenderSESSDKAdapter implements MailSenderPort {
constructor() {}
async send(params: SendEmailDTO): Promise<void> {
const sendPromise = new AWS.SES(config).sendEmail(params).promise();
sendPromise
.then(function (data) {
console.log('### MailSenderSESSDKAdapter sendPromise data', data);
})
.catch(function (err) {
console.error('### err', err);
});
}
}
This line:
const sendPromise = new AWS.SES(config).sendEmail(params).promise();
returns a promise and when resolved, if successful, you get back the MessageId of the sent email.
I am trying to test it, I tried following approaches:
import { MailSenderSESSDKAdapter } from '../mailSenderSESSDK.adapter';
jest.mock('../mailSenderSESSDK.adapter', () => {
return jest.fn().mockImplementation(() => {
return { sendPromise: jest.fn() };
});
});
describe('mailSenderSESSDK adapter', () => {
let adapter: MailSenderSESSDKAdapter;
let sendPromise: any;
const mockDataResponse = {
ResponseMetadata: {
RequestId: 'ABC123',
},
MessageId: 'abc-123',
};
beforeEach(async () => {
adapter = new MailSenderSESSDKAdapter();
sendPromise = jest.fn().mockResolvedValue(Promise);
});
it.only('sends an email via SES', async () => {
const sendEmailDTO = {
subject: 'foo subject',
body: 'foo body',
from: 'from#mail.com',
to: 'to#mail.com',
};
await adapter.send(sendEmailDTO);
await expect(sendPromise).resolves.toBe(mockDataResponse);
});
});
But I don't know how to mock a method within a class method. I know how to mock the send method, but not the sendPromise promise inside the send method.
Only idea that I have would be to create a method that wraps the creation of the promise, and then mock this method, but seems overkill.
Anyone knows how to do it or best practices?

Download file from internet and send it to S3 as stream

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",
}
}

How to mock external API calls using jest?

I am working on a NodeJS app and I am having trouble understanding & mocking external API calls. Here is my code:
contactController.ts
import { APIService } from '../client/ExternalRestClient';
const url = process.env.API_URL;
const apiKey = process.env.API_KEY;
const apiSecret = process.env.API_SECRET;
const client = new APIService(url, apiKey, apiSecret);
export default class contactController{
public async getContact(id) {
const response = await client.getContactById(this.contactID);
return response;
}
}
ExternalRestClient.ts
import { RestClient } from "./RestClient";
export default class APIService extends RestClient {
private apiKey: string;
private apiSecret: string;
public constructor(
url: string,
_apiKey: string,
_apiSecret: string
) {
super(url);
this.apiKey = _apiKey;
this.apiSecret = _Secret;
}
public async getContactById(id) {
const data = await this.axiosClient.get(
`${this.url}/${id}`,
{
headers: {
client_id: this.apiKey,
client_secret: this.apiSecret,
},
}
);
return data;
}
}
RestClient.ts
import axios, { AxiosInstance, AxiosResponse } from "axios";
declare module "axios" {
interface AxiosResponse<T = any> extends Promise<T> {}
}
export abstract class RestClient {
protected readonly axiosClient: AxiosInstance;
protected readonly url: string;
constructor(url: string) {
this.url = url;
this.axiosClient = axios.create({
url,
});
this._initializeResponseInterceptor();
}
private _handleResponse = ({ data }: AxiosResponse) => data;
protected _handleError = (error: any) => Promise.reject(error);
private _initializeResponseInterceptor = () => {
this.axiosClient.interceptors.response.use(
this._handleResponse,
this._handleError
);
};
}
I am trying to write a test for contactController. I tried using jest.mock('axios') but it didn't work out. This is how I was doing it:
import contactController from "../src/controllers/contactController"
import axios from "axios";
jest.mock("axios");
describe("Test", () => {
describe("Individual ID", () => {
it("Checking information retrived", async () => {
const controller = new contactController();
const expected = {
"dataResponse": "success",
"id": "1234",
"hasMore": false
};
axios.get.mockResolvedValue(expected);
return controller.getContact("1234").then(data => expect(data).toEqual(expected));
});
});
});
Can someone please advice how can I write the test for this contoller? I am not able to grasp or figure out how should I proceed.
Thakns.
You are mocking the axios.get method, while your code is using axios.create(...).get basically.
Perhaps there is a better solution, but I would try mocking .create like this:
axios.create.mockResolvedValue({ get: () => expected })
That should mock the create method to return an instance that returns expected result upon calling .get on it.
P.S.: I didn't try it myself, so perhaps you also have to add proper methods to the mock to make sure that this snippet doesn't fail with trying to access some method on undefined:
private _initializeResponseInterceptor = () => {
this.axiosClient.interceptors.response.use(
this._handleResponse,
this._handleError
);
};

How to send buffer in a HTTP request

Im using NodeJS/Angular.
I wanted to render a document using Carbone in NodeJS, and send the result in a http request.
Myfile.js:
router.get('/executeFusion/', async (req, res) => {
try {
// Data to inject
const data = {
firstname: 'BLB',
lastname: 'MAR'
};
carbone.render('./node_modules/carbone/examples/simple.odt', data, async function(err, result) {
if (err) {
return console.log(err);
}
});
const file = './result.odt';
res.download(file);
}
catch (err) {
errorDbHandler.sendErrorHttp(err, res);
}
});
MyComponent.ts:
this.fusionService.executeFusion()
.subscribe(data => {console.log(data)},
(error) => console.log(error));
MyService.ts :
export class FusionService {
constructor(private httpClient: HttpClient, private configService: ConfigService) { }
executeFusion() {
return this.httpClient.get<any>(`${this.configService.getUrlApi()}/api/Fusion/executeFusion/`);
}
}
But the, I got this error:
The main idea here is, generating a document in NodeJS, send it to Angular in order to download it.
The error is related to promise which is mentioned in the question:
this.fusionService.executeFusion().toPromise().then(res => {
console.log(res);
}).catch(e) {
console.log(e);
}
But when you request the Http request in angular, you should use httpClient to make it and it returns the observable like this:
this.fusionService.executeFusion()
.subscribe(data => {console.log(data)},
(error) =>. console.log(error));
And, to make the express server send the file as a downloaded one:
res.download(file);
xport class FusionService {
constructor(private httpClient: HttpClient, private configService: ConfigService) { }
executeFusion() {
return this.httpClient.get<any>(`${this.configService.getUrlApi()}/api/Fusion/executeFusion/`, {response: 'application/txt'}); // type should match the type you are sending from API
}
}
for more details check here.

User is not available in request with NestJS passport strategy (other than documentation exemples)

I'm trying to implement a passport strategy (passport-headerapikey), I was able to make it work and I can secure my routes.
But the request is empty and cannot access the logged in user ?
import { HeaderAPIKeyStrategy } from "passport-headerapikey";
import { PassportStrategy } from "#nestjs/passport";
import { Injectable, NotFoundException } from "#nestjs/common";
import { CompanyService } from "../../companies/companies.service";
#Injectable()
export class ApiKeyStrategy extends PassportStrategy(HeaderAPIKeyStrategy, "api-key") {
constructor(private readonly companyService: CompanyService) {
super(
{
header: "Authorization",
prefix: "Api-Key "
},
true,
async (apiKey, done) => {
return this.validate(apiKey, done);
}
);
}
public async validate(apiKey: string, done: (error: Error, data) => {}) {
const company = await this.companyService.findByApiKey(apiKey);
if (company === null) {
throw new NotFoundException("Company not found");
}
return company;
}
}
#UseGuards(AuthGuard("api-key"))
export class CompaniesController {
constructor(private companyService: CompanyService) {}
#Get()
#ApiOperation({ title: "Get company information" })
public getCompany(#Request() req) {
// here request is empty, so i cannot access the user..
console.log("request", req);
return [];
}
}
Thanks for your help !
To access the logged user, you can inject the object in the request. To do that, in your ApiKeyStrategy constructor, change the third parameter to something like this:
async (apiKey, verified, req) => {
const user = await this.findUser(apiKey);
// inject the user in the request
req.user = user || null;
return verified(null, user || false);
}
Now, you can access the logged user:
getCompany(#Request() req) {
console.log(req.user);
}
I hope that could help you.
As show in the documentation you should do some works to get the current user : here the documetation
First of all in the app.module make sure that the context is set :
context: ({ req }) => ({ req })
Then you can add this in the controller/resolver, this example use the Gql (GraphQL):
export const CurrentUser = createParamDecorator(
(data: unknown, context: ExecutionContext) => {
const ctx = GqlExecutionContext.create(context);
return ctx.getContext().req.user;
},
);
if this one doesnt work for you try this one instead :
export const CurrentUser = createParamDecorator(
(data: unknown, context: ExecutionContext) => {
const ctx = GqlExecutionContext.create(context);
const request = ctx.getContext();
request.body = ctx.getArgs();
return request.user;
},
);
Modify your validate method like so:
public async validate(apiKey: string, done: (error: Error, data) => {}) {
const company = await this.companyService.findByApiKey(apiKey);
if (company === null) {
return done(new NotFoundException("Company not found"), null);
}
return done(null, company);
}

Resources