diff --git a/.gitignore b/.gitignore index d1486f960de29db5bb18799144bd8344b6e33df5..115e10fd00b924aa8a3c09305afe7b64f58fca24 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 c34df5088f99f3c74fb8c909c06b53201964754a..b564a19eb655bce0fd5cf66634f26f884b2e6dd4 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 07f72a8dae39be21efc9374d55d3bb4852d18663..30ef1513ed479b4aeb7b0fe0f141174436ffed1f 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 e794431d67f12d769b4c16cfd6d8d637472b597e..9caaddff9ad16885e419deb31e006254adf67e21 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 991a78d063473a119a9204ca30abd715cc998bea..b89c81748bf1996838ee041549c23b0c9ef654e9 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 9a88b14e3319f74a2aa692ce4a4b914164137348..68a5550e9693089ed1d786acbcdeb74187f39d96 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 9a88b14e3319f74a2aa692ce4a4b914164137348..68a5550e9693089ed1d786acbcdeb74187f39d96 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 2b462dffe9a686b059ee262aa8aae20ed4d5e091..70281753480b8a73a4f2d8404d9f1d42c846f961 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 642959958327480c152cc2a3b6ad09bebecf425a..22a08178f9e288cc68691af8cf6e3a570ea36dfa 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 9b5cdb284d435628b98f655ac4a60364c4d3f4f2..3db74f04f2bddd9213935ca4e5cde9c0590260e7 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 c5c630c00749273794025342b1f60b6be049f12f..bffb6eaf43e8ba958fa978ecfa619bb646febfac 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 0000000000000000000000000000000000000000..afd5df97a65ed6741e182a9a5984392acaa1bf95 --- /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 0000000000000000000000000000000000000000..6cadff34ed1d90667bb42bdb003a39f3d876748b --- /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 aa1d3565c01e62554e91ff4537c67ecc952ff961..200448b6f55e01865f8f8350e4fdb9dfcfcfc028 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) =>