From ceb133e29a755d89c9c6320ada4c96aa7478783e Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Wed, 29 May 2024 16:16:08 +0200 Subject: [PATCH] feat: getting presigned url of the uploaded attachment --- packages/server/package.json | 1 + .../Attachments/AttachmentsController.ts | 29 ++++++++++ .../20231108170207_create_documents_table.js | 4 +- .../Attachments/AttachmentsApplication.ts | 13 +++++ .../Attachments/GetAttachmentPresignedUrl.ts | 27 +++++++++ .../services/Attachments/UploadDocument.ts | 10 +++- pnpm-lock.yaml | 57 ++++++++++++++----- 7 files changed, 124 insertions(+), 17 deletions(-) create mode 100644 packages/server/src/services/Attachments/GetAttachmentPresignedUrl.ts diff --git a/packages/server/package.json b/packages/server/package.json index 3af0b1e3b..f651216c1 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -21,6 +21,7 @@ }, "dependencies": { "@aws-sdk/client-s3": "^3.576.0", + "@aws-sdk/s3-request-presigner": "^3.583.0", "@casl/ability": "^5.4.3", "@hapi/boom": "^7.4.3", "@lemonsqueezy/lemonsqueezy.js": "^2.2.0", diff --git a/packages/server/src/api/controllers/Attachments/AttachmentsController.ts b/packages/server/src/api/controllers/Attachments/AttachmentsController.ts index 55150945d..9a4075a68 100644 --- a/packages/server/src/api/controllers/Attachments/AttachmentsController.ts +++ b/packages/server/src/api/controllers/Attachments/AttachmentsController.ts @@ -51,6 +51,12 @@ export class AttachmentsController extends BaseController { this.validationResult, this.unlinkDocument.bind(this) ); + router.get( + '/:id/presigned-url', + [param('id').exists()], + this.validationResult, + this.getAttachmentPresignedUrl.bind(this) + ); return router; } @@ -185,4 +191,27 @@ export class AttachmentsController extends BaseController { next(error); } } + + /** + * Retreives the presigned url of the given attachment key. + * @param {Request} req + * @param {Response} res + * @param next + */ + private async getAttachmentPresignedUrl( + req: Request, + res: Response, + next: any + ) { + const { id: documentKey } = req.params; + + try { + const presignedUrl = await this.attachmentsApplication.getPresignedUrl( + documentKey + ); + return res.status(200).send({ presignedUrl }); + } catch (error) { + next(error); + } + } } diff --git a/packages/server/src/database/migrations/20231108170207_create_documents_table.js b/packages/server/src/database/migrations/20231108170207_create_documents_table.js index b3f321cb4..8ae0cc542 100644 --- a/packages/server/src/database/migrations/20231108170207_create_documents_table.js +++ b/packages/server/src/database/migrations/20231108170207_create_documents_table.js @@ -2,7 +2,9 @@ exports.up = function (knex) { return knex.schema.createTable('documents', (table) => { table.increments('id').primary(); table.string('key').notNullable(); - table.string('extension').notNullable(); + table.string('mime_type').notNullable(); + table.integer('size').unsigned().notNullable(); + table.string('origin_name'); table.timestamps(); }); }; diff --git a/packages/server/src/services/Attachments/AttachmentsApplication.ts b/packages/server/src/services/Attachments/AttachmentsApplication.ts index 515c07d09..ff91c8aff 100644 --- a/packages/server/src/services/Attachments/AttachmentsApplication.ts +++ b/packages/server/src/services/Attachments/AttachmentsApplication.ts @@ -5,6 +5,7 @@ import { GetAttachment } from './GetAttachment'; import { AttachmentUploadPipeline } from './S3UploadPipeline'; import { LinkAttachment } from './LinkAttachment'; import { UnlinkAttachment } from './UnlinkAttachment'; +import { getAttachmentPresignedUrl } from './GetAttachmentPresignedUrl'; @Service() export class AttachmentsApplication { @@ -26,6 +27,9 @@ export class AttachmentsApplication { @Inject() private unlinkDocumentService: UnlinkAttachment; + @Inject() + private getPresignedUrlService: getAttachmentPresignedUrl; + /** * * @returns @@ -101,4 +105,13 @@ export class AttachmentsApplication { modelId ); } + + /** + * Retrieves the presigned url of the given attachment key. + * @param {string} key + * @returns {Promise} + */ + public getPresignedUrl(key: string): Promise { + return this.getPresignedUrlService.getPresignedUrl(key); + } } diff --git a/packages/server/src/services/Attachments/GetAttachmentPresignedUrl.ts b/packages/server/src/services/Attachments/GetAttachmentPresignedUrl.ts new file mode 100644 index 000000000..191d9aecc --- /dev/null +++ b/packages/server/src/services/Attachments/GetAttachmentPresignedUrl.ts @@ -0,0 +1,27 @@ +import { GetObjectCommand } from '@aws-sdk/client-s3'; +import { getSignedUrl } from '@aws-sdk/s3-request-presigner'; +import { s3 } from '@/lib/S3/S3'; +import { Service } from 'typedi'; + +@Service() +export class getAttachmentPresignedUrl { + /** + * Retrieves the presigned url of the given attachment key. + * @param {string} key + * @returns {Promise} + */ + async getPresignedUrl(key: string) { + const params = { + Bucket: process.env.AWS_BUCKET, + Key: key, + Expires: 60 * 5, // 5 minutes + }; + const command = new GetObjectCommand({ + Bucket: process.env.AWS_BUCKET, + Key: key, + }); + const signedUrl = await getSignedUrl(s3, command, { expiresIn: 300 }); + + return signedUrl; + } +} diff --git a/packages/server/src/services/Attachments/UploadDocument.ts b/packages/server/src/services/Attachments/UploadDocument.ts index c8203fb9e..7d63938ef 100644 --- a/packages/server/src/services/Attachments/UploadDocument.ts +++ b/packages/server/src/services/Attachments/UploadDocument.ts @@ -6,12 +6,20 @@ export class UploadDocument { @Inject() private tenancy: HasTenancyService; + /** + * Inserts the document metadata. + * @param {number} tenantId + * @param {} file + * @returns {} + */ async upload(tenantId: number, file: any) { const { Document } = this.tenancy.models(tenantId); const insertedDocument = await Document.query().insert({ key: file.key, - extension: file.mimetype, + mimeType: file.mimetype, + size: file.size, + originName: file.originalname, }); return insertedDocument; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 03bd3e130..950ebc05a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -38,6 +38,9 @@ importers: '@aws-sdk/client-s3': specifier: ^3.576.0 version: 3.583.0 + '@aws-sdk/s3-request-presigner': + specifier: ^3.583.0 + version: 3.583.0 '@casl/ability': specifier: ^5.4.3 version: 5.4.4 @@ -1473,6 +1476,20 @@ packages: tslib: 2.6.2 dev: false + /@aws-sdk/s3-request-presigner@3.583.0: + resolution: {integrity: sha512-Xp48RdnUzERYraIczByhbV/92TtXeJNE7QXOyMGVdvQkVP+3rwuXRGg85FqNSkOq7TFaGhnuxPiYWvsTeEzZTg==} + engines: {node: '>=16.0.0'} + dependencies: + '@aws-sdk/signature-v4-multi-region': 3.582.0 + '@aws-sdk/types': 3.577.0 + '@aws-sdk/util-format-url': 3.577.0 + '@smithy/middleware-endpoint': 3.0.0 + '@smithy/protocol-http': 4.0.0 + '@smithy/smithy-client': 3.0.1 + '@smithy/types': 3.0.0 + tslib: 2.6.2 + dev: false + /@aws-sdk/signature-v4-multi-region@3.582.0: resolution: {integrity: sha512-aFCOjjNqEX2l+V8QjOWy5F7CtHIC/RlYdBuv3No6yxn+pMvVUUe6zdMk2yHWcudVpHWsyvcZzAUBliAPeFLPsQ==} engines: {node: '>=16.0.0'} @@ -1524,6 +1541,16 @@ packages: tslib: 2.6.2 dev: false + /@aws-sdk/util-format-url@3.577.0: + resolution: {integrity: sha512-SyEGC2J+y/krFRuPgiF02FmMYhqbiIkOjDE6k4nYLJQRyS6XEAGxZoG+OHeOVEM+bsDgbxokXZiM3XKGu6qFIg==} + engines: {node: '>=16.0.0'} + dependencies: + '@aws-sdk/types': 3.577.0 + '@smithy/querystring-builder': 3.0.0 + '@smithy/types': 3.0.0 + tslib: 2.6.2 + dev: false + /@aws-sdk/util-locate-window@3.568.0: resolution: {integrity: sha512-3nh4TINkXYr+H41QaPelCceEB2FXP3fxp93YZXB/kqJvX0U9j0N0Uk45gvsjmEPzG8XxkPEeLIfT2I1M7A6Lig==} engines: {node: '>=16.0.0'} @@ -8995,7 +9022,7 @@ packages: resolution: {integrity: sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==} dependencies: pascal-case: 3.1.2 - tslib: 2.5.3 + tslib: 2.6.2 dev: false /camelcase-css@2.0.1: @@ -9054,7 +9081,7 @@ packages: resolution: {integrity: sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A==} dependencies: no-case: 3.0.4 - tslib: 2.5.3 + tslib: 2.6.2 upper-case-first: 2.0.2 dev: false @@ -9163,7 +9190,7 @@ packages: path-case: 3.0.4 sentence-case: 3.0.4 snake-case: 3.0.4 - tslib: 2.5.3 + tslib: 2.6.2 dev: false /char-regex@1.0.2: @@ -9678,7 +9705,7 @@ packages: resolution: {integrity: sha512-I2hSBi7Vvs7BEuJDr5dDHfzb/Ruj3FyvFyh7KLilAjNQw3Be+xgqUBA2W6scVEcL0hL1dwPRtIqEPVUCKkSsyQ==} dependencies: no-case: 3.0.4 - tslib: 2.5.3 + tslib: 2.6.2 upper-case: 2.0.2 dev: false @@ -11035,7 +11062,7 @@ packages: resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==} dependencies: no-case: 3.0.4 - tslib: 2.5.3 + tslib: 2.6.2 dev: false /dot-prop@4.2.1: @@ -13755,7 +13782,7 @@ packages: resolution: {integrity: sha512-H/vuk5TEEVZwrR0lp2zed9OCo1uAILMlx0JEMgC26rzyJJ3N1v6XkwHHXJQdR2doSjcGPM6OKPYoJgf0plJ11Q==} dependencies: capital-case: 1.0.4 - tslib: 2.5.3 + tslib: 2.6.2 dev: false /helmet-crossdomain@0.4.0: @@ -17083,7 +17110,7 @@ packages: /lower-case@2.0.2: resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} dependencies: - tslib: 2.5.3 + tslib: 2.6.2 dev: false /lowercase-keys@1.0.1: @@ -18120,7 +18147,7 @@ packages: resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} dependencies: lower-case: 2.0.2 - tslib: 2.5.3 + tslib: 2.6.2 dev: false /nocache@2.1.0: @@ -19209,7 +19236,7 @@ packages: resolution: {integrity: sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==} dependencies: dot-case: 3.0.4 - tslib: 2.5.3 + tslib: 2.6.2 dev: false /parent-module@1.0.1: @@ -19309,7 +19336,7 @@ packages: resolution: {integrity: sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==} dependencies: no-case: 3.0.4 - tslib: 2.5.3 + tslib: 2.6.2 dev: false /pascalcase@0.1.1: @@ -19329,7 +19356,7 @@ packages: resolution: {integrity: sha512-qO4qCFjXqVTrcbPt/hQfhTQ+VhFsqNKOPtytgNKkKxSoEp3XPUQ8ObFuePylOIok5gjn69ry8XiULxCwot3Wfg==} dependencies: dot-case: 3.0.4 - tslib: 2.5.3 + tslib: 2.6.2 dev: false /path-dirname@1.0.2: @@ -22966,7 +22993,7 @@ packages: resolution: {integrity: sha512-8LS0JInaQMCRoQ7YUytAo/xUu5W2XnQxV2HI/6uM6U7CITS1RqPElr30V6uIqyMKM9lJGRVFy5/4CuzcixNYSg==} dependencies: no-case: 3.0.4 - tslib: 2.5.3 + tslib: 2.6.2 upper-case-first: 2.0.2 dev: false @@ -23212,7 +23239,7 @@ packages: resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==} dependencies: dot-case: 3.0.4 - tslib: 2.5.3 + tslib: 2.6.2 dev: false /snapdragon-node@2.1.1: @@ -25212,13 +25239,13 @@ packages: /upper-case-first@2.0.2: resolution: {integrity: sha512-514ppYHBaKwfJRK/pNC6c/OxfGa0obSnAl106u97Ed0I625Nin96KAjttZF6ZL3e1XLtphxnqrOi9iWgm+u+bg==} dependencies: - tslib: 2.5.3 + tslib: 2.6.2 dev: false /upper-case@2.0.2: resolution: {integrity: sha512-KgdgDGJt2TpuwBUIjgG6lzw2GWFRCW9Qkfkiv0DxqHHLYJHmtmdUIKcZd8rHgFSjopVTlw6ggzCm1b8MFQwikg==} dependencies: - tslib: 2.5.3 + tslib: 2.6.2 dev: false /uri-js@4.4.1: