From 4dc871d97f0b3e4e57db67ab354c719905688750 Mon Sep 17 00:00:00 2001 From: FORESTIER Fabien <fabien.forestier@soprasteria.com> Date: Thu, 8 Nov 2018 11:19:12 +0100 Subject: [PATCH] Add config module, set the configuration in gitlab-ci and docker in order to be able to build the service for dev end rec env --- .gitignore | 4 +++ .gitlab-ci.yml | 42 ++++++++++++++++++++++++----- docker-compose.yml | 16 ++++++----- package-lock.json | 33 +++++++++-------------- package.json | 1 + src/app.module.ts | 3 ++- src/configuration/config.module.ts | 15 +++++++++++ src/configuration/config.service.ts | 29 ++++++++++++++++++++ src/configuration/config.ts | 10 +++++++ src/configuration/template.env | 3 +++ src/email/email.controller.ts | 5 ++-- src/email/email.service.ts | 18 +++++++------ src/email/email.ts | 5 +++- src/main.ts | 1 + 14 files changed, 139 insertions(+), 46 deletions(-) create mode 100644 src/configuration/config.module.ts create mode 100644 src/configuration/config.service.ts create mode 100644 src/configuration/config.ts create mode 100644 src/configuration/template.env diff --git a/.gitignore b/.gitignore index aff1045..a63a3ca 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 ebfb47e..7f2c2f4 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 7bf70fd..a2643e4 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 33a626d..d03358c 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 bb327ff..0ecc708 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 293d8cd..c8a2981 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 0000000..aa4bcfa --- /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 0000000..665132c --- /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 0000000..66d1927 --- /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 0000000..667471d --- /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 58b7439..c4bbdb8 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 3480d67..4170359 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 1448ee3..7a4f7af 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 1ed3584..8ae74b6 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)); -- GitLab