From 5eab22bc7d2712db895115810f6f986aacd5de1a Mon Sep 17 00:00:00 2001 From: Ivan Alglave <ivanalglave@outlook.fr> Date: Tue, 13 Dec 2022 22:47:31 +0100 Subject: [PATCH] fix: uploading and downloading pdf now works --- .gitignore | 2 +- package-lock.json | 46 ++++++++++++++++++++--- package.json | 1 + src/app.module.ts | 2 + src/config/config.model.ts | 12 ++++-- src/config/config.prod.json | 5 ++- src/config/config.template.json | 5 ++- src/config/index.ts | 3 ++ src/internships/dao/internships.dao.ts | 1 + src/internships/internships.controller.ts | 30 +++++---------- src/main.ts | 2 +- src/resources/resources.controller.ts | 31 +++++++++++++++ src/resources/resources.module.ts | 8 ++++ src/shared/HttpError.ts | 1 + 14 files changed, 116 insertions(+), 33 deletions(-) create mode 100644 src/resources/resources.controller.ts create mode 100644 src/resources/resources.module.ts diff --git a/.gitignore b/.gitignore index d1486f9..115e10f 100644 --- a/.gitignore +++ b/.gitignore @@ -107,4 +107,4 @@ dist config.json # file where we store pdf -internship-agreements/ \ No newline at end of file +files/* diff --git a/package-lock.json b/package-lock.json index c34df50..b564a19 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "class-validator": "^0.13.2", "fastify": "^4.9.2", "mongoose": "^6.7.2", + "multer": "^1.4.5-lts.1", "reflect-metadata": "^0.1.13", "rimraf": "^3.0.2", "rxjs": "^7.2.0" @@ -2661,6 +2662,23 @@ "@nestjs/core": "^9.0.0" } }, + "node_modules/@nestjs/platform-express/node_modules/multer": { + "version": "1.4.4-lts.1", + "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.4-lts.1.tgz", + "integrity": "sha512-WeSGziVj6+Z2/MwQo3GvqzgR+9Uc+qt8SwHKh3gvNPiISKfsMfG4SvCOFYlxxgkXt7yIV2i1yczehm0EOKIxIg==", + "dependencies": { + "append-field": "^1.0.0", + "busboy": "^1.0.0", + "concat-stream": "^1.5.2", + "mkdirp": "^0.5.4", + "object-assign": "^4.1.1", + "type-is": "^1.6.4", + "xtend": "^4.0.0" + }, + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/@nestjs/schematics": { "version": "9.0.3", "resolved": "https://registry.npmjs.org/@nestjs/schematics/-/schematics-9.0.3.tgz", @@ -7518,9 +7536,9 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node_modules/multer": { - "version": "1.4.4-lts.1", - "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.4-lts.1.tgz", - "integrity": "sha512-WeSGziVj6+Z2/MwQo3GvqzgR+9Uc+qt8SwHKh3gvNPiISKfsMfG4SvCOFYlxxgkXt7yIV2i1yczehm0EOKIxIg==", + "version": "1.4.5-lts.1", + "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.1.tgz", + "integrity": "sha512-ywPWvcDMeH+z9gQq5qYHCCy+ethsk4goepZ45GLD63fOu0YcNecQxi64nDs3qluZB+murG3/D4dJ7+dGctcCQQ==", "dependencies": { "append-field": "^1.0.0", "busboy": "^1.0.0", @@ -12039,6 +12057,22 @@ "express": "4.18.2", "multer": "1.4.4-lts.1", "tslib": "2.4.1" + }, + "dependencies": { + "multer": { + "version": "1.4.4-lts.1", + "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.4-lts.1.tgz", + "integrity": "sha512-WeSGziVj6+Z2/MwQo3GvqzgR+9Uc+qt8SwHKh3gvNPiISKfsMfG4SvCOFYlxxgkXt7yIV2i1yczehm0EOKIxIg==", + "requires": { + "append-field": "^1.0.0", + "busboy": "^1.0.0", + "concat-stream": "^1.5.2", + "mkdirp": "^0.5.4", + "object-assign": "^4.1.1", + "type-is": "^1.6.4", + "xtend": "^4.0.0" + } + } } }, "@nestjs/schematics": { @@ -15754,9 +15788,9 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "multer": { - "version": "1.4.4-lts.1", - "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.4-lts.1.tgz", - "integrity": "sha512-WeSGziVj6+Z2/MwQo3GvqzgR+9Uc+qt8SwHKh3gvNPiISKfsMfG4SvCOFYlxxgkXt7yIV2i1yczehm0EOKIxIg==", + "version": "1.4.5-lts.1", + "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.1.tgz", + "integrity": "sha512-ywPWvcDMeH+z9gQq5qYHCCy+ethsk4goepZ45GLD63fOu0YcNecQxi64nDs3qluZB+murG3/D4dJ7+dGctcCQQ==", "requires": { "append-field": "^1.0.0", "busboy": "^1.0.0", diff --git a/package.json b/package.json index 07f72a8..30ef151 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "class-validator": "^0.13.2", "fastify": "^4.9.2", "mongoose": "^6.7.2", + "multer": "^1.4.5-lts.1", "reflect-metadata": "^0.1.13", "rimraf": "^3.0.2", "rxjs": "^7.2.0" diff --git a/src/app.module.ts b/src/app.module.ts index e794431..9caaddf 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -4,12 +4,14 @@ import config from './config'; import { PeopleModule } from './people/people.module'; import { GroupsModule } from './groups/groups.module'; import { InternshipsModule } from './internships/internships.module'; +import { ResourcesModule } from './resources/resources.module'; @Module({ imports: [ PeopleModule, GroupsModule, InternshipsModule, + ResourcesModule, MongooseModule.forRoot(config.mongodb.uri), ], }) diff --git a/src/config/config.model.ts b/src/config/config.model.ts index 991a78d..b89c817 100644 --- a/src/config/config.model.ts +++ b/src/config/config.model.ts @@ -1,13 +1,19 @@ -export interface IServerConfig { - url: string; +interface IServerConfig { + uri: string; port: number; } -export interface IMongodbConfig { +interface IResources { + root: string; + agreements: string; +} + +interface IMongodbConfig { uri: string; } export interface IConfig { server: IServerConfig; + resources: IResources; mongodb: IMongodbConfig; } diff --git a/src/config/config.prod.json b/src/config/config.prod.json index 9a88b14..68a5550 100644 --- a/src/config/config.prod.json +++ b/src/config/config.prod.json @@ -1,8 +1,11 @@ { "server": { - "url": "localhost", + "uri": "localhost", "port": 3001 }, + "resources": { + "internshipAgreements": "internship-agreements" + }, "mongodb": { "uri": "mongodb://localhost:27017/internship-manager" } diff --git a/src/config/config.template.json b/src/config/config.template.json index 9a88b14..68a5550 100644 --- a/src/config/config.template.json +++ b/src/config/config.template.json @@ -1,8 +1,11 @@ { "server": { - "url": "localhost", + "uri": "localhost", "port": 3001 }, + "resources": { + "internshipAgreements": "internship-agreements" + }, "mongodb": { "uri": "mongodb://localhost:27017/internship-manager" } diff --git a/src/config/index.ts b/src/config/index.ts index 2b462df..7028175 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -8,6 +8,7 @@ const CONFIG_DEV = 'config.json'; const CONFIG_PROD = 'config.prod.json'; let config: IConfig; +console.log(process.env.NODE_ENV); // Load config based on env switch (process.env.NODE_ENV) { case 'dev': @@ -30,5 +31,7 @@ switch (process.env.NODE_ENV) { exit(-1); } +console.log(config); + // Export config export default config; diff --git a/src/internships/dao/internships.dao.ts b/src/internships/dao/internships.dao.ts index 6429599..22a0817 100644 --- a/src/internships/dao/internships.dao.ts +++ b/src/internships/dao/internships.dao.ts @@ -120,6 +120,7 @@ export class InternshipDao { content: string | boolean, ): Promise<Internship | void> => new Promise((resolve, reject) => { + console.log('%s/%s: {%s}', studentId, state, content); if (!isStateValid(state)) reject(BAD_REQUEST); console.log(content); let nextState: string, contentHolder: string; diff --git a/src/internships/internships.controller.ts b/src/internships/internships.controller.ts index 9b5cdb2..3db74f0 100644 --- a/src/internships/internships.controller.ts +++ b/src/internships/internships.controller.ts @@ -16,7 +16,6 @@ import { CreateInternshipDto } from './dto/create-internship.dto'; import { InternshipEntity } from './entities/internship.entity'; import { InternshipService } from './internships.service'; import { FileInterceptor } from '@nestjs/platform-express'; -import * as path from 'path'; import { STATE_RESPONSIBLE_ACCEPTS_INTERNSHIP_INFORMATION, STATE_STUDENT_ENTERS_INTERNSHIP_INFORMATION, @@ -24,6 +23,7 @@ import { import config from 'src/config'; import { Optional } from '@nestjs/common/decorators'; import { v4 } from 'uuid'; +import { diskStorage } from 'multer'; @Controller('internships') @UseInterceptors(HttpInterceptor) @@ -57,14 +57,16 @@ export class InternshipsController { return this._internshipsService.update(params.studentId, internshipDto); } + // uploads even if invalid state... @Put(':studentId/:state') @UseInterceptors( FileInterceptor('pdf', { - dest: './internship-agreements', - fileFilter: (req, file, cb) => { - file.filename = `${v4()}.pdf`; - cb(null, true); - }, + storage: diskStorage({ + destination: './files', + filename: (_req, _file, cb) => { + return cb(null, `${v4()}.pdf`); + }, + }), }), ) updateState( @@ -74,12 +76,6 @@ export class InternshipsController { ): Promise<InternshipEntity | void> { if (!InternshipStates.isStateValid(params.state)) throw BAD_TRACKING_STATE(params.state); - - // AMINE : Handle PDF file upload -> save file in /pdf/ folder and set content as local file URL. In case of step with no file, set content as true/false - - //case where there isn't a file - //Deux premiers états - if ( params.state === STATE_STUDENT_ENTERS_INTERNSHIP_INFORMATION || params.state === STATE_RESPONSIBLE_ACCEPTS_INTERNSHIP_INFORMATION @@ -92,18 +88,12 @@ export class InternshipsController { ); } - //case where there is a file if (!file) throw BAD_REQUEST; - console.log(file); - console.log(config); + console.log(params.state); return this._internshipsService.updateTracking( params.studentId, params.state, - path.join( - `${config.server.url}:${config.server.port}`, - file.path, - file.fieldname, - ), + `${config.server.uri}:${config.server.port}/resources/agreements/${file.filename}`, ); } diff --git a/src/main.ts b/src/main.ts index c5c630c..bffb6ea 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,7 +1,7 @@ +import config from './config'; import { ValidationPipe } from '@nestjs/common'; import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; -import config from './config'; async function bootstrap() { const env = process.env.NODE_ENV; diff --git a/src/resources/resources.controller.ts b/src/resources/resources.controller.ts new file mode 100644 index 0000000..afd5df9 --- /dev/null +++ b/src/resources/resources.controller.ts @@ -0,0 +1,31 @@ +import { + Controller, + Get, + Param, + UseInterceptors, + Res, + StreamableFile, +} from '@nestjs/common'; +import { NOT_FOUND } from 'src/shared/HttpError'; +import { HttpInterceptor } from '../interceptors/http.interceptor'; +import { Response } from 'express'; +import { createReadStream, existsSync } from 'fs'; + +@Controller('resources') +@UseInterceptors(HttpInterceptor) +export class ResourcesController { + @Get('agreements/:filename') + serveAgreement( + @Param('filename') filename, + @Res({ passthrough: true }) res: Response, + ): StreamableFile { + const filepath = `files\\${filename}`; + if (!existsSync(filepath)) throw NOT_FOUND; + const file = createReadStream(filepath); + res.set({ + 'Content-Type': 'application/pdf', + 'Content-Disposition': 'attachment; filename="agreement.pdf"', + }); + return new StreamableFile(file); + } +} diff --git a/src/resources/resources.module.ts b/src/resources/resources.module.ts new file mode 100644 index 0000000..6cadff3 --- /dev/null +++ b/src/resources/resources.module.ts @@ -0,0 +1,8 @@ +import { Module, Logger } from '@nestjs/common'; +import { ResourcesController } from './resources.controller'; + +@Module({ + controllers: [ResourcesController], + providers: [Logger], +}) +export class ResourcesModule {} diff --git a/src/shared/HttpError.ts b/src/shared/HttpError.ts index aa1d356..200448b 100644 --- a/src/shared/HttpError.ts +++ b/src/shared/HttpError.ts @@ -11,6 +11,7 @@ export const NOT_FOUND = new NotFoundException(); export const CONFLICT = new ConflictException(); export const BAD_REQUEST = new BadRequestException(); export const INTERNAL = new InternalServerErrorException(); +export const UNPROCESSABLE_ENTITY = new UnprocessableEntityException(); export const BAD_TRACKING_STATE = (badState: string) => new UnprocessableEntityException(`Unknown state [${badState}]`); export const CUSTOM = (reason: string, errorStatus: number) => -- GitLab