diff --git a/.gitignore b/.gitignore index aff1045b47f66ef6617345db3c3e05e98a0bb0a4..a63a3ca18744344c22da1189818e3c0a80004bce 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,7 @@ coverage node_modules npm-debug.log + +*.env + +!template.env diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ebfb47e1e0700cd8ce19033b3b1606be30c1a529..7f2c2f41ee5d8e9183dfe9042221562c595bad0a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -2,18 +2,48 @@ stages: - build - deploy -build: +build_development: stage: build only: - master - - sandbox script: - - docker-compose build --no-cache + - export NODE_ENV=DEV + - export SERVICE_EMAIL_PORT=3001 + - export RABBITMQ_STANDARD_PORT=5672 + - export RABBITMQ_INTERFACE_PORT=15672 + - docker-compose --project-name service-email-dev build -deploy: +deploy_development: stage: deploy only: - master - - sandbox script: - - docker-compose up -d + - export NODE_ENV=DEV + - export SERVICE_EMAIL_PORT=3001 + - export RABBITMQ_STANDARD_PORT=5672 + - export RABBITMQ_INTERFACE_PORT=15672 + - docker-compose --project-name service-email-dev up -d + +build_staging: + stage: build + only: + - staging + script: + - export NODE_ENV=REC + - export SERVICE_EMAIL_PORT=3101 + - export RABBITMQ_STANDARD_PORT=5673 + - export RABBITMQ_INTERFACE_PORT=15673 + - sed -i 's/DEV_/REC_/g' docker-compose.yml + - docker-compose --project-name service-email-rec build + +deploy_staging: + stage: deploy + only: + - staging + script: + - export NODE_ENV=REC + - export SERVICE_EMAIL_PORT=3101 + - export RABBITMQ_STANDARD_PORT=5673 + - export RABBITMQ_INTERFACE_PORT=15673 + - sed -i 's/DEV_/REC_/g' docker-compose.yml + - docker-compose --project-name service-email-rec up -d diff --git a/docker-compose.yml b/docker-compose.yml index 7bf70fdad199c5348a20706512172e794ca7ac3e..a2643e49d1131d57b0d0f4d712f7beb6569fed10 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,21 +3,23 @@ version: '3.1' services: service-email: build: . - container_name: service-email + container_name: service-email-${NODE_ENV} ports: - - 3001:3000 + - ${SERVICE_EMAIL_PORT}:3000 environment: - - ADMIN_EMAIL=${ADMIN_EMAIL} + - ADMIN_EMAILS=${DEV_ADMIN_EMAILS} + - RABBITMQ_USER=${DEV_RABBITMQ_USER} + - RABBITMQ_PASSWORD=${DEV_RABBITMQ_PASSWORD} restart: unless-stopped rabbitmq: image: 'rabbitmq:3-management-alpine' container_name: rabbitmq ports: - - 5672:5672 # standar port for communication - - 15672:15672 # graphique interface + - ${RABBITMQ_STANDARD_PORT}:5672 # standar port for communication + - ${RABBITMQ_INTERFACE_PORT}:15672 # graphique interface environment: - - RABBITMQ_DEFAULT_USER=${RABBITMQ_USERNAME} - - RABBITMQ_DEFAULT_PASS=${RABBITMQ_PASSWORD} + - RABBITMQ_DEFAULT_USER=${DEV_RABBITMQ_USER} + - RABBITMQ_DEFAULT_PASS=${DEV_RABBITMQ_PASSWORD} volumes: - rabbitmqPersistence:/var/lib/rabbitmq restart: unless-stopped diff --git a/package-lock.json b/package-lock.json index 33a626deec4f30d5c7dea41edcfecd1a55de9f85..d03358c580cd31aabd2de8234f6c554f7fe0a99e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2499,6 +2499,11 @@ "is-obj": "^1.0.0" } }, + "dotenv": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-6.1.0.tgz", + "integrity": "sha512-/veDn2ztgRlB7gKmE3i9f6CmDIyXAy6d5nBq+whO9SLX+Zs1sXEgFLPi+aSuWqUuusMfbi84fT8j34fs1HaYUw==" + }, "duplexer": { "version": "0.1.1", "resolved": "http://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", @@ -3396,14 +3401,12 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -3418,20 +3421,17 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -3548,8 +3548,7 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.5", @@ -3561,7 +3560,6 @@ "version": "1.0.0", "bundled": true, "dev": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -3576,7 +3574,6 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -3584,14 +3581,12 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "minipass": { "version": "2.2.4", "bundled": true, "dev": true, - "optional": true, "requires": { "safe-buffer": "^5.1.1", "yallist": "^3.0.0" @@ -3610,7 +3605,6 @@ "version": "0.5.1", "bundled": true, "dev": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -3691,8 +3685,7 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", @@ -3704,7 +3697,6 @@ "version": "1.4.0", "bundled": true, "dev": true, - "optional": true, "requires": { "wrappy": "1" } @@ -3826,7 +3818,6 @@ "version": "1.0.2", "bundled": true, "dev": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", diff --git a/package.json b/package.json index bb327ffc1a61f13dd1a5b9f6e6decb11a5183882..0ecc70822e65f74cbbf5ed4ab01c8fbc98136e1a 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "@nestjs/common": "^5.1.0", "@nestjs/core": "^5.1.0", "@nestjs/swagger": "^2.5.1", + "dotenv": "^6.1.0", "amqplib": "^0.5.2", "class-transformer": "^0.1.9", "class-validator": "^0.9.1", diff --git a/src/app.module.ts b/src/app.module.ts index 293d8cd67d71350b5451b72b1242ca70e0d09103..c8a2981e9007e4f742e9bdd7ae221a9f929b845b 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -1,8 +1,9 @@ import { Module } from '@nestjs/common'; import { EmailModule } from 'email/email.module'; +import { ConfigModule } from 'configuration/config.module'; @Module({ - imports: [EmailModule], + imports: [ConfigModule, EmailModule], controllers: [], providers: [], }) diff --git a/src/configuration/config.module.ts b/src/configuration/config.module.ts new file mode 100644 index 0000000000000000000000000000000000000000..aa4bcfac317d3bcd65bed3a1a976ebc7f7ae344f --- /dev/null +++ b/src/configuration/config.module.ts @@ -0,0 +1,15 @@ + +import { Module, Global } from '@nestjs/common'; +import { ConfigService } from './config.service'; + +@Global() +@Module({ + providers: [ + { + provide: ConfigService, + useValue: new ConfigService(), + }, + ], + exports: [ConfigService], +}) +export class ConfigModule {} \ No newline at end of file diff --git a/src/configuration/config.service.ts b/src/configuration/config.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..665132c3d17d66e193b90704a212c3b165dde9c0 --- /dev/null +++ b/src/configuration/config.service.ts @@ -0,0 +1,29 @@ + +import * as dotenv from 'dotenv'; +import * as fs from 'fs'; +import { config } from './config'; + +export class ConfigService { + private _config = config; + + constructor() { + // Only for development purpose, set environment variable based on the env file provided + if (process.env.NODE_ENV === 'LOCAL') { + const envConfig = dotenv.parse(fs.readFileSync(`./src/configuration/${process.env.NODE_ENV}.env`)); + for (const k in envConfig) { + if (envConfig.hasOwnProperty(k)) { + process.env[k] = envConfig[k]; + } + } + } + + // Initializing conf with values from var env + this._config.rabbitMQ.user = process.env.RABBITMQ_USER; + this._config.rabbitMQ.password = process.env.RABBITMQ_PASSWORD; + this._config.adminEmails = process.env.ADMIN_EMAILS.split(','); + } + + get config() { + return this._config; + } +} \ No newline at end of file diff --git a/src/configuration/config.ts b/src/configuration/config.ts new file mode 100644 index 0000000000000000000000000000000000000000..66d1927c920f3bed971b5f9901948cb08b44c094 --- /dev/null +++ b/src/configuration/config.ts @@ -0,0 +1,10 @@ +export const config = { + rabbitMQ: { + user: null, + password: null, + host: 'rabbitmq', + port: '5672', + }, + mailerQueue: 'portail-data-send-email', + adminEmails: [], +}; \ No newline at end of file diff --git a/src/configuration/template.env b/src/configuration/template.env new file mode 100644 index 0000000000000000000000000000000000000000..667471d3cee6becea56289e49c0b931f63442883 --- /dev/null +++ b/src/configuration/template.env @@ -0,0 +1,3 @@ +RABBITMQ_USER= +RABBITMQ_PASSWORD= +ADMIN_EMAILS= \ No newline at end of file diff --git a/src/email/email.controller.ts b/src/email/email.controller.ts index 58b743952e76a6af36ffe223ff00aa1870853a87..c4bbdb875f41aa412ddc110d6c3614a6e6e0a9fe 100644 --- a/src/email/email.controller.ts +++ b/src/email/email.controller.ts @@ -1,8 +1,9 @@ import { Controller, Post, Body, Res } from '@nestjs/common'; import { ContactForm } from './email'; import { EmailService } from './email.service'; -import { ApiBadRequestResponse, ApiOkResponse } from '@nestjs/swagger'; +import { ApiBadRequestResponse, ApiOkResponse, ApiUseTags } from '@nestjs/swagger'; +@ApiUseTags('email') @Controller('email') export class EmailController { @@ -16,7 +17,7 @@ export class EmailController { create(@Body() contactForm: ContactForm, @Res() res) { this.emailService.send(contactForm, (err, result) => { if (err !== null || result !== true) { - res.status(400).send({error: err}); + res.status(err.status).send({error: err.message}); } else { res.status(200).send(); } diff --git a/src/email/email.service.ts b/src/email/email.service.ts index 3480d675714a1d3d00796eecf5099f9b279d3233..4170359b52a96787a28ebc3cf079ba86eac26be4 100644 --- a/src/email/email.service.ts +++ b/src/email/email.service.ts @@ -1,34 +1,36 @@ import { Injectable, Logger } from '@nestjs/common'; import * as amqp from 'amqplib/callback_api'; import { ContactForm, Email } from './email'; +import { config } from 'configuration/config'; @Injectable() export class EmailService { send(contactForm: ContactForm, done) { - const rabbitmqUrl = 'amqp://user:password123@rabbitmq:5672'; - const mailerQueue = 'portail-data-send-email'; + const rabbitmqUrl = `amqp://${config.rabbitMQ.user}:${config.rabbitMQ.password}@${config.rabbitMQ.host}:${config.rabbitMQ.port}`; + const mailerQueue = config.mailerQueue; const email = new Email(); email.from = `${contactForm.firstname} ${contactForm.lastname} ${contactForm.from}`; - email.to = [process.env.ADMIN_EMAIL]; + email.to = config.adminEmails; email.subject = contactForm.subject; email.text = contactForm.text; + Logger.log('[-] send method'); Logger.log(email); // Connect to rabbitmq amqp.connect(rabbitmqUrl, (err, conn) => { if (err != null) { - Logger.log(err); - done('Couldn\'t connect to rabbitMQ.'); + Logger.error(' [x] Error connecting to RabbitMQ: ', err); + done({ message: 'Could not connect to rabbitMQ.', status: 500}); } else { // Create a communication channel conn.createChannel((error, ch) => { if (error != null) { - Logger.log(error); - done('Couldn\'t create channel.'); + Logger.error(' [x] Error creating channel: ', error); + done({ message: 'Could not create channel.', status: 500}); } else { // Stringify and bufferise message const buffer = Buffer.from(JSON.stringify(email)); @@ -37,7 +39,7 @@ export class EmailService { ch.sendToQueue(mailerQueue, buffer, { persistent: true }); - Logger.log(`sent to queue ${mailerQueue}: ${JSON.stringify(email)}`); + Logger.log(`Sent to queue ${mailerQueue}: ${JSON.stringify(email)}`); done(null, true); diff --git a/src/email/email.ts b/src/email/email.ts index 1448ee36ad6bf186ba5f1f517a4ce3f9d5a32d2d..7a4f7af9e11956d6a9a09bf9ac538129207da74f 100644 --- a/src/email/email.ts +++ b/src/email/email.ts @@ -1,10 +1,11 @@ -import { IsDefined } from 'class-validator'; +import { IsDefined, Length, IsEmail } from 'class-validator'; import { ApiModelProperty, ApiModelPropertyOptional } from '@nestjs/swagger'; export class ContactForm { @ApiModelProperty() @IsDefined() + @IsEmail() from: string; @ApiModelProperty() @@ -13,10 +14,12 @@ export class ContactForm { @ApiModelProperty() @IsDefined() + @Length(1) firstname: string; @ApiModelProperty() @IsDefined() + @Length(1) lastname: string; @ApiModelProperty() diff --git a/src/main.ts b/src/main.ts index 1ed3584c2931484716180310c37a77b55a3cf97d..8ae74b66febe46b7dd74898ad582dd0f42368e24 100644 --- a/src/main.ts +++ b/src/main.ts @@ -12,6 +12,7 @@ async function bootstrap() { .setTitle('Portail Data Email MicroService API') .setDescription('APIs description for the Email API') .setVersion('0.1') + .addTag('email') .build(); const document = SwaggerModule.createDocument(app, options); fs.writeFileSync('./swagger-spec.json', JSON.stringify(document));