diff --git a/docker-compose.yml b/docker-compose.yml index 385077772ec900e9d95ddb752bb61986581d2af5..08281efdbea1386e79225c1c0913d8f5dd325a24 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -18,6 +18,7 @@ services: SALT: ${SALT} MAIL_URL: ${MAIL_URL} MAIL_TOKEN: ${MAIL_TOKEN} + MAIL_SOSTECH: ${MAIL_SOSTECH} MAIL_CONTACT: ${MAIL_CONTACT} NODE_ENV: ${NODE_ENV} APTIC_TOKEN: ${APTIC_TOKEN} diff --git a/src/configuration/config.ts b/src/configuration/config.ts index f53310aedbb4c529ef6714938eecd3f924d5d23c..65af84c64717b854d1bc3c37becf0f4e98fdb8f5 100644 --- a/src/configuration/config.ts +++ b/src/configuration/config.ts @@ -85,5 +85,9 @@ export const config = { ejs: 'newApticStructure.ejs', json: 'newApticStructure.json', }, + sosTechEMail: { + ejs: 'sosTechEmail.ejs', + json: 'sosTechEmail.json', + }, }, }; diff --git a/src/configuration/configuration.service.ts b/src/configuration/configuration.service.ts index 5ccb5d1cf756dee1eb81aff5a60c3396240ae46c..1b8d4df7b102ab565b4c5d72d7d29d06af9a4746 100644 --- a/src/configuration/configuration.service.ts +++ b/src/configuration/configuration.service.ts @@ -37,6 +37,10 @@ export class ConfigurationService { return process.env.NODE_ENV === 'local'; } + public isProdConf(): boolean { + return process.env.NODE_ENV === 'production'; + } + get config() { return this._config; } diff --git a/src/mailer/mail-templates/sosTechEmail.ejs b/src/mailer/mail-templates/sosTechEmail.ejs new file mode 100644 index 0000000000000000000000000000000000000000..512c3ceaa72470f9432825e91e5dd4472f624823 --- /dev/null +++ b/src/mailer/mail-templates/sosTechEmail.ejs @@ -0,0 +1,31 @@ +Bonjour,<br /> +<br /> +Veuillez trouver ci-joint la liste des demandes d'aide en ligne du jour : +<br /> +<br /> + +<table style="border: solid 1px"> + <thead> + <tr> + <th style="padding: 8px">Nom</th> + <th style="padding: 8px">Prénom</th> + <th style="padding: 8px">Téléphone</th> + <th style="padding: 8px">Besoin(s)</th> + <th style="padding: 8px">Créneau</th> + </tr> + </thead> + <tbody> + <% demands.forEach((demand) => { %> + <tr> + <td style="padding: 8px; border-top: solid 1px"><%= demand.name %></td> + <td style="padding: 8px; border-top: solid 1px"><%= demand.surname %></td> + <td style="padding: 8px; border-top: solid 1px"><%= demand.phone %></td> + <td style="padding: 8px; border-top: solid 1px"> + <% demand.onlineDemarchType.forEach((demarch) => { %> - <%= demarch %> <br /> + <% }); %> + </td> + <td style="padding: 8px; border-top: solid 1px"><%= demand.dateSlot.day %><br /><%= demand.dateSlot.hours %></td> + </tr> + <% }); %> + </tbody> +</table> diff --git a/src/mailer/mail-templates/sosTechEmail.json b/src/mailer/mail-templates/sosTechEmail.json new file mode 100644 index 0000000000000000000000000000000000000000..1a17fe6552d38e2d50e5617066a227b69b237445 --- /dev/null +++ b/src/mailer/mail-templates/sosTechEmail.json @@ -0,0 +1,3 @@ +{ + "subject": "[Rés'in] Demandes journalières d'aide en ligne" +} diff --git a/src/online-mediation/onlineMediation.module.ts b/src/online-mediation/onlineMediation.module.ts index cbbb250a32be9a288fb47298d39a6e0fddb74b8b..ed7b1defa8ae9b3011d0b720bb39d06c8eb3f23a 100644 --- a/src/online-mediation/onlineMediation.module.ts +++ b/src/online-mediation/onlineMediation.module.ts @@ -3,9 +3,10 @@ import { OnlineMediationService } from './onlineMediation.service'; import { OnlineMediationController } from './onlineMediation.controller'; import { MongooseModule } from '@nestjs/mongoose'; import { OnlineMediation, OnlineMediationSchema } from './onlineMediation.schema'; +import { MailerModule } from '../mailer/mailer.module'; @Module({ - imports: [MongooseModule.forFeature([{ name: OnlineMediation.name, schema: OnlineMediationSchema }])], + imports: [MongooseModule.forFeature([{ name: OnlineMediation.name, schema: OnlineMediationSchema }]), MailerModule], providers: [OnlineMediationService], controllers: [OnlineMediationController], }) diff --git a/src/online-mediation/onlineMediation.service.spec.ts b/src/online-mediation/onlineMediation.service.spec.ts index 2db57b758e79e523471a53f52d193d55f09ea76d..4a0bcebe7f68aeae6057bf31789529b975cfe3c6 100644 --- a/src/online-mediation/onlineMediation.service.spec.ts +++ b/src/online-mediation/onlineMediation.service.spec.ts @@ -2,7 +2,13 @@ import { getModelToken } from '@nestjs/mongoose'; import { Test, TestingModule } from '@nestjs/testing'; import { OnlineMediationService } from './onlineMediation.service'; import { onlineMediationMockData } from '../../test/mock/data/onlineMediation.mock.data'; +import { MailerService } from '../mailer/mailer.service'; +import { ConfigurationService } from '../configuration/configuration.service'; +import { MailerModule } from '../mailer/mailer.module'; +import { HttpModule } from '@nestjs/axios'; +import * as ejs from 'ejs'; +jest.mock('ejs'); describe('userRegistryService', () => { let service: OnlineMediationService; const mockOnlineMediationModel = { @@ -10,13 +16,35 @@ describe('userRegistryService', () => { create: jest.fn(() => mockOnlineMediationModel), sort: jest.fn(() => mockOnlineMediationModel), exec: jest.fn(() => mockOnlineMediationModel), + deleteMany: jest.fn(() => mockOnlineMediationModel), + }; + const mockMailService = { + send: jest.fn(), + getTemplateLocation: jest.fn(), + loadJsonConfig: jest.fn(), + config: { + templates: { + sosTechEMail: { + ejs: 'sosTechEmail.ejs', + json: 'sosTechEmail.json', + }, + }, + }, + }; + const mockConfigService = { + isProdConf: jest.fn(), }; + const ejsSpy = jest.spyOn(ejs, 'renderFile'); + const mailerSpy = jest.spyOn(mockMailService, 'send'); + beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ - imports: [], + imports: [MailerModule, HttpModule], providers: [ OnlineMediationService, + { provide: ConfigurationService, useValue: mockConfigService }, + { provide: MailerService, useValue: mockMailService }, { provide: getModelToken('OnlineMediation'), useValue: mockOnlineMediationModel, @@ -50,4 +78,27 @@ describe('userRegistryService', () => { expect(await service.create(res)).toStrictEqual(res); }); }); + describe('sendMailAndResetDb', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + it('should send the mail and reset db', async () => { + mockConfigService.isProdConf.mockReturnValueOnce(true); + mockOnlineMediationModel.exec.mockResolvedValueOnce(onlineMediationMockData); + mockOnlineMediationModel.deleteMany.mockResolvedValueOnce(onlineMediationMockData.length); + mockMailService.loadJsonConfig.mockReturnValueOnce({ subject: 'Teste Mail' }); + ejsSpy.mockResolvedValueOnce('coucou'); + const response = await service.sendMailAndResetDb(); + expect(mailerSpy).toHaveBeenCalledTimes(1); + expect(response).toBe(onlineMediationMockData.length); + }); + it('should send the mail and reset db', async () => { + mockConfigService.isProdConf.mockReturnValueOnce(false); + mockOnlineMediationModel.exec.mockResolvedValueOnce(onlineMediationMockData); + mockOnlineMediationModel.deleteMany.mockResolvedValueOnce(onlineMediationMockData.length); + const response = await service.sendMailAndResetDb(); + expect(mailerSpy).toHaveBeenCalledTimes(0); + expect(response).toBe(onlineMediationMockData.length); + }); + }); }); diff --git a/src/online-mediation/onlineMediation.service.ts b/src/online-mediation/onlineMediation.service.ts index 4dcf8684ab867f19a07590edb223df75c38f5ea1..f0fc5a8f877573453d625e65bc0cd97f20d34edb 100644 --- a/src/online-mediation/onlineMediation.service.ts +++ b/src/online-mediation/onlineMediation.service.ts @@ -1,15 +1,22 @@ import { HttpException, HttpStatus, Injectable, Logger } from '@nestjs/common'; import { InjectModel } from '@nestjs/mongoose'; +import { Cron, CronExpression } from '@nestjs/schedule'; import { Model } from 'mongoose'; +import { ConfigurationService } from '../configuration/configuration.service'; +import { MailerService } from '../mailer/mailer.service'; import { OnlineMediationDto } from './dto/onlineMediation.dto'; import { IOnlineMediation } from './interfaces/onlineMediation.interface'; import { OnlineMediation, OnlineMediationDocument } from './onlineMediation.schema'; - +import * as ejs from 'ejs'; @Injectable() export class OnlineMediationService { private readonly logger = new Logger(OnlineMediationService.name); - constructor(@InjectModel(OnlineMediation.name) private OnlineMediationModel: Model<OnlineMediationDocument>) {} + constructor( + @InjectModel(OnlineMediation.name) private OnlineMediationModel: Model<OnlineMediationDocument>, + private configurationService: ConfigurationService, + private readonly mailerService: MailerService + ) {} /** * @param newMediation @@ -30,4 +37,30 @@ export class OnlineMediationService { public async getAll(): Promise<IOnlineMediation[]> { return this.OnlineMediationModel.find().sort({ NamedNodeMap: 1 }).exec(); } + + @Cron(CronExpression.EVERY_DAY_AT_11PM) + public async sendMailAndResetDb(): Promise<{ deletedCount?: number }> { + this.logger.debug('sendMailAndResetDb'); + const docs = await this.getAll(); + const isProd = this.configurationService.isProdConf(); + //To test it locally, comment the isProd condition, change the mail receiver and edit cron frequency + if (docs.length) { + if (isProd) { + //send Mail + const config = this.mailerService.config; + const ejsPath = this.mailerService.getTemplateLocation(config.templates.sosTechEMail.ejs); + const jsonConfig = this.mailerService.loadJsonConfig(config.templates.sosTechEMail.json); + this.logger.debug(`sendMaildetails: ${docs}`); + + const html = await ejs.renderFile(ejsPath, { + config, + demands: docs, + }); + this.mailerService.send(process.env.MAIL_SOSTECH, jsonConfig.subject, html); + } + //reset docs + this.logger.debug('deletedocs'); + return this.OnlineMediationModel.deleteMany({ _id: { $in: docs.map((doc) => doc._id) } }); + } + } } diff --git a/src/structures/dto/structure.dto.ts b/src/structures/dto/structure.dto.ts index 63996fb2032e7de9759d899dc0544e76c94ac2f7..90ab990342b385d6c545955d1a06ccefb2012d4a 100644 --- a/src/structures/dto/structure.dto.ts +++ b/src/structures/dto/structure.dto.ts @@ -1,5 +1,5 @@ import { Type } from 'class-transformer'; -import { ArrayNotEmpty, IsArray, IsNotEmpty, ValidateNested } from 'class-validator'; +import { ArrayNotEmpty, IsArray, IsNotEmpty, IsOptional, ValidateNested } from 'class-validator'; import { Address } from '../schemas/address.schema'; import { Week } from '../../shared/schemas/week.schema'; import { PersonalOfferDocument } from '../../personal-offers/schemas/personal-offer.schema'; @@ -9,7 +9,9 @@ export class StructureDto { numero: string; createdAt: Date; updatedAt: Date; - toBeDeletedAt: Date; + @IsOptional() + toBeDeletedAt?: Date; + deletedAt: Date; @IsNotEmpty() diff --git a/src/structures/schemas/structure.schema.ts b/src/structures/schemas/structure.schema.ts index e3da7a141ad58dedb0a6e0e4d642a2784bd1fb06..06f06c6172b37d711000176a00bbd402264183f2 100644 --- a/src/structures/schemas/structure.schema.ts +++ b/src/structures/schemas/structure.schema.ts @@ -178,7 +178,7 @@ export class Structure { coord: number[]; @Prop() - toBeDeletedAt: Date; + toBeDeletedAt?: Date; @Prop() deletedAt: Date; diff --git a/template.env b/template.env index ffa047137f23d6214cb698c674b6887c21851263..7f866863895e7fbfe28b91d7d21fb00aab4d8cbd 100644 --- a/template.env +++ b/template.env @@ -14,6 +14,7 @@ SALT=<Salt must be in the form of: $Vers$log2(NumRounds)$saltvalue> MAIL_URL=<SEN API url> MAIL_TOKEN=<SEN API token> MAIL_CONTACT=<MAIL_CONTACT> +MAIL_SOSTECH=<MAIL_SOSTECH> APTIC_TOKEN=<APTIC API TOKEN> GHOST_PORT=<ghost port> GHOST_DB_PASSWORD=<ghost db password>