From b1cff287a4800a653fa7b8f9a3ff582e18aa403e Mon Sep 17 00:00:00 2001 From: Konstantine Datunishvili <68782786+KostaD02@users.noreply.github.com> Date: Wed, 11 Oct 2023 15:00:16 +0400 Subject: [PATCH] refactor: qrcode (#84) * refactor: dtos validation * refactor: qrcode --- src/consts/api.config.ts | 1 + src/enums/exceptions.enum.ts | 4 ++ .../qrcode/dtos/generate-qr-image.dto.ts | 7 ++- src/modules/qrcode/dtos/generate-qr.dto.ts | 6 +- src/modules/qrcode/qrcode.controller.ts | 2 +- src/modules/qrcode/qrcode.module.ts | 3 +- src/modules/qrcode/qrcode.service.ts | 60 +++++++++++++++---- 7 files changed, 68 insertions(+), 15 deletions(-) diff --git a/src/consts/api.config.ts b/src/consts/api.config.ts index 530eaf3..75320cd 100644 --- a/src/consts/api.config.ts +++ b/src/consts/api.config.ts @@ -16,4 +16,5 @@ export const API_CONFIG = { MIN_LASTNAME_LENGTH: 2, MIN_PASSWORD_LENGTH: 8, MAX_PASSWORD_LENGTH: 30, + MIN_QRCODE_TEXT_LENGTH: 1, }; diff --git a/src/enums/exceptions.enum.ts b/src/enums/exceptions.enum.ts index 8fb02d3..05d9567 100644 --- a/src/enums/exceptions.enum.ts +++ b/src/enums/exceptions.enum.ts @@ -111,6 +111,10 @@ export enum CartExpectionKeys { export enum QRCodeExpectionKeys { TextShouldNotBeEmpty = 'errors.text_should_not_be_empty', ImageUrlShouldNotBeEmpty = 'errors.image_url_should_not_be_empty', + TextShouldHaveValue = 'errors.text_should_have_value', + InvalidImageURL = 'errors.invalid_image_url', + InvalidImage = 'errors.invalid_image', + NotConvertable = 'errors.not_convertable_text_to_qr', } export enum QuoteExpectionKeys { diff --git a/src/modules/qrcode/dtos/generate-qr-image.dto.ts b/src/modules/qrcode/dtos/generate-qr-image.dto.ts index 2517c08..402a0ba 100644 --- a/src/modules/qrcode/dtos/generate-qr-image.dto.ts +++ b/src/modules/qrcode/dtos/generate-qr-image.dto.ts @@ -1,10 +1,15 @@ -import { IsString } from 'class-validator'; +import { IsString, IsUrl, MinLength } from 'class-validator'; +import { API_CONFIG } from 'src/consts'; import { QRCodeExpectionKeys } from 'src/enums'; export class GenerateQrCodeWithImageDto { @IsString({ message: QRCodeExpectionKeys.TextShouldNotBeEmpty }) + @MinLength(API_CONFIG.MIN_QRCODE_TEXT_LENGTH, { + message: QRCodeExpectionKeys.TextShouldHaveValue, + }) text: string; @IsString({ message: QRCodeExpectionKeys.ImageUrlShouldNotBeEmpty }) + @IsUrl({}, { message: QRCodeExpectionKeys.InvalidImageURL }) imageURL: string; } diff --git a/src/modules/qrcode/dtos/generate-qr.dto.ts b/src/modules/qrcode/dtos/generate-qr.dto.ts index df70324..e2c0117 100644 --- a/src/modules/qrcode/dtos/generate-qr.dto.ts +++ b/src/modules/qrcode/dtos/generate-qr.dto.ts @@ -1,7 +1,11 @@ -import { IsString } from 'class-validator'; +import { IsString, MinLength } from 'class-validator'; +import { API_CONFIG } from 'src/consts'; import { QRCodeExpectionKeys } from 'src/enums'; export class GenerateQrCodeDto { @IsString({ message: QRCodeExpectionKeys.TextShouldNotBeEmpty }) + @MinLength(API_CONFIG.MIN_QRCODE_TEXT_LENGTH, { + message: QRCodeExpectionKeys.TextShouldHaveValue, + }) text: string; } diff --git a/src/modules/qrcode/qrcode.controller.ts b/src/modules/qrcode/qrcode.controller.ts index 05d74e4..149aacc 100644 --- a/src/modules/qrcode/qrcode.controller.ts +++ b/src/modules/qrcode/qrcode.controller.ts @@ -12,7 +12,7 @@ export class QrCodeController { generateDefaultQrCode() { return this.qrCodeService.generateQRCodeWithImage( 'https://everrest.educata.dev', - this.qrCodeService.basePath, + 'https://github.com/educata/everrest/blob/main/docs/public/logo.png?raw=true', ); } diff --git a/src/modules/qrcode/qrcode.module.ts b/src/modules/qrcode/qrcode.module.ts index f70ed97..5ff3030 100644 --- a/src/modules/qrcode/qrcode.module.ts +++ b/src/modules/qrcode/qrcode.module.ts @@ -1,10 +1,11 @@ import { Module } from '@nestjs/common'; import { QrCodeController } from './qrcode.controller'; import { QrCodeService } from './qrcode.service'; +import { ExceptionService } from 'src/shared'; @Module({ imports: [], - providers: [QrCodeService], + providers: [QrCodeService, ExceptionService], controllers: [QrCodeController], }) export class QrCodeModule {} diff --git a/src/modules/qrcode/qrcode.service.ts b/src/modules/qrcode/qrcode.service.ts index f6319ed..fe0812e 100644 --- a/src/modules/qrcode/qrcode.service.ts +++ b/src/modules/qrcode/qrcode.service.ts @@ -1,27 +1,43 @@ +import { ExceptionService } from 'src/shared'; import { Injectable } from '@nestjs/common'; import { createCanvas, loadImage } from 'canvas'; import * as QRCode from 'qrcode'; +import { ExceptionStatusKeys, QRCodeExpectionKeys } from 'src/enums'; @Injectable() export class QrCodeService { - readonly basePath = 'assets/images/educata-bg-white.png'; + constructor(private exceptionService: ExceptionService) {} generateQrCode(text: string) { - return new Promise((resolve, reject) => { + return new Promise((resolve) => { QRCode.toDataURL(text, (err, url) => { if (err) { - reject(err); + this.exceptionService.throwError( + ExceptionStatusKeys.BadRequest, + err.message, + QRCodeExpectionKeys.NotConvertable, + ); } - resolve({ result: url }); + resolve({ + text, + type: 'png', + format: 'base64', + errorCorrectionLevel: 'M', + result: url, + }); }); }); } - async generateQRCodeWithImage( - value: string, - imageSrc: string, - ): Promise { - const qrCode = QRCode.create(value, { errorCorrectionLevel: 'H' }); + async generateQRCodeWithImage(value: string, imageSrc: string) { + await this.checkIfImageIsValid(imageSrc).catch(() => { + this.exceptionService.throwError( + ExceptionStatusKeys.BadRequest, + 'Invalid image', + QRCodeExpectionKeys.InvalidImage, + ); + }); + const qrCode = QRCode.create(value, { errorCorrectionLevel: 'M' }); const canvas = createCanvas( qrCode.modules.size * 8, qrCode.modules.size * 8, @@ -30,7 +46,7 @@ export class QrCodeService { QRCode.toCanvas( canvas, value, - { errorCorrectionLevel: 'H', margin: 0 }, + { errorCorrectionLevel: 'M', margin: 0 }, (error) => error ?? '', ); @@ -59,6 +75,28 @@ export class QrCodeService { imageHeight, ); - return canvas.toDataURL(); + return { + text: value, + type: 'png', + format: 'base64', + errorCorrectionLevel: 'M', + result: canvas.toDataURL(), + }; + } + + checkIfImageIsValid(url: string) { + return new Promise((resolve, reject) => { + fetch(url) + .then((response) => { + if (response.ok) { + const currentContentType = response.headers.get('Content-Type'); + if (currentContentType.indexOf('image') > -1) { + return resolve(url); + } + } + reject(false); + }) + .catch(reject); + }); } }