fix: TS types

This commit is contained in:
Ahmed Bouhuolia
2024-05-30 17:47:27 +02:00
parent 308a4f62ae
commit 6a6dcadaf9
9 changed files with 112 additions and 42 deletions

View File

@@ -25,6 +25,7 @@
"@casl/ability": "^5.4.3", "@casl/ability": "^5.4.3",
"@hapi/boom": "^7.4.3", "@hapi/boom": "^7.4.3",
"@lemonsqueezy/lemonsqueezy.js": "^2.2.0", "@lemonsqueezy/lemonsqueezy.js": "^2.2.0",
"@types/express": "^4.17.21",
"@types/i18n": "^0.8.7", "@types/i18n": "^0.8.7",
"@types/knex": "^0.16.1", "@types/knex": "^0.16.1",
"@types/mathjs": "^6.0.12", "@types/mathjs": "^6.0.12",

View File

@@ -1,9 +1,8 @@
import mime from 'mime-types'; import mime from 'mime-types';
import { Service, Inject } from 'typedi'; import { Service, Inject } from 'typedi';
import { Router, Response } from 'express'; import { Router, Response, NextFunction, Request } from 'express';
import { body, param } from 'express-validator'; import { body, param } from 'express-validator';
import BaseController from '@/api/controllers/BaseController'; import BaseController from '@/api/controllers/BaseController';
import { Request } from 'express-validator/src/base';
import { AttachmentsApplication } from '@/services/Attachments/AttachmentsApplication'; import { AttachmentsApplication } from '@/services/Attachments/AttachmentsApplication';
@Service() @Service()
@@ -20,6 +19,7 @@ export class AttachmentsController extends BaseController {
router.post( router.post(
'/', '/',
this.attachmentsApplication.uploadPipeline.single('file'), this.attachmentsApplication.uploadPipeline.single('file'),
this.validateUploadedFileExistance,
this.uploadAttachment.bind(this) this.uploadAttachment.bind(this)
); );
router.delete( router.delete(
@@ -57,18 +57,42 @@ export class AttachmentsController extends BaseController {
this.validationResult, this.validationResult,
this.getAttachmentPresignedUrl.bind(this) this.getAttachmentPresignedUrl.bind(this)
); );
return router; 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. * Uploads the attachments to S3 and store the file metadata to DB.
* @param {Request} req * @param {Request} req
* @param {Response} res * @param {Response} res
* @param {Function} next * @param {NextFunction} next
* @returns * @returns {Response|void}
*/ */
private async uploadAttachment(req: Request, res: Response, next: Function) { private async uploadAttachment(
req: Request,
res: Response,
next: NextFunction
): Promise<Response | void> {
const { tenantId } = req; const { tenantId } = req;
const file = req.file; const file = req.file;
@@ -86,12 +110,17 @@ export class AttachmentsController extends BaseController {
} }
/** /**
* * Retrieves the given attachment key.
* @param {Request} req * @param {Request} req
* @param {Response} res * @param {Response} res
* @param next * @param {NextFunction} next
* @returns {Promise<Response|void>}
*/ */
private async getAttachment(req: Request, res: Response, next: Function) { private async getAttachment(
req: Request,
res: Response,
next: NextFunction
): Promise<Response | void> {
const { tenantId } = req; const { tenantId } = req;
const { id } = req.params; const { id } = req.params;
@@ -118,9 +147,13 @@ export class AttachmentsController extends BaseController {
* @param {Request} req * @param {Request} req
* @param {Response} res * @param {Response} res
* @param {NextFunction} next * @param {NextFunction} next
* @returns * @returns {Promise<Response|void>}
*/ */
private async deleteAttachment(req: Request, res: Response, next: Function) { private async deleteAttachment(
req: Request,
res: Response,
next: NextFunction
): Promise<Response | void> {
const { tenantId } = req; const { tenantId } = req;
const { id: documentId } = req.params; const { id: documentId } = req.params;
@@ -141,9 +174,13 @@ export class AttachmentsController extends BaseController {
* @param {Request} req * @param {Request} req
* @param {Response} res * @param {Response} res
* @param {NextFunction} next * @param {NextFunction} next
* @returns * @returns {Promise<Response|void>}
*/ */
private async linkDocument(req: Request, res: Response, next: Function) { private async linkDocument(
req: Request,
res: Response,
next: Function
): Promise<Response | void> {
const { tenantId } = req; const { tenantId } = req;
const { id: documentId } = req.params; const { id: documentId } = req.params;
const { modelRef, modelId } = this.matchedBodyData(req); const { modelRef, modelId } = this.matchedBodyData(req);
@@ -169,9 +206,13 @@ export class AttachmentsController extends BaseController {
* @param {Request} req * @param {Request} req
* @param {Response} res * @param {Response} res
* @param {NextFunction} next * @param {NextFunction} next
* @returns * @returns {Promise<Response|void>}
*/ */
private async unlinkDocument(req: Request, res: Response, next: Function) { private async unlinkDocument(
req: Request,
res: Response,
next: NextFunction
): Promise<Response | void> {
const { tenantId } = req; const { tenantId } = req;
const { id: documentId } = req.params; const { id: documentId } = req.params;
const { modelRef, modelId } = this.matchedBodyData(req); const { modelRef, modelId } = this.matchedBodyData(req);
@@ -196,13 +237,14 @@ export class AttachmentsController extends BaseController {
* Retreives the presigned url of the given attachment key. * Retreives the presigned url of the given attachment key.
* @param {Request} req * @param {Request} req
* @param {Response} res * @param {Response} res
* @param next * @param {NextFunction} next
* @returns {Promise<Response|void>}
*/ */
private async getAttachmentPresignedUrl( private async getAttachmentPresignedUrl(
req: Request, req: Request,
res: Response, res: Response,
next: any next: NextFunction
) { ): Promise<Response | void> {
const { id: documentKey } = req.params; const { id: documentKey } = req.params;
try { try {

View File

@@ -1,10 +1,11 @@
import { S3Client } from '@aws-sdk/client-s3'; import { S3Client } from '@aws-sdk/client-s3';
import config from '@/config';
export const s3 = new S3Client({ export const s3 = new S3Client({
region: process.env.AWS_REGION, region: config.s3.region,
credentials: { credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID as string, accessKeyId: config.s3.accessKeyId,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY as string, secretAccessKey: config.s3.secretAccessKey,
}, },
endpoint: process.env.AWS_ENDPOINT, endpoint: config.s3.endpoint,
}); });

View File

@@ -6,6 +6,7 @@ import { AttachmentUploadPipeline } from './S3UploadPipeline';
import { LinkAttachment } from './LinkAttachment'; import { LinkAttachment } from './LinkAttachment';
import { UnlinkAttachment } from './UnlinkAttachment'; import { UnlinkAttachment } from './UnlinkAttachment';
import { getAttachmentPresignedUrl } from './GetAttachmentPresignedUrl'; import { getAttachmentPresignedUrl } from './GetAttachmentPresignedUrl';
import type { Multer } from 'multer';
@Service() @Service()
export class AttachmentsApplication { export class AttachmentsApplication {
@@ -31,18 +32,18 @@ export class AttachmentsApplication {
private getPresignedUrlService: getAttachmentPresignedUrl; private getPresignedUrlService: getAttachmentPresignedUrl;
/** /**
* * Express middleware for uploading attachments to an S3 bucket.
* @returns * @returns {Multer}
*/ */
get uploadPipeline() { get uploadPipeline(): Multer {
return this.uploadPipelineService.uploadPipeline(); return this.uploadPipelineService.uploadPipeline();
} }
/** /**
* Uploads * Saves the metadata of uploaded document to S3 on database.
* @param {number} tenantId * @param {number} tenantId
* @param {} file * @param {} file
* @returns * @returns {Promise<Document>}
*/ */
public upload(tenantId: number, file: any) { public upload(tenantId: number, file: any) {
return this.uploadDocumentService.upload(tenantId, file); return this.uploadDocumentService.upload(tenantId, file);

View File

@@ -1,15 +1,21 @@
import multer from 'multer'; import multer from 'multer';
import type { Multer } from 'multer'
import multerS3 from 'multer-s3'; import multerS3 from 'multer-s3';
import { s3 } from '@/lib/S3/S3'; import { s3 } from '@/lib/S3/S3';
import { Service } from 'typedi'; import { Service } from 'typedi';
import config from '@/config';
@Service() @Service()
export class AttachmentUploadPipeline { 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({ return multer({
storage: multerS3({ storage: multerS3({
s3, s3,
bucket: process.env.AWS_BUCKET, bucket: config.s3.bucket,
contentType: multerS3.AUTO_CONTENT_TYPE, contentType: multerS3.AUTO_CONTENT_TYPE,
metadata: function (req, file, cb) { metadata: function (req, file, cb) {
cb(null, { fieldName: file.fieldname }); cb(null, { fieldName: file.fieldname });

View File

@@ -12,9 +12,9 @@ import {
useGetPresignedUrlAttachment, useGetPresignedUrlAttachment,
useUploadAttachments, useUploadAttachments,
} from '@/hooks/query/attachments'; } from '@/hooks/query/attachments';
import { formatBytes } from '../Sales/Invoices/InvoiceForm/utils';
import styles from './UploadAttachmentPopoverContent.module.scss'; import styles from './UploadAttachmentPopoverContent.module.scss';
import { MIME_TYPES } from '@/components/Dropzone/mine-types'; import { MIME_TYPES } from '@/components/Dropzone/mine-types';
import { formatBytes } from '@/utils/format-bytes';
interface AttachmentFileCommon { interface AttachmentFileCommon {
originName: string; originName: string;

View File

@@ -415,15 +415,3 @@ export const useIsInvoiceTaxExclusive = () => {
return values.inclusive_exclusive_tax === TaxType.Exclusive; 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];
}

View File

@@ -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];
}

3
pnpm-lock.yaml generated
View File

@@ -50,6 +50,9 @@ importers:
'@lemonsqueezy/lemonsqueezy.js': '@lemonsqueezy/lemonsqueezy.js':
specifier: ^2.2.0 specifier: ^2.2.0
version: 2.2.0 version: 2.2.0
'@types/express':
specifier: ^4.17.21
version: 4.17.21
'@types/i18n': '@types/i18n':
specifier: ^0.8.7 specifier: ^0.8.7
version: 0.8.8 version: 0.8.8