diff --git a/package-lock.json b/package-lock.json index 26f6823102671c944e40ab31097f71212cc5cb61..88230fccec693a6d208a911da1dd68fa1dfcf665 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8982,8 +8982,7 @@ "deepmerge": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", - "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", - "dev": true + "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==" }, "defaults": { "version": "1.0.3", @@ -9174,7 +9173,6 @@ "version": "1.3.2", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.3.2.tgz", "integrity": "sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig==", - "dev": true, "requires": { "domelementtype": "^2.0.1", "domhandler": "^4.2.0", @@ -9190,8 +9188,7 @@ "domelementtype": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz", - "integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==", - "dev": true + "integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==" }, "domexception": { "version": "2.0.1", @@ -9214,7 +9211,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.0.tgz", "integrity": "sha512-fC0aXNQXqKSFTr2wDNZDhsEYjCiYsDWl3D01kwt25hm1YIPyDGHvvi3rw+PLqHAl/m71MaiF7d5zvBr0p5UB2g==", - "dev": true, "requires": { "domelementtype": "^2.2.0" } @@ -9223,7 +9219,6 @@ "version": "2.8.0", "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", - "dev": true, "requires": { "dom-serializer": "^1.0.1", "domelementtype": "^2.2.0", @@ -9374,9 +9369,9 @@ "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, "ejs": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.5.tgz", - "integrity": "sha512-dldq3ZfFtgVTJMLjOe+/3sROTzALlL9E34V4/sDtUd/KlBSS0s6U1/+WPE1B4sj9CXHJpL1M6rhNJnc9Wbal9w==", + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.6.tgz", + "integrity": "sha512-9lt9Zse4hPucPkoP7FHDF0LQAlGyF9JVpnClFLFH3aSSbxmyoqINRpp/9wePWJTUl4KOQwRL72Iw3InHPDkoGw==", "requires": { "jake": "^10.6.1" } @@ -9469,8 +9464,7 @@ "entities": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", - "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", - "dev": true + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==" }, "errno": { "version": "0.1.7", @@ -10375,9 +10369,9 @@ "optional": true }, "filelist": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.1.tgz", - "integrity": "sha512-8zSK6Nu0DQIC08mUC46sWGXi+q3GGpKydAG36k+JDba6VRpkevvOWUW5a/PhShij4+vHT9M+ghgG7eM+a9JDUQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.2.tgz", + "integrity": "sha512-z7O0IS8Plc39rTCq6i6iHxk43duYOn8uFJiWSewIq0Bww1RNybVHSCjahmcC87ZqAm4OTvFzlzeGu3XAzG1ctQ==", "requires": { "minimatch": "^3.0.4" } @@ -11322,7 +11316,6 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", - "dev": true, "requires": { "domelementtype": "^2.0.1", "domhandler": "^4.0.0", @@ -14697,6 +14690,11 @@ "dev": true, "optional": true }, + "nanoid": { + "version": "3.1.32", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.32.tgz", + "integrity": "sha512-F8mf7R3iT9bvThBoW4tGXhXFHCctyCiUUPrWF8WaTqa3h96d9QybkSeba43XVOOE3oiLfkVDe4bT8MeGmkrTxw==" + }, "nanomatch": { "version": "1.2.13", "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", @@ -15443,6 +15441,11 @@ "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", "dev": true }, + "parse-srcset": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/parse-srcset/-/parse-srcset-1.0.2.tgz", + "integrity": "sha1-8r0iH2zJcKk42IVWq8WJyqqiveE=" + }, "parse5-htmlparser2-tree-adapter": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", @@ -15600,8 +15603,7 @@ "picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" }, "picomatch": { "version": "2.2.2", @@ -15651,6 +15653,16 @@ "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", "dev": true }, + "postcss": { + "version": "8.4.5", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.5.tgz", + "integrity": "sha512-jBDboWM8qpaqwkMwItqTQTiFikhs/67OYVvblFFTM7MrZjt6yMKd6r2kgXizEbTTljacm4NldIlZnhbjr84QYg==", + "requires": { + "nanoid": "^3.1.30", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.1" + } + }, "prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -16410,6 +16422,31 @@ } } }, + "sanitize-html": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.6.1.tgz", + "integrity": "sha512-DzjSz3H5qDntD7s1TcWCSoRPmNR8UmA+y+xZQOvWgjATe2Br9ZW73+vD3Pj6Snrg0RuEuJdXgrKvnYuiuixRkA==", + "requires": { + "deepmerge": "^4.2.2", + "escape-string-regexp": "^4.0.0", + "htmlparser2": "^6.0.0", + "is-plain-object": "^5.0.0", + "parse-srcset": "^1.0.2", + "postcss": "^8.3.11" + }, + "dependencies": { + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==" + }, + "is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==" + } + } + }, "saslprep": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz", @@ -16836,6 +16873,11 @@ "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", "dev": true }, + "source-map-js": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.1.tgz", + "integrity": "sha512-4+TN2b3tqOCd/kaGRJ/sTYA0tR0mdXx26ipdolxcwtJVqEnqNYvlCAt1q3ypy4QMlYus+Zh34RNtYLoq2oQ4IA==" + }, "source-map-resolve": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", diff --git a/package.json b/package.json index a42098352a937e0fb37708dbf79ad87b49ca9351..f022cb4dedd0bb8fc85124509609219657e9b575 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "class-transformer": "^0.3.1", "class-validator": "^0.12.2", "dotenv": "^8.2.0", - "ejs": "^3.1.5", + "ejs": "^3.1.6", "form-data": "^3.0.0", "luxon": "^1.25.0", "migrate": "^1.7.0", @@ -55,6 +55,7 @@ "reflect-metadata": "^0.1.13", "rimraf": "^3.0.2", "rxjs": "^6.6.3", + "sanitize-html": "^2.6.1", "standard-version": "^9.3.2", "swagger-ui-express": "^4.3.0" }, diff --git a/src/app.module.ts b/src/app.module.ts index 3cd15b2b24ef294009ddb6c3a55989789bd9bfd4..96c38318fa9f1359ce29a6764d71da6acfafc27c 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -13,6 +13,7 @@ import { AdminModule } from './admin/admin.module'; import { PostsModule } from './posts/posts.module'; import { TempUserModule } from './temp-user/temp-user.module'; import { NewsletterModule } from './newsletter/newsletter.module'; +import { ContactModule } from './contact/contact.module'; @Module({ imports: [ ConfigurationModule, @@ -30,6 +31,7 @@ import { NewsletterModule } from './newsletter/newsletter.module'; PostsModule, TempUserModule, NewsletterModule, + ContactModule, ], controllers: [AppController], }) diff --git a/src/configuration/config.ts b/src/configuration/config.ts index ba92a750b583fef8a4a06edbe0b228ebae405763..f81023c4e753c40854020b92c241d54d156772f9 100644 --- a/src/configuration/config.ts +++ b/src/configuration/config.ts @@ -61,6 +61,10 @@ export const config = { ejs: 'structureDeletionNotification.ejs', json: 'structureDeletionNotification.json', }, + contactMessage: { + ejs: 'contactMessage.ejs', + json: 'contactMessage.json', + }, newApticStructure: { ejs: 'newApticStructure.ejs', json: 'newApticStructure.json', diff --git a/src/contact/contact.controller.spec.ts b/src/contact/contact.controller.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..e6338a453dfe1a57b558342bd315bab0b9316b53 --- /dev/null +++ b/src/contact/contact.controller.spec.ts @@ -0,0 +1,22 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { ContactController } from './contact.controller'; +import { ContactService } from './contact.service'; +import { MailerModule } from '../mailer/mailer.module'; + +describe('ContactController', () => { + let controller: ContactController; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + imports: [MailerModule], + providers: [ContactService], + controllers: [ContactController], + }).compile(); + + controller = module.get<ContactController>(ContactController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/src/contact/contact.controller.ts b/src/contact/contact.controller.ts new file mode 100644 index 0000000000000000000000000000000000000000..6f3de72aa7f2e9f2bce8a491ebb4be3cab823105 --- /dev/null +++ b/src/contact/contact.controller.ts @@ -0,0 +1,13 @@ +import { Body, Controller, Post } from '@nestjs/common'; +import { ContactMessage } from './schemas/contact-message.schema'; +import { ContactService } from './contact.service'; + +@Controller('contact') +export class ContactController { + constructor(private contactService: ContactService) {} + + @Post('message') + public async sendContactMessage(@Body() data: { contactMessage: ContactMessage }): Promise<any> { + await this.contactService.sendMessage(data.contactMessage); + } +} diff --git a/src/contact/contact.module.ts b/src/contact/contact.module.ts new file mode 100644 index 0000000000000000000000000000000000000000..3ee19dd8bbafada37695d535020ece3a243daaa2 --- /dev/null +++ b/src/contact/contact.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; +import { MailerModule } from '../mailer/mailer.module'; +import { ContactController } from './contact.controller'; +import { ContactService } from './contact.service'; + +@Module({ + imports: [MailerModule], + controllers: [ContactController], + providers: [ContactService] +}) +export class ContactModule {} diff --git a/src/contact/contact.service.spec.ts b/src/contact/contact.service.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..0f03f1cd993bf2f01cbe5ef27c76a70089d82b07 --- /dev/null +++ b/src/contact/contact.service.spec.ts @@ -0,0 +1,20 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { MailerModule } from '../mailer/mailer.module'; +import { ContactService } from './contact.service'; + +describe('ContactService', () => { + let service: ContactService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + imports: [MailerModule], + providers: [ContactService], + }).compile(); + + service = module.get<ContactService>(ContactService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/src/contact/contact.service.ts b/src/contact/contact.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..f700ba7cca67b0da6065be756aa0956528277a44 --- /dev/null +++ b/src/contact/contact.service.ts @@ -0,0 +1,26 @@ +import { Injectable } from '@nestjs/common'; +import { MailerService } from '../mailer/mailer.service'; +import { ContactMessage } from './schemas/contact-message.schema'; +import * as ejs from 'ejs'; +import * as sanitizeHtml from 'sanitize-html'; + +@Injectable() +export class ContactService { + constructor(private readonly mailerService: MailerService) {} + + public async sendMessage(contactMessage: ContactMessage): Promise<any> { + const config = this.mailerService.config; + const ejsPath = this.mailerService.getTemplateLocation(config.templates.contactMessage.ejs); + const jsonConfig = this.mailerService.loadJsonConfig(config.templates.contactMessage.json); + + const html = await ejs.renderFile(ejsPath, { + config, + name: sanitizeHtml(contactMessage.name), + email: sanitizeHtml(contactMessage.email), + phone: sanitizeHtml(contactMessage.phone), + subject: sanitizeHtml(contactMessage.subject), + message: sanitizeHtml(contactMessage.message).replace(/\n/g, "<br />"), + }); + return this.mailerService.send(process.env.MAIL_CONTACT, jsonConfig.subject, html); + } +} \ No newline at end of file diff --git a/src/contact/schemas/contact-message.schema.ts b/src/contact/schemas/contact-message.schema.ts new file mode 100644 index 0000000000000000000000000000000000000000..35a6aae8c07e765c93fc90a9c0d95f6465543782 --- /dev/null +++ b/src/contact/schemas/contact-message.schema.ts @@ -0,0 +1,20 @@ +import { IsNotEmpty } from 'class-validator'; + +export class ContactMessage { + @IsNotEmpty() + surname: string; + + @IsNotEmpty() + name: string; + + @IsNotEmpty() + email: string; + + phone: string; + + @IsNotEmpty() + subject: string; + + @IsNotEmpty() + message: string; +} diff --git a/src/mailer/mail-templates/contactMessage.ejs b/src/mailer/mail-templates/contactMessage.ejs new file mode 100644 index 0000000000000000000000000000000000000000..ecbf1b4d4a8f5eab2f309b0d1e9616d16fe5faa4 --- /dev/null +++ b/src/mailer/mail-templates/contactMessage.ejs @@ -0,0 +1,12 @@ +Bonjour,<br /> +<br /> +Une demande de contact a été envoyée par :<br /> +<br /> +Prénom et Nom : <%= name %><br /> +Adresse mail : <%= email %><br /> +<%if (phone) { %>Téléphone : <%= phone %><br /><% } %> +<br /> +<strong>Objet du message : <%= subject %></strong><br /> +Message :<br /> +<%# Unescaped raw output with tag %- (for html new line tags), cf. https://github.com/mde/ejs %> +<%- message %><br /> diff --git a/src/mailer/mail-templates/contactMessage.json b/src/mailer/mail-templates/contactMessage.json new file mode 100644 index 0000000000000000000000000000000000000000..76c17c6708b7503a0c93dc0bc6a15aad37fcb591 --- /dev/null +++ b/src/mailer/mail-templates/contactMessage.json @@ -0,0 +1,3 @@ +{ + "subject": "Demande de contact" +} diff --git a/template.env b/template.env index 027d8b6a62352ab2d9b95ae9c6d337d9e19d13c6..05049ed6a7cd626825ec2fec8f59345d1411f51d 100644 --- a/template.env +++ b/template.env @@ -13,6 +13,7 @@ ME_PORT=<mongo express port> 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> APTIC_TOKEN=<APTIC API TOKEN> GHOST_PORT=<ghost port> GHOST_DB_PASSWORD=<ghost db password>