diff --git a/src/app.module.ts b/src/app.module.ts index 5e7865bac281ebc0539b8f24c728ef52d540c7f9..317caaab60e78a921488aa404f57fde2e6da13c5 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -4,6 +4,7 @@ import { AppController } from './app.controller'; import { StructuresModule } from './structures/structures.module'; import { ConfigurationModule } from './configuration/configuration.module'; import { CategoriesModule } from './categories/categories.module'; +import { MailerModule } from './mailer/mailer.module'; @Module({ imports: [ ConfigurationModule, @@ -12,6 +13,7 @@ import { CategoriesModule } from './categories/categories.module'; ), StructuresModule, CategoriesModule, + MailerModule, ], controllers: [AppController], }) diff --git a/src/configuration/config.ts b/src/configuration/config.ts new file mode 100644 index 0000000000000000000000000000000000000000..ee0b9db0dbad8115e0bafe32c87cead508ac0490 --- /dev/null +++ b/src/configuration/config.ts @@ -0,0 +1,21 @@ +export const config = { + url: process.env.MAIL_URL, + token: process.env.MAIL_TOKEN, + host: 'ram.grandlyon.com', + protocol: 'https', + port: '443', + from: 'inclusionnumerique@grandlyon.com', + from_name: 'Réseau des acteurs de la médiation numérique', + replyTo: 'inclusionnumerique@grandlyon.com', + templates: { + directory: './src/mailer/mail-templates', + verify: { + ejs: 'verify.ejs', + json: 'verify.json', + }, + changeEmail: { + ejs: 'changeEmail.ejs', + json: 'changeEmail.json', + }, + }, +}; diff --git a/src/configuration/configuration.service.ts b/src/configuration/configuration.service.ts index ecb1919a1519df3436d2be9d8753972e08e1ef2d..09d6666ca4b1ea1f417218e794f25db463b21789 100644 --- a/src/configuration/configuration.service.ts +++ b/src/configuration/configuration.service.ts @@ -1,8 +1,14 @@ import * as dotenv from 'dotenv'; - +import { config } from './config'; export class ConfigurationService { + private _config = config; + constructor() { // Initializing conf with values from var env dotenv.config(); } + + get config() { + return this._config; + } } diff --git a/src/mailer/mail-templates/changeEmail.ejs b/src/mailer/mail-templates/changeEmail.ejs new file mode 100644 index 0000000000000000000000000000000000000000..636faf2345061368d97af669dbdd882758b9800f --- /dev/null +++ b/src/mailer/mail-templates/changeEmail.ejs @@ -0,0 +1,8 @@ +Bonjour,<br /> +<br /> +Votre adresse email a été modifiée, si vous en avez bien fait la demande, +<a href="<%= config.protocol %>://<%= config.host %><%= config.port ? ':' + config.port : '' %>/change-email/<%= token %>" + >cliquez ici pour valider le changement.</a +> +<br /> +Ce mail est un mail automatique. Merci de ne pas y répondre. diff --git a/src/mailer/mail-templates/changeEmail.json b/src/mailer/mail-templates/changeEmail.json new file mode 100644 index 0000000000000000000000000000000000000000..99c1dddc92c5a36cbb56f51d5f799d872dbc7ef8 --- /dev/null +++ b/src/mailer/mail-templates/changeEmail.json @@ -0,0 +1,3 @@ +{ + "subject": "Changement d'adresse email" +} diff --git a/src/mailer/mail-templates/verify.ejs b/src/mailer/mail-templates/verify.ejs new file mode 100644 index 0000000000000000000000000000000000000000..e384dae43ff76ded6f735fe586b80598c6f8fb14 --- /dev/null +++ b/src/mailer/mail-templates/verify.ejs @@ -0,0 +1,10 @@ +Bonjour<br /> +<br /> +Afin de pouvoir vous connecter sur la plateforme, merci de cliquer sur +<a + href="<%= config.protocol %>://<%= config.host %><%= config.port ? ':' + config.port : '' %>/users/verify/<%= token %>" + >ce lien</a +> +afin de valider votre inscription<br /> +<br /> +Ce mail est un mail automatique. Merci de ne pas y répondre. diff --git a/src/mailer/mail-templates/verify.json b/src/mailer/mail-templates/verify.json new file mode 100644 index 0000000000000000000000000000000000000000..4e9f7278c5459890d1a8a9e4c479873cf6362a25 --- /dev/null +++ b/src/mailer/mail-templates/verify.json @@ -0,0 +1,3 @@ +{ + "subject": "Bienvenue sur le Réseau des Acteurs de la Médiation Numérique de la Métropole de Lyon" +} diff --git a/src/mailer/mailer.module.ts b/src/mailer/mailer.module.ts new file mode 100644 index 0000000000000000000000000000000000000000..e7b3a0e6a361722b7b16ae713fca532a546f02e2 --- /dev/null +++ b/src/mailer/mailer.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; +import { HttpModule } from '@nestjs/common/http/http.module'; +import { MailerService } from './mailer.service'; + +@Module({ + imports: [HttpModule], + providers: [MailerService], + exports: [MailerService], +}) +export class MailerModule {} diff --git a/src/mailer/mailer.service.spec.ts b/src/mailer/mailer.service.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..1ab5e828cc63cdd85859586764c0ca2a9d4dc882 --- /dev/null +++ b/src/mailer/mailer.service.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { MailerService } from './mailer.service'; + +describe('MailerService', () => { + let service: MailerService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [MailerService], + }).compile(); + + service = module.get<MailerService>(MailerService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/src/mailer/mailer.service.ts b/src/mailer/mailer.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..fdebbee315d40fb73dcb0afcc5ae233f8715c932 --- /dev/null +++ b/src/mailer/mailer.service.ts @@ -0,0 +1,77 @@ +import { HttpService, Injectable, Logger } from '@nestjs/common'; +import { ConfigurationService } from '../configuration/configuration.service'; +import * as fs from 'fs'; +import * as path from 'path'; +@Injectable() +export class MailerService { + public config = null; + constructor(private readonly httpService: HttpService, private configurationService: ConfigurationService) { + this.config = this.configurationService.config; + } + + /** + * Send an email + * + * @param {string} from + * @param {string} to + * @param {string} replyTo + * @param {string} subject + * @param {string} html + * @param {string} text + */ + public send(to: string, subject: string, html: string): Promise<any> { + const data = JSON.stringify({ + from: this.config.from, + // eslint-disable-next-line camelcase + from_name: this.config.from_name, + to: to, + subject: subject, + content: html, + }); + Logger.log(`[Mailer] Send mail : ${subject}`); + return new Promise((resolve, reject) => { + this.httpService + .post(process.env.MAIL_URL, data, { + headers: { + 'Content-Type': 'application/json', + Authorization: 'Bearer ' + process.env.MAIL_TOKEN, + }, + }) + .subscribe( + (body) => { + Logger.log(`[Mailer] Send mail : ${subject} success`); + return resolve(body); + }, + (err) => { + Logger.error(err); + return reject(err); + } + ); + }); + } + + /** + * Get email template location from config directory and given filename. Also, check if file exists. + * + * @param {string} filename + */ + public getTemplateLocation(filename) { + const ejsPath = path.join(this.config.templates.directory, filename); + if (!fs.existsSync(ejsPath)) { + throw new Error(`Email template '${filename}' cannot be found in ${this.config.templates.directory}`); + } + return ejsPath; + } + /** + * Load email json config file from config directory and given filename. Also, check if file exists + * + * @param {filename} filename + */ + public loadJsonConfig(filename) { + const jsonPath = path.join(this.config.templates.directory, filename); + if (!fs.existsSync(jsonPath)) { + throw new Error(`Email json definition file '${filename}' cannot be found in ${this.config.templates.directory}`); + } + return JSON.parse(fs.readFileSync(jsonPath).toString()); + } +} diff --git a/src/structures/dto/create-structure.dto.ts b/src/structures/dto/create-structure.dto.ts index 019b210aa4da6a72982a1f56dc1d3568d83f31d4..857697914c2aad758535f706b2e2f0811fb3e2bd 100644 --- a/src/structures/dto/create-structure.dto.ts +++ b/src/structures/dto/create-structure.dto.ts @@ -36,7 +36,7 @@ export class CreateStructureDto { aideALaParentalite: string[]; cultureEtSecuriteNumerique: string[]; wifiEnAccesLibre: boolean; - ordinateurs: boolean; + nbComputers: boolean; nombre: string; tablettes: boolean; bornesNumeriques: boolean; diff --git a/src/structures/schemas/structure.schema.ts b/src/structures/schemas/structure.schema.ts index 821547e9f35edd4d65eb38b2dd10dd3cf3480ad8..711c63714c5c39442ed74f8bf29d333788155755 100644 --- a/src/structures/schemas/structure.schema.ts +++ b/src/structures/schemas/structure.schema.ts @@ -112,22 +112,10 @@ export class Structure { cultureEtSecuriteNumerique: string[]; @Prop() - wifiEnAccesLibre: boolean; + equipementsEtServicesProposes: string[]; @Prop() - ordinateurs: boolean; - - @Prop() - nombre: string; - - @Prop() - tablettes: boolean; - - @Prop() - bornesNumeriques: boolean; - - @Prop() - imprimantes: boolean; + nbComputers: number; @Prop() precisionsSiNecessaire: string; diff --git a/src/structures/structures.controller.ts b/src/structures/structures.controller.ts index 27fa79cc82204ce5f96b3b84249c7e76bd0ee584..b3a33b8aa05b35c191abfef770bb228f2cb3a420 100644 --- a/src/structures/structures.controller.ts +++ b/src/structures/structures.controller.ts @@ -35,11 +35,7 @@ export class StructuresController { this.structureService.countByStructureKey('publicsAcceptes'), this.structureService.countByStructureKey('modalitesDacces'), this.structureService.countByStructureKey('lesCompetencesDeBase'), - this.structureService.countByEquipmentsKey('wifiEnAccesLibre', 'Wifi en accès libre'), - this.structureService.countByEquipmentsKey('ordinateurs', 'Ordinateurs'), - this.structureService.countByEquipmentsKey('tablettes', 'Tablettes'), - this.structureService.countByEquipmentsKey('bornesNumeriques', 'Bornes numériques'), - this.structureService.countByEquipmentsKey('imprimantes', 'Imprimantes'), + this.structureService.countByStructureKey('equipementsEtServicesProposes'), ]); // Return a concat of all arrays return data.reduce((a, b) => [...a, ...b]); diff --git a/src/structures/structures.module.ts b/src/structures/structures.module.ts index f6843d41be67818a99918eae46421f7948632340..6a82aae92ddcec6e352b3ede79367694322e150f 100644 --- a/src/structures/structures.module.ts +++ b/src/structures/structures.module.ts @@ -1,11 +1,12 @@ import { HttpModule, Module } from '@nestjs/common'; import { MongooseModule } from '@nestjs/mongoose'; +import { MailerModule } from '../mailer/mailer.module'; import { Structure, StructureSchema } from './schemas/structure.schema'; import { StructuresController } from './structures.controller'; import { StructuresService } from './structures.service'; @Module({ - imports: [MongooseModule.forFeature([{ name: Structure.name, schema: StructureSchema }]), HttpModule], + imports: [MongooseModule.forFeature([{ name: Structure.name, schema: StructureSchema }]), HttpModule, MailerModule], controllers: [StructuresController], providers: [StructuresService], }) diff --git a/src/structures/structures.service.ts b/src/structures/structures.service.ts index 7c9b021980a81598267c22dd013e60f330b946a4..9e52b0937eee1a679ca545a70f2f1400c846a60a 100644 --- a/src/structures/structures.service.ts +++ b/src/structures/structures.service.ts @@ -105,10 +105,6 @@ export class StructuresService { ); } - public async countByEquipmentsKey(key: string, displayKey: string): Promise<any> { - return [{ id: displayKey, count: await this.structureModel.countDocuments({ [key]: true }).exec() }]; - } - public getCoord(numero: string, address: string, zipcode: string): Observable<AxiosResponse<any>> { const req = 'https://download.data.grandlyon.com/geocoding/photon/api' + '?q=' + numero + ' ' + address + ' ' + zipcode; diff --git a/template.env b/template.env index 92d0f28c21f7d10bf355d6b3108a61482e9c4ef0..bd0a81b58efd1d682e148deff4171376ae8dc3a6 100644 --- a/template.env +++ b/template.env @@ -9,3 +9,5 @@ MONGO_DB_HOST_AND_PORT=<host:port used by the api to connect to mongo db> ME_CONFIG_BASICAUTH_USERNAME=<mongo express username> ME_CONFIG_BASICAUTH_PASSWORD=<mongo express password> ME_PORT=<mongo express port> +MAIL_URL=<API url> +MAIL_TOKEN=<API token> \ No newline at end of file