From d1fceca268ecac013ac058d04bf75ab69f400985 Mon Sep 17 00:00:00 2001 From: Eric Ke Date: Sat, 31 Oct 2020 00:46:43 -0700 Subject: [PATCH 01/12] upload in controller and service --- package-lock.json | 200 ++++++++++++++++++++++++--- package.json | 3 + src/app.ts | 7 + src/controllers/StorageController.ts | 55 ++++++++ src/controllers/index.ts | 10 +- src/services/StorageService.ts | 32 +++++ src/services/index.ts | 1 + 7 files changed, 291 insertions(+), 17 deletions(-) create mode 100644 src/controllers/StorageController.ts create mode 100644 src/services/StorageService.ts diff --git a/package-lock.json b/package-lock.json index beaeb179..188d7f08 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2091,7 +2091,6 @@ "version": "1.19.0", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.0.tgz", "integrity": "sha512-W98JrE0j2K78swW4ukqMleo8R7h/pFETjM2DQ90MF6XK2i4LO4W3gQ71Lt4w3bfm2EvVSyWHplECvB5sK22yFQ==", - "dev": true, "requires": { "@types/connect": "*", "@types/node": "*" @@ -2115,7 +2114,6 @@ "version": "3.4.33", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.33.tgz", "integrity": "sha512-2+FrkXY4zllzTNfJth7jOqEHC+enpLeGslEhpnTAkg21GkRrWV4SsAtqchtT4YS9/nODBU2/ZfsBY2X4J/dX7A==", - "dev": true, "requires": { "@types/node": "*" } @@ -2145,7 +2143,6 @@ "version": "4.17.6", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.6.tgz", "integrity": "sha512-n/mr9tZI83kd4azlPG5y997C/M4DNABK9yErhFM6hKdym4kkmd9j0vtsJyjFIwfRBxtrxZtAfGZCNRIBMFLK5w==", - "dev": true, "requires": { "@types/body-parser": "*", "@types/express-serve-static-core": "*", @@ -2166,7 +2163,6 @@ "version": "4.17.7", "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.7.tgz", "integrity": "sha512-EMgTj/DF9qpgLXyc+Btimg+XoH7A2liE8uKul8qSmMTHCeNYzydDKFdsJskDvw42UsesCnhO63dO0Grbj8J4Dw==", - "dev": true, "requires": { "@types/node": "*", "@types/qs": "*", @@ -2240,8 +2236,7 @@ "@types/mime": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.2.tgz", - "integrity": "sha512-4kPlzbljFcsttWEq6aBW0OZe6BDajAmyvr2xknBG92tejQnvdGtT9+kXSZ580DqpxY9qG2xeQVF9Dq0ymUTo5Q==", - "dev": true + "integrity": "sha512-4kPlzbljFcsttWEq6aBW0OZe6BDajAmyvr2xknBG92tejQnvdGtT9+kXSZ580DqpxY9qG2xeQVF9Dq0ymUTo5Q==" }, "@types/morgan": { "version": "1.9.1", @@ -2252,6 +2247,14 @@ "@types/node": "*" } }, + "@types/multer": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.4.tgz", + "integrity": "sha512-wdfkiKBBEMTODNbuF3J+qDDSqJxt50yB9pgDiTcFew7f97Gcc7/sM4HR66ofGgpJPOALWOqKAch4gPyqEXSkeQ==", + "requires": { + "@types/express": "*" + } + }, "@types/node": { "version": "14.0.26", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.26.tgz", @@ -2278,20 +2281,17 @@ "@types/qs": { "version": "6.9.3", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.3.tgz", - "integrity": "sha512-7s9EQWupR1fTc2pSMtXRQ9w9gLOcrJn+h7HOXw4evxyvVqMi4f+q7d2tnFe3ng3SNHjtK+0EzGMGFUQX4/AQRA==", - "dev": true + "integrity": "sha512-7s9EQWupR1fTc2pSMtXRQ9w9gLOcrJn+h7HOXw4evxyvVqMi4f+q7d2tnFe3ng3SNHjtK+0EzGMGFUQX4/AQRA==" }, "@types/range-parser": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz", - "integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==", - "dev": true + "integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==" }, "@types/serve-static": { "version": "1.13.4", "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.4.tgz", "integrity": "sha512-jTDt0o/YbpNwZbQmE/+2e+lfjJEJJR0I3OFaKQKPWkASkCoW3i6fsUnqudSMcNAfbtmADGu8f4MV4q+GqULmug==", - "dev": true, "requires": { "@types/express-serve-static-core": "*", "@types/mime": "*" @@ -2598,6 +2598,11 @@ "resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-3.0.0.tgz", "integrity": "sha512-qMcx+Gy2UZynHjOHOIXPNvpf+9cjvk3cWrBBK7zg4gH9+clobJRb9NGzcT7mQTcV/6Gm/1WelUtqxVXnNlrwcw==" }, + "append-field": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", + "integrity": "sha1-HjRA6RXwsSA9I3SOeO3XubW0PlY=" + }, "arg": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", @@ -2712,6 +2717,58 @@ "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", "dev": true }, + "aws-sdk": { + "version": "2.773.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.773.0.tgz", + "integrity": "sha512-bwqEm/x3HMUd/xfcUeTjCQFi904oSNcwl2ZNz3mwAdEIqt3sQ9aE3GYoZQxKXw/XHQlF7hPiKO07GDGmS6x4AQ==", + "requires": { + "buffer": "4.9.2", + "events": "1.1.1", + "ieee754": "1.1.13", + "jmespath": "0.15.0", + "querystring": "0.2.0", + "sax": "1.2.1", + "url": "0.10.3", + "uuid": "3.3.2", + "xml2js": "0.4.19" + }, + "dependencies": { + "buffer": { + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", + "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + } + }, + "sax": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", + "integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o=" + }, + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" + }, + "xml2js": { + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", + "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", + "requires": { + "sax": ">=0.6.0", + "xmlbuilder": "~9.0.1" + } + }, + "xmlbuilder": { + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", + "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=" + } + } + }, "aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", @@ -3044,6 +3101,47 @@ "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz", "integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==" }, + "busboy": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-0.2.14.tgz", + "integrity": "sha1-bCpiLvz0fFe7vh4qnDetNseSVFM=", + "requires": { + "dicer": "0.2.5", + "readable-stream": "1.1.x" + }, + "dependencies": { + "dicer": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.2.5.tgz", + "integrity": "sha1-WZbAhrszIYyBLAkL3cCc0S+stw8=", + "requires": { + "readable-stream": "1.1.x", + "streamsearch": "0.1.2" + } + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + } + } + }, "bytes": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", @@ -4411,6 +4509,11 @@ "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", "optional": true }, + "events": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", + "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=" + }, "exec-sh": { "version": "0.3.4", "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.3.4.tgz", @@ -8601,6 +8704,11 @@ } } }, + "jmespath": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz", + "integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc=" + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -9343,8 +9451,7 @@ "minimist": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" }, "mixin-deep": { "version": "1.3.2", @@ -9371,7 +9478,6 @@ "version": "0.5.5", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, "requires": { "minimist": "^1.2.5" } @@ -9405,6 +9511,48 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, + "multer": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.2.tgz", + "integrity": "sha512-xY8pX7V+ybyUpbYMxtjM9KAiD9ixtg5/JkeKUTD6xilfDv0vzzOFcCp4Ljb1UU3tSOM3VTZtKo63OmzOrGi3Cg==", + "requires": { + "append-field": "^1.0.0", + "busboy": "^0.2.11", + "concat-stream": "^1.5.2", + "mkdirp": "^0.5.1", + "object-assign": "^4.1.1", + "on-finished": "^2.3.0", + "type-is": "^1.6.4", + "xtend": "^4.0.0" + }, + "dependencies": { + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + } + } + }, "mute-stream": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", @@ -10292,6 +10440,11 @@ "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" }, + "querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" + }, "range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -11964,8 +12117,7 @@ "typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", - "optional": true + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" }, "typedarray-to-buffer": { "version": "3.1.5", @@ -12108,6 +12260,22 @@ "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", "dev": true }, + "url": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", + "integrity": "sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=", + "requires": { + "punycode": "1.3.2", + "querystring": "0.2.0" + }, + "dependencies": { + "punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" + } + } + }, "use": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", diff --git a/package.json b/package.json index f970a178..3a1465a1 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "license": "ISC", "dependencies": { "@sendgrid/mail": "^7.2.1", + "aws-sdk": "^2.773.0", "body-parser": "^1.19.0", "class-transformer": "^0.3.1", "class-validator": "^0.12.2", @@ -50,6 +51,7 @@ "firebase-admin": "^8.9.2", "module-alias": "^2.2.2", "morgan": "^1.10.0", + "multer": "^1.4.2", "pg": "^8.3.0", "redoc-express": "^1.0.0", "reflect-metadata": "^0.1.13", @@ -67,6 +69,7 @@ "@types/express-rate-limit": "^5.0.0", "@types/jest": "^26.0.10", "@types/morgan": "^1.9.1", + "@types/multer": "^1.4.4", "@types/node": "^14.0.26", "@types/supertest": "^2.0.10", "@typescript-eslint/eslint-plugin": "^3.1.0", diff --git a/src/app.ts b/src/app.ts index 11f63352..49c4286f 100644 --- a/src/app.ts +++ b/src/app.ts @@ -16,6 +16,13 @@ import morgan from 'morgan'; import { checkCurrentUserToken } from './decorators'; +import AWS from 'aws-sdk'; + +AWS.config.update({ + accessKeyId: process.env.AWS_ACCESS_KEY_ID, + secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY, +}); + const limiter = rateLimit({ windowMs: parseInt(process.env.RATE_LIMIT_TIMEFRAME, 10), max: parseInt(process.env.RATE_LIMIT_MAX_REQUESTS, 10), diff --git a/src/controllers/StorageController.ts b/src/controllers/StorageController.ts new file mode 100644 index 00000000..81f9aa61 --- /dev/null +++ b/src/controllers/StorageController.ts @@ -0,0 +1,55 @@ +import { + JsonController, + Post, + UploadedFile, + Req, + BadRequestError, + HttpCode, +} from 'routing-controllers'; +import { StorageService, StorageServiceImpl } from '@Services'; +import multer from 'multer'; + +const fileFilter = (req: Express.Request, file: Express.Multer.File, cb: Function) => { + if ( + file.mimetype.includes('image') || + file.mimetype.includes('pdf') || + file.mimetype.includes('word') + ) { + cb(null, true); + } else { + console.log('Invalid file type'); + cb(null, false); + } +}; + +const fileUploadOptions = { + storage: multer.memoryStorage(), + fileFilter: fileFilter, + limits: { + fieldNameSize: 255, + fileSize: 1024 * 1024 * 5, + }, +}; + +@JsonController('/api/storage') +export class StorageController { + constructor(private storageService: StorageService) {} + + @HttpCode(201) + @Post('/upload') + async saveFile( + @Req() req: Express.Request, + @UploadedFile('file', { + options: fileUploadOptions, + }) + file: Express.Multer.File + ): Promise { + if (!file) { + throw new BadRequestError('Invalid file'); + } + const name = Date.now() + '-' + file.originalname; + return this.storageService.uploadFile(name, file.buffer); + } +} + +export const StorageControllerImpl = new StorageController(StorageServiceImpl); diff --git a/src/controllers/index.ts b/src/controllers/index.ts index 2b9b0c2e..c02c4420 100644 --- a/src/controllers/index.ts +++ b/src/controllers/index.ts @@ -2,8 +2,15 @@ import { EventController, EventControllerImpl } from './EventController'; import { UserController, UserControllerImpl } from './UserController'; import { AuthController, AuthControllerImpl } from './AuthController'; import { TeapotController, TeapotControllerImpl } from './TeapotController'; +import { StorageController, StorageControllerImpl } from './StorageController'; -export const controllers = [EventController, UserController, AuthController, TeapotController]; +export const controllers = [ + EventController, + UserController, + AuthController, + TeapotController, + StorageController, +]; // map from name of controller to an instance // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -12,6 +19,7 @@ controllerMap.set(EventController.name, EventControllerImpl); controllerMap.set(UserController.name, UserControllerImpl); controllerMap.set(AuthController.name, AuthControllerImpl); controllerMap.set(TeapotController.name, TeapotControllerImpl); +controllerMap.set(StorageController.name, StorageControllerImpl); export const ControllerContainer = { // controller here is just a class and all classes have names in ts diff --git a/src/services/StorageService.ts b/src/services/StorageService.ts new file mode 100644 index 00000000..095b72de --- /dev/null +++ b/src/services/StorageService.ts @@ -0,0 +1,32 @@ +import AWS from 'aws-sdk'; + +const s3 = new AWS.S3(); + +export class StorageService { + async uploadFile( + fileName: string, + fileContent: string | Buffer | Blob | NodeJS.TypedArray | NodeJS.ReadableStream + ): Promise { + const params = { + Bucket: process.env.BUCKET_NAME, + Key: fileName, + Body: fileContent, + }; + + return s3 + .upload(params) + .promise() + .then( + function(data: AWS.S3.ManagedUpload.SendData) { + console.log(`File uploaded successfully. ${data.Location}`); + return data.Location; + }, + function(err: Error) { + console.log(`Error while uploading file: ${err}`); + return ''; + } + ); + } +} + +export const StorageServiceImpl = new StorageService(); diff --git a/src/services/index.ts b/src/services/index.ts index 3dd2de65..246aa8a1 100644 --- a/src/services/index.ts +++ b/src/services/index.ts @@ -5,3 +5,4 @@ export { RSVPService, RSVPServiceImpl } from './RSVPService'; export { AuthenticationService, AuthenticationServiceImpl } from './AuthenticationService'; export { AuthorizationService, AuthorizationServiceImpl } from './AuthorizationService'; export { AccountService, AccountServiceImpl } from './AccountService'; +export { StorageService, StorageServiceImpl } from './StorageService'; From e866d94bc627db8d4aaac80ebb3d04defad9f501 Mon Sep 17 00:00:00 2001 From: Eric Ke Date: Sat, 5 Dec 2020 12:24:57 -0800 Subject: [PATCH 02/12] fix naming, basic download --- src/controllers/StorageController.ts | 34 ++++++++++++++++++++++++++-- src/services/StorageService.ts | 18 ++++++++++++--- 2 files changed, 47 insertions(+), 5 deletions(-) diff --git a/src/controllers/StorageController.ts b/src/controllers/StorageController.ts index 81f9aa61..29df20c9 100644 --- a/src/controllers/StorageController.ts +++ b/src/controllers/StorageController.ts @@ -1,13 +1,17 @@ import { JsonController, + Get, Post, + QueryParam, UploadedFile, Req, + Res, BadRequestError, HttpCode, } from 'routing-controllers'; import { StorageService, StorageServiceImpl } from '@Services'; import multer from 'multer'; +import { Readable } from 'typeorm/platform/PlatformTools'; const fileFilter = (req: Express.Request, file: Express.Multer.File, cb: Function) => { if ( @@ -37,7 +41,7 @@ export class StorageController { @HttpCode(201) @Post('/upload') - async saveFile( + async uploadFile( @Req() req: Express.Request, @UploadedFile('file', { options: fileUploadOptions, @@ -48,7 +52,33 @@ export class StorageController { throw new BadRequestError('Invalid file'); } const name = Date.now() + '-' + file.originalname; - return this.storageService.uploadFile(name, file.buffer); + try { + return this.storageService.uploadFile(name, file.buffer); + } catch (e) { + throw new BadRequestError(`Error uploading to storage: ${e.message}`); + } + } + + @HttpCode(200) + @Get('/download') + downloadFile(@QueryParam('fileName') fileName: string): NodeJS.ReadableStream { + const filepathRegex = + '^(?:[w]:|\\)(\\[a-z_-s0-9.]+)+.(txt|gif|pdf|doc|docx|xls|xlsx|png|jpg|jpeg)$'; + + if (fileName.match(filepathRegex)) { + try { + return this.storageService.downloadFile(fileName); + } catch (e) { + throw new BadRequestError(`Error loading from storage: ${e.message}`); + } + } + throw new BadRequestError('Invalid file path'); + } + + @HttpCode(200) + @Get('/index') + async getFileIndex(): Promise { + return ''; } } diff --git a/src/services/StorageService.ts b/src/services/StorageService.ts index 095b72de..7f15ee0e 100644 --- a/src/services/StorageService.ts +++ b/src/services/StorageService.ts @@ -21,12 +21,24 @@ export class StorageService { console.log(`File uploaded successfully. ${data.Location}`); return data.Location; }, - function(err: Error) { - console.log(`Error while uploading file: ${err}`); - return ''; + function(e: Error) { + throw new Error(`Error while uploading file: ${e.message}`); } ); } + + downloadFile(fileName: string): NodeJS.ReadableStream { + try { + const params = { + Bucket: process.env.BUCKET_NAME, + Key: fileName, + }; + + return s3.getObject(params).createReadStream(); + } catch (e) { + throw new Error(`Could not retrieve file from S3: ${e.message}`); + } + } } export const StorageServiceImpl = new StorageService(); From 17bb0bc47ba045ff1a9207badde6a96dba84ba29 Mon Sep 17 00:00:00 2001 From: Eric Ke Date: Sat, 5 Dec 2020 12:50:43 -0800 Subject: [PATCH 03/12] remove regex --- src/controllers/StorageController.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/controllers/StorageController.ts b/src/controllers/StorageController.ts index 29df20c9..51a03e89 100644 --- a/src/controllers/StorageController.ts +++ b/src/controllers/StorageController.ts @@ -62,10 +62,7 @@ export class StorageController { @HttpCode(200) @Get('/download') downloadFile(@QueryParam('fileName') fileName: string): NodeJS.ReadableStream { - const filepathRegex = - '^(?:[w]:|\\)(\\[a-z_-s0-9.]+)+.(txt|gif|pdf|doc|docx|xls|xlsx|png|jpg|jpeg)$'; - - if (fileName.match(filepathRegex)) { + if (fileName) { try { return this.storageService.downloadFile(fileName); } catch (e) { From 97187a26008008d0ed8aa18e8fa9e9f68e1dcb6c Mon Sep 17 00:00:00 2001 From: Eric Ke Date: Wed, 27 Jan 2021 18:29:10 -0800 Subject: [PATCH 04/12] resume service, with separate privileged storage service --- .gitignore | 3 +- package-lock.json | 1 + package.json | 2 +- src/controllers/StorageController.ts | 42 ++++++++------- src/controllers/UserController.ts | 73 +++++++++++++++++++++++++- src/services/LocalStorageService.ts | 31 +++++++++++ src/services/ResumeService.ts | 34 ++++++++++++ src/services/StorageService.ts | 77 +++++++++++++++++++++++----- src/services/index.ts | 2 + 9 files changed, 231 insertions(+), 34 deletions(-) create mode 100644 src/services/LocalStorageService.ts create mode 100644 src/services/ResumeService.ts diff --git a/.gitignore b/.gitignore index 66e72ceb..5dd3d965 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ dist .env .DS_Store .eslintignore -.eslintcache \ No newline at end of file +.eslintcache +local_fs/ \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 2820f6e1..4f15e2fe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2260,6 +2260,7 @@ "version": "1.4.4", "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.4.tgz", "integrity": "sha512-wdfkiKBBEMTODNbuF3J+qDDSqJxt50yB9pgDiTcFew7f97Gcc7/sM4HR66ofGgpJPOALWOqKAch4gPyqEXSkeQ==", + "dev": true, "requires": { "@types/express": "*" } diff --git a/package.json b/package.json index c0e15a00..f9baf27c 100644 --- a/package.json +++ b/package.json @@ -114,4 +114,4 @@ "@Repositories": "dist/repositories", "@Config": "dist/config.js" } -} \ No newline at end of file +} diff --git a/src/controllers/StorageController.ts b/src/controllers/StorageController.ts index 51a03e89..bec39d91 100644 --- a/src/controllers/StorageController.ts +++ b/src/controllers/StorageController.ts @@ -4,14 +4,13 @@ import { Post, QueryParam, UploadedFile, - Req, - Res, BadRequestError, - HttpCode, + UseBefore, } from 'routing-controllers'; -import { StorageService, StorageServiceImpl } from '@Services'; +import { AppUserService, AppUserServiceImpl, StorageService, StorageServiceImpl } from '@Services'; import multer from 'multer'; -import { Readable } from 'typeorm/platform/PlatformTools'; +import { OfficerAuthMiddleware } from '@Middlewares'; +import { OpenAPI } from 'routing-controllers-openapi'; const fileFilter = (req: Express.Request, file: Express.Multer.File, cb: Function) => { if ( @@ -31,37 +30,39 @@ const fileUploadOptions = { fileFilter: fileFilter, limits: { fieldNameSize: 255, - fileSize: 1024 * 1024 * 5, + fileSize: 1024 * 1024 * 50, }, }; @JsonController('/api/storage') export class StorageController { - constructor(private storageService: StorageService) {} + constructor(private storageService: StorageService, private appUserService: AppUserService) {} - @HttpCode(201) @Post('/upload') + @UseBefore(OfficerAuthMiddleware) + @OpenAPI({ security: [{ TokenAuth: [] }] }) async uploadFile( - @Req() req: Express.Request, @UploadedFile('file', { options: fileUploadOptions, }) file: Express.Multer.File - ): Promise { + ): Promise { if (!file) { throw new BadRequestError('Invalid file'); } - const name = Date.now() + '-' + file.originalname; + const name = + Date.now() + '-' + file.originalname.substring(0, file.originalname.lastIndexOf('.')); try { - return this.storageService.uploadFile(name, file.buffer); + return this.storageService.uploadFile(name, file); } catch (e) { throw new BadRequestError(`Error uploading to storage: ${e.message}`); } } - @HttpCode(200) @Get('/download') - downloadFile(@QueryParam('fileName') fileName: string): NodeJS.ReadableStream { + @UseBefore(OfficerAuthMiddleware) + @OpenAPI({ security: [{ TokenAuth: [] }] }) + async downloadFile(@QueryParam('fileName') fileName: string): Promise { if (fileName) { try { return this.storageService.downloadFile(fileName); @@ -72,11 +73,16 @@ export class StorageController { throw new BadRequestError('Invalid file path'); } - @HttpCode(200) @Get('/index') - async getFileIndex(): Promise { - return ''; + @UseBefore(OfficerAuthMiddleware) + @OpenAPI({ security: [{ TokenAuth: [] }] }) + async getFileIndex(): Promise | null> { + try { + return this.storageService.getFileIndex(); + } catch (e) { + throw new BadRequestError(`Error loading from storage: ${e.message}`); + } } } -export const StorageControllerImpl = new StorageController(StorageServiceImpl); +export const StorageControllerImpl = new StorageController(StorageServiceImpl, AppUserServiceImpl); diff --git a/src/controllers/UserController.ts b/src/controllers/UserController.ts index acd17769..78ace193 100644 --- a/src/controllers/UserController.ts +++ b/src/controllers/UserController.ts @@ -8,6 +8,10 @@ import { Body, UseBefore, QueryParams, + UploadedFile, + BadRequestError, + OnUndefined, + Res, } from 'routing-controllers'; import { ResponseSchema, OpenAPI } from 'routing-controllers-openapi'; @@ -17,6 +21,8 @@ import { AppUserServiceImpl, AttendanceService, AttendanceServiceImpl, + ResumeService, + ResumeServiceImpl, } from '@Services'; import { AppUserPostRequest, @@ -34,13 +40,33 @@ import { import { AppUserMapper, AppUserMapperImpl } from '@Mappers'; import { InducteeAuthMiddleware, MemberAuthMiddleware, OfficerAuthMiddleware } from '@Middlewares'; import { formatISO } from 'date-fns'; +import multer from 'multer'; + +const fileFilter = (req: Express.Request, file: Express.Multer.File, cb: Function) => { + if (file.mimetype.includes('pdf') || file.mimetype.includes('word')) { + cb(null, true); + } else { + console.log('Invalid file type'); + cb(null, false); + } +}; + +const fileUploadOptions = { + storage: multer.memoryStorage(), + fileFilter: fileFilter, + limits: { + fieldNameSize: 255, + fileSize: 1024 * 1024 * 5, + }, +}; @JsonController('/api/users') export class UserController { constructor( private appUserService: AppUserService, private attendanceService: AttendanceService, - private appUserMapper: AppUserMapper + private appUserMapper: AppUserMapper, + private resumeService: ResumeService ) {} @Get('/') @@ -216,10 +242,53 @@ export class UserController { return this.appUserMapper.entityToResponse(updatedAppUser); } + + @Post('/:userID/resume') + @UseBefore(InducteeAuthMiddleware) + @OpenAPI({ security: [{ TokenAuth: [] }] }) + async uploadResume( + @Param('userID') userID: number, + @CurrentUser({ required: true }) appUser: AppUser, + @UploadedFile('file', { + options: fileUploadOptions, + }) + file: Express.Multer.File + ): Promise { + if (this.appUserService.isUnauthedUserOrNonOfficer(appUser, userID)) { + throw new ForbiddenError(); + } + if (!file) { + throw new BadRequestError('Invalid file'); + } + try { + return this.resumeService.uploadResume(appUser, file); + } catch (e) { + throw new BadRequestError(`Error uploading to storage: ${e.message}`); + } + } + + @Get('/:userID/resume') + @UseBefore(InducteeAuthMiddleware) + @OpenAPI({ security: [{ TokenAuth: [] }] }) + async downloadResume( + @Param('userID') userID: number, + @CurrentUser({ required: true }) appUser: AppUser + ): Promise { + if (this.appUserService.isUnauthedUserOrNonOfficer(appUser, userID)) { + throw new ForbiddenError(); + } + + try { + return this.resumeService.downloadResume(appUser); + } catch (e) { + throw new BadRequestError(`Error uploading to storage: ${e.message}`); + } + } } export const UserControllerImpl = new UserController( AppUserServiceImpl, AttendanceServiceImpl, - AppUserMapperImpl + AppUserMapperImpl, + ResumeServiceImpl ); diff --git a/src/services/LocalStorageService.ts b/src/services/LocalStorageService.ts new file mode 100644 index 00000000..9d14493b --- /dev/null +++ b/src/services/LocalStorageService.ts @@ -0,0 +1,31 @@ +import fs from 'fs'; + +const dir_path = process.cwd() + '/local_fs/'; + +if (!fs.existsSync(dir_path)) { + fs.mkdirSync(dir_path); +} + +export class LocalStorageService { + async uploadFile( + fileName: string, + fileContent: string | Buffer | Blob | NodeJS.TypedArray | NodeJS.ReadableStream + ): Promise { + fs.writeFile(dir_path + fileName, Buffer.from(fileContent), function(err) { + if (err) { + throw new Error(`Could not write file to ${dir_path + fileName}: ${err.message}`); + } + }); + return `Uploaded file to ${dir_path + fileName}`; + } + + async downloadFile(fileName: string): Promise { + try { + return fs.promises.readFile(dir_path + fileName); + } catch (e) { + throw new Error(`Could not retrieve file from fs at ${dir_path + fileName}: ${e.message}`); + } + } +} + +export const LocalStorageServiceImpl = new LocalStorageService(); diff --git a/src/services/ResumeService.ts b/src/services/ResumeService.ts new file mode 100644 index 00000000..79a357ca --- /dev/null +++ b/src/services/ResumeService.ts @@ -0,0 +1,34 @@ +import { AppUser } from '@Entities'; + +import { StorageService, StorageServiceImpl } from '@Services'; + +export class ResumeService { + constructor(private storageService: StorageService) {} + + async uploadResume(appUser: AppUser, file: Express.Multer.File): Promise { + const storedfileName = `${appUser.firstName}_${appUser.lastName}_Resume`; + const options = { + appendFileName: appUser.id, + }; + try { + return this.storageService.uploadFile(storedfileName, file, options).catch(e => { + return null; + }); + } catch (e) { + console.log(`Error uploading to resume storage: ${e.message}`); + return null; + } + } + + async downloadResume(appUser: AppUser): Promise { + try { + const storedfileName = `${appUser.firstName}_${appUser.lastName}_${appUser.id}_Resume`; + return this.storageService.downloadFile(storedfileName); + } catch (e) { + console.log(`Could not retrieve file from S3: ${e.message}`); + return null; + } + } +} + +export const ResumeServiceImpl = new ResumeService(StorageServiceImpl); diff --git a/src/services/StorageService.ts b/src/services/StorageService.ts index 7f15ee0e..d05da39e 100644 --- a/src/services/StorageService.ts +++ b/src/services/StorageService.ts @@ -1,16 +1,26 @@ import AWS from 'aws-sdk'; -const s3 = new AWS.S3(); - +const s3 = new AWS.S3({ region: process.env.BUCKET_REGION }); export class StorageService { async uploadFile( fileName: string, - fileContent: string | Buffer | Blob | NodeJS.TypedArray | NodeJS.ReadableStream - ): Promise { + file: Express.Multer.File, + options?: Object + ): Promise { + let fileNameKey = fileName; + if (options) { + if ('appendFileName' in options) { + fileNameKey = `${fileName}_${options['appendFileName']}`; + } + } const params = { Bucket: process.env.BUCKET_NAME, - Key: fileName, - Body: fileContent, + Key: fileNameKey, + Body: file.buffer, + ContentDisposition: `attachment; filename="${fileName}${file.originalname.substring( + file.originalname.lastIndexOf('.') + )}"`, + ContentType: `${file.mimetype}`, }; return s3 @@ -19,24 +29,67 @@ export class StorageService { .then( function(data: AWS.S3.ManagedUpload.SendData) { console.log(`File uploaded successfully. ${data.Location}`); - return data.Location; + return 'File uploaded successfully'; }, function(e: Error) { - throw new Error(`Error while uploading file: ${e.message}`); + // throw new Error(`Error while uploading file: ${e.message}`); + console.log(`Error while uploading file: ${e.message}`); + return null; } - ); + ) + .catch(e => { + console.log(`Error while uploading file: ${e.message}`); + return null; + }); } - downloadFile(fileName: string): NodeJS.ReadableStream { + async downloadFile(fileName: string): Promise { try { const params = { Bucket: process.env.BUCKET_NAME, Key: fileName, }; - return s3.getObject(params).createReadStream(); + return s3 + .getObject(params) + .promise() + .then(res => Buffer.from(res.Body)) + .catch(e => { + console.log(`Could not retrieve file: ${e.message}`); + return null; + }); + } catch (e) { + console.log(`Could not retrieve file from S3: ${e.message}`); + return null; + } + } + + async getFileIndex(): Promise | null> { + try { + const params = { + Bucket: process.env.BUCKET_NAME, + }; + return s3 + .listObjectsV2(params, function(err) { + if (err) { + console.log(`Could not retrieve file from S3: ${err.message}`); + return null; + } + }) + .promise() + .then(function(data) { + const contents = data.Contents; + return contents.map(content => { + return content.Key; + }); + }) + .catch(e => { + console.log(`Could not retrieve file from S3: ${e.message}`); + return null; + }); } catch (e) { - throw new Error(`Could not retrieve file from S3: ${e.message}`); + console.log(`Could not retrieve file from S3: ${e.message}`); + return null; } } } diff --git a/src/services/index.ts b/src/services/index.ts index db497cfd..c6b87c99 100644 --- a/src/services/index.ts +++ b/src/services/index.ts @@ -7,3 +7,5 @@ export { AuthorizationService, AuthorizationServiceImpl } from './AuthorizationS export { AccountService, AccountServiceImpl } from './AccountService'; export { StorageService, StorageServiceImpl } from './StorageService'; export { InductionClassService, InductionClassServiceImpl } from './InductionClassService'; +export { LocalStorageService, LocalStorageServiceImpl } from './LocalStorageService'; +export { ResumeService, ResumeServiceImpl } from './ResumeService'; From df589ff34982507e76d2d21c90a6f4ebce17a1e1 Mon Sep 17 00:00:00 2001 From: Eric Ke Date: Fri, 29 Jan 2021 12:04:27 -0800 Subject: [PATCH 05/12] remove unused import --- src/controllers/UserController.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/controllers/UserController.ts b/src/controllers/UserController.ts index 78ace193..a290fa11 100644 --- a/src/controllers/UserController.ts +++ b/src/controllers/UserController.ts @@ -10,8 +10,6 @@ import { QueryParams, UploadedFile, BadRequestError, - OnUndefined, - Res, } from 'routing-controllers'; import { ResponseSchema, OpenAPI } from 'routing-controllers-openapi'; From 090fbc569162f364b02892adc01b3e2df9880186 Mon Sep 17 00:00:00 2001 From: Eric Ke Date: Sat, 30 Jan 2021 00:49:21 -0800 Subject: [PATCH 06/12] comments, err handling to controller level, fix download file --- src/app.ts | 10 +-- src/config.ts | 20 +++++ src/controllers/StorageController.ts | 30 ++++++-- src/controllers/UserController.ts | 10 ++- src/loaders/AWSLoader.ts | 13 ++++ src/loaders/index.ts | 1 + src/services/LocalStorageService.ts | 30 +++++--- src/services/ResumeService.ts | 27 +++++-- src/services/StorageService.ts | 110 ++++++++++++++++++--------- 9 files changed, 183 insertions(+), 68 deletions(-) create mode 100644 src/loaders/AWSLoader.ts diff --git a/src/app.ts b/src/app.ts index 54e4802f..a530e661 100644 --- a/src/app.ts +++ b/src/app.ts @@ -10,19 +10,12 @@ import { DocsRouter } from './routers/DocsRouter'; import { useExpressServer, useContainer as routingUseContainer } from 'routing-controllers'; import { controllers, ControllerContainer } from './controllers'; -import { loadFirebase, loadORM } from './loaders'; +import { loadAWS, loadFirebase, loadORM } from './loaders'; import { config } from './config'; import morgan from 'morgan'; import { checkCurrentUserToken } from './decorators'; -import AWS from 'aws-sdk'; - -AWS.config.update({ - accessKeyId: process.env.AWS_ACCESS_KEY_ID, - secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY, -}); - const limiter = rateLimit({ windowMs: parseInt(process.env.RATE_LIMIT_TIMEFRAME, 10), max: parseInt(process.env.RATE_LIMIT_MAX_REQUESTS, 10), @@ -31,6 +24,7 @@ const limiter = rateLimit({ export const getExpressApp = async () => { const connection = await loadORM(); loadFirebase(); + loadAWS(); const app = express(); diff --git a/src/config.ts b/src/config.ts index 2ee8718d..8d70c428 100644 --- a/src/config.ts +++ b/src/config.ts @@ -4,8 +4,16 @@ type FirebaseConfig = { privateKey: string; }; +type AWSConfig = { + accessKeyId: string; + secretKey: string; + bucketName: string; + bucketRegion: string; +}; + type Config = { firebaseConfig: FirebaseConfig; + awsConfig: AWSConfig; dbURL: string; clientAppID: string; clientApiKey: string; @@ -21,6 +29,10 @@ const { FIREBASE_DATABASE_URL, FIREBASE_CLIENT_ID, FIREBASE_CLIENT_API_KEY, + AWS_ACCESS_KEY_ID, + AWS_SECRET_ACCESS_KEY, + BUCKET_NAME, + BUCKET_REGION, DEV_AUTH, DD_METRIC_TAG, NODE_ENV, @@ -32,8 +44,16 @@ const firebaseConfig: FirebaseConfig = { privateKey: FIREBASE_PRIVATE_KEY.replace(/\\n/g, '\n'), }; +const awsConfig: AWSConfig = { + accessKeyId: AWS_ACCESS_KEY_ID, + secretKey: AWS_SECRET_ACCESS_KEY, + bucketName: BUCKET_NAME, + bucketRegion: BUCKET_REGION, +}; + export const config: Config = { firebaseConfig: firebaseConfig, + awsConfig: awsConfig, dbURL: FIREBASE_DATABASE_URL, clientAppID: FIREBASE_CLIENT_ID, clientApiKey: FIREBASE_CLIENT_API_KEY, diff --git a/src/controllers/StorageController.ts b/src/controllers/StorageController.ts index bec39d91..5bd7487f 100644 --- a/src/controllers/StorageController.ts +++ b/src/controllers/StorageController.ts @@ -2,16 +2,27 @@ import { JsonController, Get, Post, + Res, QueryParam, UploadedFile, BadRequestError, UseBefore, } from 'routing-controllers'; -import { AppUserService, AppUserServiceImpl, StorageService, StorageServiceImpl } from '@Services'; +import { Response } from 'express'; import multer from 'multer'; + +import { AppUserService, AppUserServiceImpl, StorageService, StorageServiceImpl } from '@Services'; import { OfficerAuthMiddleware } from '@Middlewares'; import { OpenAPI } from 'routing-controllers-openapi'; +/** + * File filter object, which checks the file's attributes then does a callback + * depending on whether the file is accepted. + * + * @param req incoming request + * @param file file object from multer + * @param cb callback to accept/reject file + */ const fileFilter = (req: Express.Request, file: Express.Multer.File, cb: Function) => { if ( file.mimetype.includes('image') || @@ -25,6 +36,12 @@ const fileFilter = (req: Express.Request, file: Express.Multer.File, cb: Functio } }; +/** + * File upload options for multer: + * memoryStorage - (where to store files) store in memory as Buffer + * fileFilter - (filter to accept/reject files) see above + * limits - limits on size of file and file name, etc + */ const fileUploadOptions = { storage: multer.memoryStorage(), fileFilter: fileFilter, @@ -53,7 +70,7 @@ export class StorageController { const name = Date.now() + '-' + file.originalname.substring(0, file.originalname.lastIndexOf('.')); try { - return this.storageService.uploadFile(name, file); + return await this.storageService.uploadFile(name, file); } catch (e) { throw new BadRequestError(`Error uploading to storage: ${e.message}`); } @@ -62,10 +79,13 @@ export class StorageController { @Get('/download') @UseBefore(OfficerAuthMiddleware) @OpenAPI({ security: [{ TokenAuth: [] }] }) - async downloadFile(@QueryParam('fileName') fileName: string): Promise { + async downloadFile( + @QueryParam('fileName') fileName: string, + @Res() res: Response + ): Promise { if (fileName) { try { - return this.storageService.downloadFile(fileName); + return await this.storageService.downloadFile(fileName, res); } catch (e) { throw new BadRequestError(`Error loading from storage: ${e.message}`); } @@ -78,7 +98,7 @@ export class StorageController { @OpenAPI({ security: [{ TokenAuth: [] }] }) async getFileIndex(): Promise | null> { try { - return this.storageService.getFileIndex(); + return await this.storageService.getFileIndex(); } catch (e) { throw new BadRequestError(`Error loading from storage: ${e.message}`); } diff --git a/src/controllers/UserController.ts b/src/controllers/UserController.ts index a290fa11..3ced9055 100644 --- a/src/controllers/UserController.ts +++ b/src/controllers/UserController.ts @@ -1,6 +1,7 @@ import { JsonController, Get, + Res, Param, CurrentUser, ForbiddenError, @@ -11,6 +12,7 @@ import { UploadedFile, BadRequestError, } from 'routing-controllers'; +import { Response } from 'express'; import { ResponseSchema, OpenAPI } from 'routing-controllers-openapi'; import { AppUser } from '@Entities'; @@ -259,7 +261,7 @@ export class UserController { throw new BadRequestError('Invalid file'); } try { - return this.resumeService.uploadResume(appUser, file); + return await this.resumeService.uploadResume(appUser, file); } catch (e) { throw new BadRequestError(`Error uploading to storage: ${e.message}`); } @@ -269,17 +271,17 @@ export class UserController { @UseBefore(InducteeAuthMiddleware) @OpenAPI({ security: [{ TokenAuth: [] }] }) async downloadResume( + @Res() res: Response, @Param('userID') userID: number, @CurrentUser({ required: true }) appUser: AppUser ): Promise { if (this.appUserService.isUnauthedUserOrNonOfficer(appUser, userID)) { throw new ForbiddenError(); } - try { - return this.resumeService.downloadResume(appUser); + return await this.resumeService.downloadResume(appUser, res); } catch (e) { - throw new BadRequestError(`Error uploading to storage: ${e.message}`); + throw new BadRequestError(`Error downloading from storage: ${e.message}`); } } } diff --git a/src/loaders/AWSLoader.ts b/src/loaders/AWSLoader.ts new file mode 100644 index 00000000..cfe8d22a --- /dev/null +++ b/src/loaders/AWSLoader.ts @@ -0,0 +1,13 @@ +import AWS from 'aws-sdk'; +import { config } from '../config'; + +export function loadAWS(): void { + AWS.config.update({ + accessKeyId: config.awsConfig.accessKeyId, + secretAccessKey: config.awsConfig.secretKey, + }); +} + +export function loadAWS_S3(): AWS.S3 { + return new AWS.S3({ region: config.awsConfig.bucketRegion }); +} diff --git a/src/loaders/index.ts b/src/loaders/index.ts index b2182fd2..e0facc15 100644 --- a/src/loaders/index.ts +++ b/src/loaders/index.ts @@ -1,2 +1,3 @@ export { loadFirebase } from './FirebaseLoader'; export { loadORM } from './ORMLoader'; +export { loadAWS, loadAWS_S3 } from './AWSLoader'; diff --git a/src/services/LocalStorageService.ts b/src/services/LocalStorageService.ts index 9d14493b..1a63c869 100644 --- a/src/services/LocalStorageService.ts +++ b/src/services/LocalStorageService.ts @@ -1,4 +1,5 @@ import fs from 'fs'; +import { Response } from 'express'; const dir_path = process.cwd() + '/local_fs/'; @@ -7,20 +8,31 @@ if (!fs.existsSync(dir_path)) { } export class LocalStorageService { - async uploadFile( - fileName: string, - fileContent: string | Buffer | Blob | NodeJS.TypedArray | NodeJS.ReadableStream - ): Promise { - fs.writeFile(dir_path + fileName, Buffer.from(fileContent), function(err) { - if (err) { - throw new Error(`Could not write file to ${dir_path + fileName}: ${err.message}`); + async uploadFile(fileName: string, file: Express.Multer.File, options?: Object): Promise { + let fileNameKey = fileName; + if (options) { + if ('appendFileName' in options) { + fileNameKey = `${fileName}_${options['appendFileName']}`; } - }); + } + + fs.writeFile( + dir_path + fileNameKey + file.originalname.substring(file.originalname.lastIndexOf('.')), + file.buffer, + function(err) { + if (err) { + throw new Error(`Could not write file to ${dir_path + fileName}: ${err.message}`); + } + } + ); return `Uploaded file to ${dir_path + fileName}`; } - async downloadFile(fileName: string): Promise { + async downloadFile(fileName: string, res: Response): Promise { try { + res.set({ + 'content-disposition': `attachment; filename="${fileName}"`, + }); return fs.promises.readFile(dir_path + fileName); } catch (e) { throw new Error(`Could not retrieve file from fs at ${dir_path + fileName}: ${e.message}`); diff --git a/src/services/ResumeService.ts b/src/services/ResumeService.ts index 79a357ca..66be4e82 100644 --- a/src/services/ResumeService.ts +++ b/src/services/ResumeService.ts @@ -1,3 +1,5 @@ +import { Response } from 'express'; + import { AppUser } from '@Entities'; import { StorageService, StorageServiceImpl } from '@Services'; @@ -5,25 +7,38 @@ import { StorageService, StorageServiceImpl } from '@Services'; export class ResumeService { constructor(private storageService: StorageService) {} + /** + * Uploads a document as the resume for the current signed in user. Returns + * a string to confirm success, and null otherwise. + * + * @param {AppUser} appUser The current signed in user. + * @param {Express.Multer.File} file The file object passed in with multer. + * @return {Promise} A Promise that indicates whether the upload succeeded. + */ async uploadResume(appUser: AppUser, file: Express.Multer.File): Promise { const storedfileName = `${appUser.firstName}_${appUser.lastName}_Resume`; const options = { appendFileName: appUser.id, }; try { - return this.storageService.uploadFile(storedfileName, file, options).catch(e => { - return null; - }); + return this.storageService.uploadFile(storedfileName, file, options); } catch (e) { console.log(`Error uploading to resume storage: ${e.message}`); return null; } } - async downloadResume(appUser: AppUser): Promise { + /** + * Downloads the resume for the current user. If none exists, returns null. + * Otherwise, returns the file contents in a Buffer. + * + * @param {AppUser} appUser The current user object. + * @param {Response} res The response object. + */ + async downloadResume(appUser: AppUser, res: Response): Promise { try { - const storedfileName = `${appUser.firstName}_${appUser.lastName}_${appUser.id}_Resume`; - return this.storageService.downloadFile(storedfileName); + const storedfileName = `${appUser.firstName}_${appUser.lastName}_Resume_${appUser.id}`; + return this.storageService.downloadFile(storedfileName, res); } catch (e) { console.log(`Could not retrieve file from S3: ${e.message}`); return null; diff --git a/src/services/StorageService.ts b/src/services/StorageService.ts index d05da39e..fb73e5dd 100644 --- a/src/services/StorageService.ts +++ b/src/services/StorageService.ts @@ -1,7 +1,20 @@ -import AWS from 'aws-sdk'; +import { Response } from 'express'; -const s3 = new AWS.S3({ region: process.env.BUCKET_REGION }); +import { loadAWS_S3 } from '../loaders'; +import { config } from '../config'; + +const s3 = loadAWS_S3(); export class StorageService { + /** + * Uploads a file to the S3 bucket, given a filename and the file itself + * through multer. There is also an options object to add details to the + * key in the filesystem while not changing the download name. + * + * @param {string} fileName The fileName as we want it to appear in the fs. + * @param {Express.Multer.File} file A file object uploaded through multer. + * @param {Object} options An options object to configure extra functionality. + * @returns {Promise} A Promise that indicates whether the upload succeeded. + */ async uploadFile( fileName: string, file: Express.Multer.File, @@ -13,8 +26,14 @@ export class StorageService { fileNameKey = `${fileName}_${options['appendFileName']}`; } } + + /** + * We store the key as a string without extension, since when we retrieve we + * do not know the extension of file. But, we can set content disposition and + * mimetype so that when we download, the file will be downloaded correctly. + */ const params = { - Bucket: process.env.BUCKET_NAME, + Bucket: config.awsConfig.bucketName, Key: fileNameKey, Body: file.buffer, ContentDisposition: `attachment; filename="${fileName}${file.originalname.substring( @@ -26,69 +45,88 @@ export class StorageService { return s3 .upload(params) .promise() - .then( - function(data: AWS.S3.ManagedUpload.SendData) { - console.log(`File uploaded successfully. ${data.Location}`); - return 'File uploaded successfully'; - }, - function(e: Error) { - // throw new Error(`Error while uploading file: ${e.message}`); - console.log(`Error while uploading file: ${e.message}`); - return null; - } - ) - .catch(e => { - console.log(`Error while uploading file: ${e.message}`); - return null; + .then(data => { + console.log(`File uploaded successfully. ${data.Location}`); + return 'File uploaded successfully'; }); + // .catch(e => { + // console.log(`Error while uploading file: ${e.message}`); + // return null; + // }); // TODO cleanup } - async downloadFile(fileName: string): Promise { + /** + * Downloads a file from the S3 bucket, using a fileName as the key. If an + * error occurs, returns null, otherwise returns the contents of the file as a + * Buffer. + * + * @param {string} fileName The fileName as it appears in the key of the bucket. + * @param {Response} res The response object. + * @returns {Promise} A Promise that returns the contents of the file in a Buffer. + */ + async downloadFile(fileName: string, res: Response): Promise { try { const params = { - Bucket: process.env.BUCKET_NAME, + Bucket: config.awsConfig.bucketName, Key: fileName, }; return s3 .getObject(params) .promise() - .then(res => Buffer.from(res.Body)) - .catch(e => { - console.log(`Could not retrieve file: ${e.message}`); - return null; + .then(data => { + res.set({ + 'content-disposition': data.ContentDisposition, + 'content-type': data.ContentType, + }); + return Buffer.from(data.Body); }); + // .catch(e => { + // console.log(`Could not retrieve file: ${e.message}`); + // return null; + // }); // TODO cleanup } catch (e) { console.log(`Could not retrieve file from S3: ${e.message}`); return null; } } + /** + * Lists all of the files currently present in the S3 bucket. + * + * @returns {Promise | null>} A Promise that returns the list of all objects in the bucket. + */ async getFileIndex(): Promise | null> { try { const params = { - Bucket: process.env.BUCKET_NAME, + Bucket: config.awsConfig.bucketName, }; return s3 - .listObjectsV2(params, function(err) { - if (err) { - console.log(`Could not retrieve file from S3: ${err.message}`); - return null; - } - }) + .listObjectsV2( + params + // err => { + // if (err) { + // console.log(`Could not retrieve file from S3: ${err.message}`); + // return null; + // } + // } // TODO cleanup + ) .promise() - .then(function(data) { + .then(data => { + if (!data) { + return []; + } const contents = data.Contents; return contents.map(content => { return content.Key; }); - }) - .catch(e => { - console.log(`Could not retrieve file from S3: ${e.message}`); - return null; }); + // .catch(e => { + // console.log(`Could not retrieve file from S3: ${e.message}`); + // return null; + // }); // TODO cleanup } catch (e) { - console.log(`Could not retrieve file from S3: ${e.message}`); + console.log(`Could not retrieve index from S3: ${e.message}`); return null; } } From 2dc739ba3c8a12f1b019dbb50cff2199a80af5e5 Mon Sep 17 00:00:00 2001 From: Eric Ke Date: Sat, 30 Jan 2021 01:01:57 -0800 Subject: [PATCH 07/12] fix option type --- src/services/ResumeService.ts | 2 +- src/services/StorageService.ts | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/services/ResumeService.ts b/src/services/ResumeService.ts index 66be4e82..3c378876 100644 --- a/src/services/ResumeService.ts +++ b/src/services/ResumeService.ts @@ -18,7 +18,7 @@ export class ResumeService { async uploadResume(appUser: AppUser, file: Express.Multer.File): Promise { const storedfileName = `${appUser.firstName}_${appUser.lastName}_Resume`; const options = { - appendFileName: appUser.id, + appendFileName: `${appUser.id}`, }; try { return this.storageService.uploadFile(storedfileName, file, options); diff --git a/src/services/StorageService.ts b/src/services/StorageService.ts index fb73e5dd..c2743b0f 100644 --- a/src/services/StorageService.ts +++ b/src/services/StorageService.ts @@ -4,6 +4,10 @@ import { loadAWS_S3 } from '../loaders'; import { config } from '../config'; const s3 = loadAWS_S3(); + +type UploadOptions = { + appendFileName: string; +}; export class StorageService { /** * Uploads a file to the S3 bucket, given a filename and the file itself @@ -18,7 +22,7 @@ export class StorageService { async uploadFile( fileName: string, file: Express.Multer.File, - options?: Object + options?: UploadOptions ): Promise { let fileNameKey = fileName; if (options) { From 2d06f7ce4b51b0d084c5d93ad8246b620419b47d Mon Sep 17 00:00:00 2001 From: Eric Ke Date: Wed, 3 Feb 2021 19:38:35 -0800 Subject: [PATCH 08/12] cleanup and move err handling to service --- src/controllers/UserController.ts | 14 ++--- src/services/ResumeService.ts | 17 ++---- src/services/StorageService.ts | 87 +++++++++++++------------------ 3 files changed, 43 insertions(+), 75 deletions(-) diff --git a/src/controllers/UserController.ts b/src/controllers/UserController.ts index 3ced9055..ce263a4f 100644 --- a/src/controllers/UserController.ts +++ b/src/controllers/UserController.ts @@ -260,11 +260,8 @@ export class UserController { if (!file) { throw new BadRequestError('Invalid file'); } - try { - return await this.resumeService.uploadResume(appUser, file); - } catch (e) { - throw new BadRequestError(`Error uploading to storage: ${e.message}`); - } + + return this.resumeService.uploadResume(appUser, file); } @Get('/:userID/resume') @@ -278,11 +275,8 @@ export class UserController { if (this.appUserService.isUnauthedUserOrNonOfficer(appUser, userID)) { throw new ForbiddenError(); } - try { - return await this.resumeService.downloadResume(appUser, res); - } catch (e) { - throw new BadRequestError(`Error downloading from storage: ${e.message}`); - } + // this call modifies the response object + return this.resumeService.downloadResume(appUser, res); } } diff --git a/src/services/ResumeService.ts b/src/services/ResumeService.ts index 3c378876..cd5b532d 100644 --- a/src/services/ResumeService.ts +++ b/src/services/ResumeService.ts @@ -20,12 +20,8 @@ export class ResumeService { const options = { appendFileName: `${appUser.id}`, }; - try { - return this.storageService.uploadFile(storedfileName, file, options); - } catch (e) { - console.log(`Error uploading to resume storage: ${e.message}`); - return null; - } + + return this.storageService.uploadFile(storedfileName, file, options); } /** @@ -36,13 +32,8 @@ export class ResumeService { * @param {Response} res The response object. */ async downloadResume(appUser: AppUser, res: Response): Promise { - try { - const storedfileName = `${appUser.firstName}_${appUser.lastName}_Resume_${appUser.id}`; - return this.storageService.downloadFile(storedfileName, res); - } catch (e) { - console.log(`Could not retrieve file from S3: ${e.message}`); - return null; - } + const storedfileName = `${appUser.firstName}_${appUser.lastName}_Resume_${appUser.id}`; + return this.storageService.downloadFile(storedfileName, res); } } diff --git a/src/services/StorageService.ts b/src/services/StorageService.ts index c2743b0f..efe542d2 100644 --- a/src/services/StorageService.ts +++ b/src/services/StorageService.ts @@ -2,6 +2,7 @@ import { Response } from 'express'; import { loadAWS_S3 } from '../loaders'; import { config } from '../config'; +import { BadRequestError } from 'routing-controllers'; const s3 = loadAWS_S3(); @@ -24,39 +25,39 @@ export class StorageService { file: Express.Multer.File, options?: UploadOptions ): Promise { - let fileNameKey = fileName; - if (options) { - if ('appendFileName' in options) { - fileNameKey = `${fileName}_${options['appendFileName']}`; + try { + let fileNameKey = fileName; + if (options) { + if ('appendFileName' in options) { + fileNameKey = `${fileName}_${options['appendFileName']}`; + } } - } - /** - * We store the key as a string without extension, since when we retrieve we - * do not know the extension of file. But, we can set content disposition and - * mimetype so that when we download, the file will be downloaded correctly. - */ - const params = { - Bucket: config.awsConfig.bucketName, - Key: fileNameKey, - Body: file.buffer, - ContentDisposition: `attachment; filename="${fileName}${file.originalname.substring( - file.originalname.lastIndexOf('.') - )}"`, - ContentType: `${file.mimetype}`, - }; + /** + * We store the key as a string without extension, since when we retrieve we + * do not know the extension of file. But, we can set content disposition and + * mimetype so that when we download, the file will be downloaded correctly. + */ + const params = { + Bucket: config.awsConfig.bucketName, + Key: fileNameKey, + Body: file.buffer, + ContentDisposition: `attachment; filename="${fileName}${file.originalname.substring( + file.originalname.lastIndexOf('.') + )}"`, + ContentType: `${file.mimetype}`, + }; - return s3 - .upload(params) - .promise() - .then(data => { - console.log(`File uploaded successfully. ${data.Location}`); - return 'File uploaded successfully'; - }); - // .catch(e => { - // console.log(`Error while uploading file: ${e.message}`); - // return null; - // }); // TODO cleanup + return await s3 + .upload(params) + .promise() + .then(data => { + console.log(`File uploaded successfully. ${data.Location}`); + return 'File uploaded successfully'; + }); + } catch (e) { + throw new BadRequestError(`Error downloading from storage: ${e.message}`); + } } /** @@ -75,7 +76,7 @@ export class StorageService { Key: fileName, }; - return s3 + return await s3 .getObject(params) .promise() .then(data => { @@ -85,13 +86,8 @@ export class StorageService { }); return Buffer.from(data.Body); }); - // .catch(e => { - // console.log(`Could not retrieve file: ${e.message}`); - // return null; - // }); // TODO cleanup } catch (e) { - console.log(`Could not retrieve file from S3: ${e.message}`); - return null; + throw new BadRequestError(`Error downloading from storage: ${e.message}`); } } @@ -106,15 +102,7 @@ export class StorageService { Bucket: config.awsConfig.bucketName, }; return s3 - .listObjectsV2( - params - // err => { - // if (err) { - // console.log(`Could not retrieve file from S3: ${err.message}`); - // return null; - // } - // } // TODO cleanup - ) + .listObjectsV2(params) .promise() .then(data => { if (!data) { @@ -125,13 +113,8 @@ export class StorageService { return content.Key; }); }); - // .catch(e => { - // console.log(`Could not retrieve file from S3: ${e.message}`); - // return null; - // }); // TODO cleanup } catch (e) { - console.log(`Could not retrieve index from S3: ${e.message}`); - return null; + throw new BadRequestError(`Error downloading from storage: ${e.message}`); } } } From 34b27f2655cc9c73673ed188844782440fc2a075 Mon Sep 17 00:00:00 2001 From: Eric Ke Date: Sat, 6 Feb 2021 11:45:34 -0800 Subject: [PATCH 09/12] move around options and cleanup --- src/controllers/UserController.ts | 22 ++-------------------- src/services/ResumeService.ts | 20 +++++++++++++++++++- src/services/StorageService.ts | 23 ++++++++++++----------- src/services/index.ts | 2 +- 4 files changed, 34 insertions(+), 33 deletions(-) diff --git a/src/controllers/UserController.ts b/src/controllers/UserController.ts index ce263a4f..6c6e1bcc 100644 --- a/src/controllers/UserController.ts +++ b/src/controllers/UserController.ts @@ -23,6 +23,7 @@ import { AttendanceServiceImpl, ResumeService, ResumeServiceImpl, + resumeFileUploadOptions, } from '@Services'; import { AppUserPostRequest, @@ -40,25 +41,6 @@ import { import { AppUserMapper, AppUserMapperImpl } from '@Mappers'; import { InducteeAuthMiddleware, MemberAuthMiddleware, OfficerAuthMiddleware } from '@Middlewares'; import { formatISO } from 'date-fns'; -import multer from 'multer'; - -const fileFilter = (req: Express.Request, file: Express.Multer.File, cb: Function) => { - if (file.mimetype.includes('pdf') || file.mimetype.includes('word')) { - cb(null, true); - } else { - console.log('Invalid file type'); - cb(null, false); - } -}; - -const fileUploadOptions = { - storage: multer.memoryStorage(), - fileFilter: fileFilter, - limits: { - fieldNameSize: 255, - fileSize: 1024 * 1024 * 5, - }, -}; @JsonController('/api/users') export class UserController { @@ -250,7 +232,7 @@ export class UserController { @Param('userID') userID: number, @CurrentUser({ required: true }) appUser: AppUser, @UploadedFile('file', { - options: fileUploadOptions, + options: resumeFileUploadOptions, }) file: Express.Multer.File ): Promise { diff --git a/src/services/ResumeService.ts b/src/services/ResumeService.ts index cd5b532d..290fb29c 100644 --- a/src/services/ResumeService.ts +++ b/src/services/ResumeService.ts @@ -1,9 +1,27 @@ import { Response } from 'express'; +import multer from 'multer'; import { AppUser } from '@Entities'; - import { StorageService, StorageServiceImpl } from '@Services'; +const fileFilter = (req: Express.Request, file: Express.Multer.File, cb: Function): void => { + if (file.mimetype.includes('pdf')) { + cb(null, true); + } else { + console.log('Invalid file type'); + cb(null, false); + } +}; + +export const resumeFileUploadOptions = { + storage: multer.memoryStorage(), + fileFilter: fileFilter, + limits: { + fieldNameSize: 255, + fileSize: 1024 * 1024 * 5, + }, +}; + export class ResumeService { constructor(private storageService: StorageService) {} diff --git a/src/services/StorageService.ts b/src/services/StorageService.ts index efe542d2..d0d69863 100644 --- a/src/services/StorageService.ts +++ b/src/services/StorageService.ts @@ -25,6 +25,7 @@ export class StorageService { file: Express.Multer.File, options?: UploadOptions ): Promise { + let params; try { let fileNameKey = fileName; if (options) { @@ -38,7 +39,7 @@ export class StorageService { * do not know the extension of file. But, we can set content disposition and * mimetype so that when we download, the file will be downloaded correctly. */ - const params = { + params = { Bucket: config.awsConfig.bucketName, Key: fileNameKey, Body: file.buffer, @@ -47,7 +48,11 @@ export class StorageService { )}"`, ContentType: `${file.mimetype}`, }; + } catch (e) { + throw new BadRequestError(`Error in storage parameters: ${e.message}`); + } + try { return await s3 .upload(params) .promise() @@ -76,16 +81,12 @@ export class StorageService { Key: fileName, }; - return await s3 - .getObject(params) - .promise() - .then(data => { - res.set({ - 'content-disposition': data.ContentDisposition, - 'content-type': data.ContentType, - }); - return Buffer.from(data.Body); - }); + const data = await s3.getObject(params).promise(); + res.set({ + 'content-disposition': data.ContentDisposition, + 'content-type': data.ContentType, + }); + return Buffer.from(data.Body); } catch (e) { throw new BadRequestError(`Error downloading from storage: ${e.message}`); } diff --git a/src/services/index.ts b/src/services/index.ts index c6b87c99..081bdacb 100644 --- a/src/services/index.ts +++ b/src/services/index.ts @@ -8,4 +8,4 @@ export { AccountService, AccountServiceImpl } from './AccountService'; export { StorageService, StorageServiceImpl } from './StorageService'; export { InductionClassService, InductionClassServiceImpl } from './InductionClassService'; export { LocalStorageService, LocalStorageServiceImpl } from './LocalStorageService'; -export { ResumeService, ResumeServiceImpl } from './ResumeService'; +export { ResumeService, ResumeServiceImpl, resumeFileUploadOptions } from './ResumeService'; From 8d4ed479c1c626ca59472fe286f92fe19f9be6d3 Mon Sep 17 00:00:00 2001 From: Eric Ke Date: Sat, 6 Feb 2021 12:02:17 -0800 Subject: [PATCH 10/12] move demo storage controller to docs --- .../storage/StorageController.md | 5 +++++ 1 file changed, 5 insertions(+) rename src/controllers/StorageController.ts => guides/storage/StorageController.md (92%) diff --git a/src/controllers/StorageController.ts b/guides/storage/StorageController.md similarity index 92% rename from src/controllers/StorageController.ts rename to guides/storage/StorageController.md index 5bd7487f..015f1494 100644 --- a/src/controllers/StorageController.ts +++ b/guides/storage/StorageController.md @@ -1,3 +1,7 @@ +Here is an example of a storage controller, using the storage service to upload +and download files from a storage filesystem. + +```js import { JsonController, Get, @@ -106,3 +110,4 @@ export class StorageController { } export const StorageControllerImpl = new StorageController(StorageServiceImpl, AppUserServiceImpl); +``` From 7c6af1102be0a58d74248054efcd9c9c97fbd886 Mon Sep 17 00:00:00 2001 From: Eric Ke Date: Wed, 10 Feb 2021 19:07:30 -0800 Subject: [PATCH 11/12] remove storage controller ref --- src/controllers/index.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/controllers/index.ts b/src/controllers/index.ts index 72e02826..d6ce8455 100644 --- a/src/controllers/index.ts +++ b/src/controllers/index.ts @@ -2,7 +2,6 @@ import { EventController, EventControllerImpl } from './EventController'; import { UserController, UserControllerImpl } from './UserController'; import { AuthController, AuthControllerImpl } from './AuthController'; import { TeapotController, TeapotControllerImpl } from './TeapotController'; -import { StorageController, StorageControllerImpl } from './StorageController'; import { InductionClassController, InductionClassControllerImpl } from './InductionClassController'; import { PointsController, PointsControllerImpl } from './PointsController'; @@ -11,7 +10,6 @@ export const controllers = [ UserController, AuthController, TeapotController, - StorageController, InductionClassController, PointsController, ]; @@ -23,7 +21,6 @@ controllerMap.set(EventController.name, EventControllerImpl); controllerMap.set(UserController.name, UserControllerImpl); controllerMap.set(AuthController.name, AuthControllerImpl); controllerMap.set(TeapotController.name, TeapotControllerImpl); -controllerMap.set(StorageController.name, StorageControllerImpl); controllerMap.set(InductionClassController.name, InductionClassControllerImpl); controllerMap.set(PointsController.name, PointsControllerImpl); From 226f75c6debaa16383d63e6d9416c17ed759852c Mon Sep 17 00:00:00 2001 From: Eric Ke Date: Sat, 13 Feb 2021 22:12:41 -0800 Subject: [PATCH 12/12] selective import for aws --- src/loaders/AWSLoader.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/loaders/AWSLoader.ts b/src/loaders/AWSLoader.ts index cfe8d22a..94963b9f 100644 --- a/src/loaders/AWSLoader.ts +++ b/src/loaders/AWSLoader.ts @@ -1,4 +1,5 @@ -import AWS from 'aws-sdk'; +import AWS from 'aws-sdk/global'; +import S3 from 'aws-sdk/clients/s3'; import { config } from '../config'; export function loadAWS(): void { @@ -8,6 +9,6 @@ export function loadAWS(): void { }); } -export function loadAWS_S3(): AWS.S3 { - return new AWS.S3({ region: config.awsConfig.bucketRegion }); +export function loadAWS_S3(): S3 { + return new S3({ region: config.awsConfig.bucketRegion }); }