#40 - Formulaire de contact via SMTP #58
9 changed files with 134 additions and 10 deletions
11
README.md
11
README.md
|
@ -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
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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) => {
|
||||||
|
|
|
@ -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,
|
||||||
};
|
};
|
||||||
|
|
50
src/routes/api/v1/contact.js
Normal file
50
src/routes/api/v1/contact.js
Normal 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;
|
|
@ -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];
|
||||||
|
|
|
@ -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];
|
||||||
|
|
|
@ -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>
|
||||||
|
<% } %>
|
Loading…
Reference in a new issue