From 6a6dcadaf95479877983d62f0c74a29ea4785ee5 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Thu, 30 May 2024 17:47:27 +0200 Subject: [PATCH] fix: TS types --- packages/server/package.json | 1 + .../Attachments/AttachmentsController.ts | 78 ++++++++++++++----- packages/server/src/lib/S3/S3.ts | 9 ++- .../Attachments/AttachmentsApplication.ts | 11 +-- .../services/Attachments/S3UploadPipeline.ts | 10 ++- .../UploadAttachmentsPopoverContent.tsx | 2 +- .../Sales/Invoices/InvoiceForm/utils.tsx | 12 --- packages/webapp/src/utils/format-bytes.ts | 28 +++++++ pnpm-lock.yaml | 3 + 9 files changed, 112 insertions(+), 42 deletions(-) create mode 100644 packages/webapp/src/utils/format-bytes.ts diff --git a/packages/server/package.json b/packages/server/package.json index f651216c1..ef6682eca 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -25,6 +25,7 @@ "@casl/ability": "^5.4.3", "@hapi/boom": "^7.4.3", "@lemonsqueezy/lemonsqueezy.js": "^2.2.0", + "@types/express": "^4.17.21", "@types/i18n": "^0.8.7", "@types/knex": "^0.16.1", "@types/mathjs": "^6.0.12", diff --git a/packages/server/src/api/controllers/Attachments/AttachmentsController.ts b/packages/server/src/api/controllers/Attachments/AttachmentsController.ts index 9a4075a68..ba41db541 100644 --- a/packages/server/src/api/controllers/Attachments/AttachmentsController.ts +++ b/packages/server/src/api/controllers/Attachments/AttachmentsController.ts @@ -1,9 +1,8 @@ import mime from 'mime-types'; import { Service, Inject } from 'typedi'; -import { Router, Response } from 'express'; +import { Router, Response, NextFunction, Request } from 'express'; import { body, param } from 'express-validator'; import BaseController from '@/api/controllers/BaseController'; -import { Request } from 'express-validator/src/base'; import { AttachmentsApplication } from '@/services/Attachments/AttachmentsApplication'; @Service() @@ -20,6 +19,7 @@ export class AttachmentsController extends BaseController { router.post( '/', this.attachmentsApplication.uploadPipeline.single('file'), + this.validateUploadedFileExistance, this.uploadAttachment.bind(this) ); router.delete( @@ -57,18 +57,42 @@ export class AttachmentsController extends BaseController { this.validationResult, this.getAttachmentPresignedUrl.bind(this) ); - return router; } + /** + * Validates the upload file existance. + * @param {Request} req + * @param {Response} res + * @param {NextFunction} next + * @returns {Response|void} + */ + private validateUploadedFileExistance( + req: Request, + res: Response, + next: NextFunction + ) { + if (!req.file) { + return res.boom.badRequest(null, { + errorType: 'FILE_UPLOAD_FAILED', + message: 'Now file uploaded.', + }); + } + next(); + } + /** * Uploads the attachments to S3 and store the file metadata to DB. * @param {Request} req * @param {Response} res - * @param {Function} next - * @returns + * @param {NextFunction} next + * @returns {Response|void} */ - private async uploadAttachment(req: Request, res: Response, next: Function) { + private async uploadAttachment( + req: Request, + res: Response, + next: NextFunction + ): Promise { const { tenantId } = req; const file = req.file; @@ -86,12 +110,17 @@ export class AttachmentsController extends BaseController { } /** - * + * Retrieves the given attachment key. * @param {Request} req * @param {Response} res - * @param next + * @param {NextFunction} next + * @returns {Promise} */ - private async getAttachment(req: Request, res: Response, next: Function) { + private async getAttachment( + req: Request, + res: Response, + next: NextFunction + ): Promise { const { tenantId } = req; const { id } = req.params; @@ -118,9 +147,13 @@ export class AttachmentsController extends BaseController { * @param {Request} req * @param {Response} res * @param {NextFunction} next - * @returns + * @returns {Promise} */ - private async deleteAttachment(req: Request, res: Response, next: Function) { + private async deleteAttachment( + req: Request, + res: Response, + next: NextFunction + ): Promise { const { tenantId } = req; const { id: documentId } = req.params; @@ -141,9 +174,13 @@ export class AttachmentsController extends BaseController { * @param {Request} req * @param {Response} res * @param {NextFunction} next - * @returns + * @returns {Promise} */ - private async linkDocument(req: Request, res: Response, next: Function) { + private async linkDocument( + req: Request, + res: Response, + next: Function + ): Promise { const { tenantId } = req; const { id: documentId } = req.params; const { modelRef, modelId } = this.matchedBodyData(req); @@ -169,9 +206,13 @@ export class AttachmentsController extends BaseController { * @param {Request} req * @param {Response} res * @param {NextFunction} next - * @returns + * @returns {Promise} */ - private async unlinkDocument(req: Request, res: Response, next: Function) { + private async unlinkDocument( + req: Request, + res: Response, + next: NextFunction + ): Promise { const { tenantId } = req; const { id: documentId } = req.params; const { modelRef, modelId } = this.matchedBodyData(req); @@ -196,13 +237,14 @@ export class AttachmentsController extends BaseController { * Retreives the presigned url of the given attachment key. * @param {Request} req * @param {Response} res - * @param next + * @param {NextFunction} next + * @returns {Promise} */ private async getAttachmentPresignedUrl( req: Request, res: Response, - next: any - ) { + next: NextFunction + ): Promise { const { id: documentKey } = req.params; try { diff --git a/packages/server/src/lib/S3/S3.ts b/packages/server/src/lib/S3/S3.ts index e590a4927..2b81a50df 100644 --- a/packages/server/src/lib/S3/S3.ts +++ b/packages/server/src/lib/S3/S3.ts @@ -1,10 +1,11 @@ import { S3Client } from '@aws-sdk/client-s3'; +import config from '@/config'; export const s3 = new S3Client({ - region: process.env.AWS_REGION, + region: config.s3.region, credentials: { - accessKeyId: process.env.AWS_ACCESS_KEY_ID as string, - secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY as string, + accessKeyId: config.s3.accessKeyId, + secretAccessKey: config.s3.secretAccessKey, }, - endpoint: process.env.AWS_ENDPOINT, + endpoint: config.s3.endpoint, }); diff --git a/packages/server/src/services/Attachments/AttachmentsApplication.ts b/packages/server/src/services/Attachments/AttachmentsApplication.ts index ff91c8aff..ce297d3ba 100644 --- a/packages/server/src/services/Attachments/AttachmentsApplication.ts +++ b/packages/server/src/services/Attachments/AttachmentsApplication.ts @@ -6,6 +6,7 @@ import { AttachmentUploadPipeline } from './S3UploadPipeline'; import { LinkAttachment } from './LinkAttachment'; import { UnlinkAttachment } from './UnlinkAttachment'; import { getAttachmentPresignedUrl } from './GetAttachmentPresignedUrl'; +import type { Multer } from 'multer'; @Service() export class AttachmentsApplication { @@ -31,18 +32,18 @@ export class AttachmentsApplication { private getPresignedUrlService: getAttachmentPresignedUrl; /** - * - * @returns + * Express middleware for uploading attachments to an S3 bucket. + * @returns {Multer} */ - get uploadPipeline() { + get uploadPipeline(): Multer { return this.uploadPipelineService.uploadPipeline(); } /** - * Uploads + * Saves the metadata of uploaded document to S3 on database. * @param {number} tenantId * @param {} file - * @returns + * @returns {Promise} */ public upload(tenantId: number, file: any) { return this.uploadDocumentService.upload(tenantId, file); diff --git a/packages/server/src/services/Attachments/S3UploadPipeline.ts b/packages/server/src/services/Attachments/S3UploadPipeline.ts index 7371fc6f2..c79d2855f 100644 --- a/packages/server/src/services/Attachments/S3UploadPipeline.ts +++ b/packages/server/src/services/Attachments/S3UploadPipeline.ts @@ -1,15 +1,21 @@ import multer from 'multer'; +import type { Multer } from 'multer' import multerS3 from 'multer-s3'; import { s3 } from '@/lib/S3/S3'; import { Service } from 'typedi'; +import config from '@/config'; @Service() export class AttachmentUploadPipeline { - uploadPipeline() { + /** + * Express middleware for uploading attachments to an S3 bucket. + * It utilizes the multer middleware for handling multipart/form-data, specifically for file uploads. + */ + public uploadPipeline(): Multer { return multer({ storage: multerS3({ s3, - bucket: process.env.AWS_BUCKET, + bucket: config.s3.bucket, contentType: multerS3.AUTO_CONTENT_TYPE, metadata: function (req, file, cb) { cb(null, { fieldName: file.fieldname }); diff --git a/packages/webapp/src/containers/Attachments/UploadAttachmentsPopoverContent.tsx b/packages/webapp/src/containers/Attachments/UploadAttachmentsPopoverContent.tsx index 25a4c5222..d7a5b8c1e 100644 --- a/packages/webapp/src/containers/Attachments/UploadAttachmentsPopoverContent.tsx +++ b/packages/webapp/src/containers/Attachments/UploadAttachmentsPopoverContent.tsx @@ -12,9 +12,9 @@ import { useGetPresignedUrlAttachment, useUploadAttachments, } from '@/hooks/query/attachments'; -import { formatBytes } from '../Sales/Invoices/InvoiceForm/utils'; import styles from './UploadAttachmentPopoverContent.module.scss'; import { MIME_TYPES } from '@/components/Dropzone/mine-types'; +import { formatBytes } from '@/utils/format-bytes'; interface AttachmentFileCommon { originName: string; diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/utils.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/utils.tsx index 62377a21a..75449be09 100644 --- a/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/utils.tsx +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/utils.tsx @@ -415,15 +415,3 @@ export const useIsInvoiceTaxExclusive = () => { return values.inclusive_exclusive_tax === TaxType.Exclusive; }; - -export function formatBytes(bytes, decimals = 2) { - if (bytes === 0) return '0 Bytes'; - - const k = 1024; - const dm = decimals < 0 ? 0 : decimals; - const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; - - const i = Math.floor(Math.log(bytes) / Math.log(k)); - - return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]; -} diff --git a/packages/webapp/src/utils/format-bytes.ts b/packages/webapp/src/utils/format-bytes.ts new file mode 100644 index 000000000..9823753db --- /dev/null +++ b/packages/webapp/src/utils/format-bytes.ts @@ -0,0 +1,28 @@ +/** + * Converts a number of bytes into a human-readable string with units. + * The function takes the number of bytes and an optional number of decimal places, + * then calculates the appropriate unit (Bytes, KB, MB, etc.) and formats the number. + * @param {number} bytes - The number of bytes to format. + * @param {number} decimals - The number of decimal places to include in the formatted string. + * @returns {string} The formatted string with the appropriate unit. + */ +export function formatBytes(bytes: number, decimals: number = 2): string { + if (bytes === 0) return '0 Bytes'; + + const k: number = 1024; + const dm: number = decimals < 0 ? 0 : decimals; + const sizes: string[] = [ + 'Bytes', + 'KB', + 'MB', + 'GB', + 'TB', + 'PB', + 'EB', + 'ZB', + 'YB', + ]; + const i: number = Math.floor(Math.log(bytes) / Math.log(k)); + + return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]; +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 950ebc05a..d770b0795 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -50,6 +50,9 @@ importers: '@lemonsqueezy/lemonsqueezy.js': specifier: ^2.2.0 version: 2.2.0 + '@types/express': + specifier: ^4.17.21 + version: 4.17.21 '@types/i18n': specifier: ^0.8.7 version: 0.8.8