#40 - Formulaire de contact via SMTP (#58)

Co-authored-by: dbroqua <contact@darkou.fr>
Reviewed-on: #58
This commit is contained in:
Damien Broqua 2022-09-01 10:20:13 +02:00
parent b8b3df2932
commit 2da6afa06d
9 changed files with 134 additions and 10 deletions

View file

@ -18,7 +18,9 @@ Vous pouvez, si vous le souhaitez héberger l'application sur votre propre serve
### Prérequis ### Prérequis
Il existe 2 méthodes d'installation, soit via docker soit en mode standalone. Peu importe la méthode il vous faudra un compte sur [https://formspree.io/](https://formspree.io/) afin d'avoir une page nous-contacter fonctionnelle. Il existe 2 méthodes d'installation, soit via docker soit en mode standalone.
Peu importe la méthode il vous faudra un compte sur [https://formspree.io/](https://formspree.io/) afin d'avoir une page nous-contacter fonctionnelle ou configurer le SMTP tel que défini dans la section [variables d'environnements](#env-file).
Pour la méthode docker il ne vous faudra rien de plus que `docker` et `docker-compose`. Pour la méthode docker il ne vous faudra rien de plus que `docker` et `docker-compose`.
@ -227,6 +229,13 @@ S3_BUCKET # Nom du bucket (musictopus par défaut, à changer impérativement si
JOBS_HEADER_KEY # Nom du header utilisé pour l'identification des tâches cron (musictopus par défaut) JOBS_HEADER_KEY # Nom du header utilisé pour l'identification des tâches cron (musictopus par défaut)
JOBS_HEADER_VALUE # Valeur de la clé (ooYee9xok7eigo2shiePohyoGh1eepew par défaut) JOBS_HEADER_VALUE # Valeur de la clé (ooYee9xok7eigo2shiePohyoGh1eepew par défaut)
REGISTRATION_OPEN # true/false en fonction de si vous souhaitez activer ou non l'inscription à votre instance (true par défaut) REGISTRATION_OPEN # true/false en fonction de si vous souhaitez activer ou non l'inscription à votre instance (true par défaut)
MAIL_METHOD # permet de définir la façon dont les mails de la page contact sont envoyés (formspree ou smtp)
MAIL_HOST # Adresse du server mail (dams le cas ou MAIL_METHOD est défini sur smtp)
MAIL_PORT # Port d'écoute du serveur smtp (dams le cas ou MAIL_METHOD est défini sur smtp)
MAIL_USER # Adresse mail du compte permettant d'envoyer les mails (dams le cas ou MAIL_METHOD est défini sur smtp)
MAIL_PASSWORD # Mot de passe du compte email (dams le cas ou MAIL_METHOD est défini sur smtp)
MAIL_TO # Adresse mail du contact qui recevra les messages de la page "nous contacter" (dams le cas ou MAIL_METHOD est défini sur smtp)
``` ```
## Contributeurs ## Contributeurs

View file

@ -37,6 +37,12 @@ services:
JOBS_HEADER_KEY: ${JOBS_HEADER_KEY} JOBS_HEADER_KEY: ${JOBS_HEADER_KEY}
JOBS_HEADER_VALUE: ${JOBS_HEADER_VALUE} JOBS_HEADER_VALUE: ${JOBS_HEADER_VALUE}
REGISTRATION_OPEN: ${REGISTRATION_OPEN} REGISTRATION_OPEN: ${REGISTRATION_OPEN}
MAIL_METHOD: ${MAIL_METHOD}
MAIL_HOST: ${MAIL_HOST}
MAIL_PORT: ${MAIL_PORT}
MAIL_USER: ${MAIL_USER}
MAIL_PASSWORD: ${MAIL_PASSWORD}
MAIL_TO: ${MAIL_TO}
networks: networks:
- musictopus - musictopus
musictopus-db: musictopus-db:

View file

@ -59,6 +59,7 @@
"knacss": "^8.0.4", "knacss": "^8.0.4",
"mongoose": "^6.2.1", "mongoose": "^6.2.1",
"mongoose-unique-validator": "^3.0.0", "mongoose-unique-validator": "^3.0.0",
"nodemailer": "^6.7.8",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"passport": "^0.5.2", "passport": "^0.5.2",
"passport-custom": "^1.1.1", "passport-custom": "^1.1.1",

View file

@ -23,6 +23,7 @@ import importJobsRouter from "./routes/jobs";
import importAlbumRouterApiV1 from "./routes/api/v1/albums"; import importAlbumRouterApiV1 from "./routes/api/v1/albums";
import importSearchRouterApiV1 from "./routes/api/v1/search"; import importSearchRouterApiV1 from "./routes/api/v1/search";
import importMeRouterApiV1 from "./routes/api/v1/me"; import importMeRouterApiV1 from "./routes/api/v1/me";
import importContactRouterApiV1 from "./routes/api/v1/contact";
passportConfig(passport); passportConfig(passport);
@ -91,6 +92,7 @@ app.use("/jobs", importJobsRouter);
app.use("/api/v1/albums", importAlbumRouterApiV1); app.use("/api/v1/albums", importAlbumRouterApiV1);
app.use("/api/v1/search", importSearchRouterApiV1); app.use("/api/v1/search", importSearchRouterApiV1);
app.use("/api/v1/me", importMeRouterApiV1); app.use("/api/v1/me", importMeRouterApiV1);
app.use("/api/v1/contact", importContactRouterApiV1);
// Handle 404 // Handle 404
app.use((req, res) => { app.use((req, res) => {

View file

@ -19,4 +19,14 @@ module.exports = {
process.env.JOBS_HEADER_VALUE || "ooYee9xok7eigo2shiePohyoGh1eepew", process.env.JOBS_HEADER_VALUE || "ooYee9xok7eigo2shiePohyoGh1eepew",
registrationOpen: registrationOpen:
(process.env.REGISTRATION_OPEN || "true").toLowerCase() === "true", (process.env.REGISTRATION_OPEN || "true").toLowerCase() === "true",
mailMethod: process.env.MAIL_METHOD || "formspree",
smtpConfig: {
host: process.env.MAIL_HOST,
port: process.env.MAIL_PORT,
auth: {
user: process.env.MAIL_USER,
pass: process.env.MAIL_PASSWORD,
},
},
mailTo: process.env.MAIL_TO,
}; };

View file

@ -0,0 +1,50 @@
import express from "express";
import nodemailer from "nodemailer";
import { sendResponse } from "../../../libs/format";
import { mailMethod, smtpConfig, mailTo, siteName } from "../../../config";
import ErrorEvent from "../../../libs/error";
// eslint-disable-next-line new-cap
const router = express.Router();
router.route("/").post(async (req, res, next) => {
try {
if (mailMethod === "smtp") {
const { email, name, message } = req.body;
if (!email || !message) {
throw new ErrorEvent(
406,
"Le formulaire n'est pas correctement saisi"
);
}
const transporter = nodemailer.createTransport(smtpConfig);
const text = `Bonjour,
Vous venez de recevoir un nouveau message de ${name} (${email}) :
${message}
`;
const data = await transporter.sendMail({
from: smtpConfig.auth.user,
to: mailTo,
subject: `${siteName} : Nouveau message`,
text,
});
const { messageId, response } = data;
return sendResponse(req, res, { messageId, response });
}
throw new ErrorEvent(500, "Méthode non configurée");
} catch (err) {
return next(err);
}
});
export default router;

View file

@ -185,7 +185,6 @@
const entries = urlParams.entries(); const entries = urlParams.entries();
for(const entry of entries) { for(const entry of entries) {
console.log(`${entry[0]}: ${entry[1]}`);
switch(entry[0]) { switch(entry[0]) {
case 'artists_sort': case 'artists_sort':
this.artist = entry[1]; this.artist = entry[1];

View file

@ -239,7 +239,6 @@
const entries = urlParams.entries(); const entries = urlParams.entries();
for(const entry of entries) { for(const entry of entries) {
console.log(`${entry[0]}: ${entry[1]}`);
switch(entry[0]) { switch(entry[0]) {
case 'artists_sort': case 'artists_sort':
this.artist = entry[1]; this.artist = entry[1];

View file

@ -1,22 +1,70 @@
<section class="box"> <section class="box" id="app">
<h1>Nous contacter</h1> <h1>Nous contacter</h1>
<form action="https://formspree.io/f/<%= config.formspreeId %>" method="POST"> <form @submit="send" <% if (config.mailMethod === 'formspree' ) { %> id="contact" method="POST" action="https://formspree.io/f/<%= config.formspreeId %>" <% } %>>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-16"> <div class="grid grid-cols-1 lg:grid-cols-2 gap-16">
<div class="field"> <div class="field">
<label for="email">Addresse e-mail*</label> <label for="email">Addresse e-mail*</label>
<input type="email" name="email" id="email" required /> <input type="email" name="email" id="email" v-model="email" required />
</div> </div>
<div class="field"> <div class="field">
<label for="name">Prénom, nom</label> <label for="name">Prénom, nom</label>
<input type="text" name="name" id="name" /> <input type="text" name="name" id="name" v-model="name" />
</div> </div>
</div> </div>
<div class="field"> <div class="field">
<label for="message">Message*</label> <label for="message">Message*</label>
<textarea name="message" id="message" rows="6" required ></textarea> <textarea name="message" id="message" rows="6" required v-model="message" ></textarea>
</div> </div>
<button type="submit" class="button is-primary">Envoyer</button> <button type="submit" class="button is-primary" :disabled="loading">
<% if (config.mailMethod !== 'formspree' ) { %>
<i class="icon-spin animate-spin" v-if="loading"></i>
<% } %>
Envoyer
</button>
</form> </form>
</section> </section>
<% if (config.mailMethod === 'smtp' ) { %>
<script>
Vue.createApp({
data() {
return {
email: '',
name: '',
message: '',
loading: false,
}
},
methods: {
send(event) {
event.preventDefault();
if ( this.loading ) {
return false;
}
this.loading = true;
const {
email,
message,
name
} = this;
axios.post('/api/v1/contact', {email, name, message})
.then( () => {
showToastr("Message correctement envoyé", true);
})
.catch((err) => {
showToastr(err.response?.data?.message || "Impossible d'envoyer votre message", false);
})
.finally(() => {
this.loading = false;
})
},
},
}).mount('#app');
</script>
<% } %>