Checkpoint: Version finale avec support SMTP complet :
Fonctionnalités ajoutées : - Support SMTP Direct pour l'envoi d'emails (Gmail, Outlook, serveurs personnalisés) - Interface de configuration SMTP dans l'administration - Basculement automatique entre Resend API et SMTP Direct - Affichage du statut de configuration (Resend/SMTP, Simulation/Production) Corrections apportées : - Correction du bug de décalage horaire dans le formulaire d'édition des séquences - Ajout du menu de navigation sur la page Import Excel - Déplacement du menu Import Excel vers le bloc Configuration - Correction de la fonction upsertEmailConfig pour mettre à jour au lieu de créer des doublons - Correction du bug d'enregistrement des champs SMTP dans la base de données - Correction du problème de cache React Query qui gardait l'ancienne configuration - Invalidation automatique du cache après enregistrement de la configuration Scripts de déploiement : - Scripts de mise à jour VPS avec sauvegarde automatique - Package de déploiement prêt pour le serveur de production
This commit is contained in:
17
.manus/db/db-query-1764510115106.json
Normal file
17
.manus/db/db-query-1764510115106.json
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"query": "SELECT id, provider, mode, active, smtpHost FROM emailConfig WHERE active = true;",
|
||||||
|
"command": "mysql --batch --raw --column-names --default-character-set=utf8mb4 --host gateway02.us-east-1.prod.aws.tidbcloud.com --port 4000 --user 4CrrYuB5tme73Qo.root --database 7PAT67UmWoxv8vwp8Bbcv6 --execute SELECT id, provider, mode, active, smtpHost FROM emailConfig WHERE active = true;",
|
||||||
|
"rows": [
|
||||||
|
{
|
||||||
|
"id": "90002",
|
||||||
|
"provider": "smtp",
|
||||||
|
"mode": "production",
|
||||||
|
"active": "1",
|
||||||
|
"smtpHost": "smtp.office365.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"messages": [],
|
||||||
|
"stdout": "id\tprovider\tmode\tactive\tsmtpHost\n90002\tsmtp\tproduction\t1\tsmtp.office365.com\n",
|
||||||
|
"stderr": "",
|
||||||
|
"execution_time_ms": 67
|
||||||
|
}
|
||||||
@@ -13,6 +13,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@
|
|||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||||
|
|
||||||
export default function AdminEmailConfig() {
|
export default function AdminEmailConfig() {
|
||||||
|
const utils = trpc.useUtils();
|
||||||
const { data: config, isLoading, refetch } = trpc.emailConfig.get.useQuery();
|
const { data: config, isLoading, refetch } = trpc.emailConfig.get.useQuery();
|
||||||
const upsertMutation = trpc.emailConfig.upsert.useMutation();
|
const upsertMutation = trpc.emailConfig.upsert.useMutation();
|
||||||
const testEmailMutation = trpc.emailConfig.testEmail.useMutation();
|
const testEmailMutation = trpc.emailConfig.testEmail.useMutation();
|
||||||
@@ -56,9 +57,11 @@ export default function AdminEmailConfig() {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
upsertMutation.mutate(formData, {
|
upsertMutation.mutate(formData, {
|
||||||
onSuccess: () => {
|
onSuccess: async () => {
|
||||||
toast.success("Configuration enregistrée avec succès");
|
toast.success("Configuration enregistrée avec succès");
|
||||||
refetch();
|
// Invalider le cache et forcer le rechargement
|
||||||
|
await utils.emailConfig.get.invalidate();
|
||||||
|
await refetch();
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
toast.error(`Erreur: ${error.message}`);
|
toast.error(`Erreur: ${error.message}`);
|
||||||
|
|||||||
112
test-smtp.mjs
Normal file
112
test-smtp.mjs
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
import { drizzle } from "drizzle-orm/mysql2";
|
||||||
|
import { eq } from "drizzle-orm";
|
||||||
|
import { emailConfig } from "./drizzle/schema.js";
|
||||||
|
import nodemailer from "nodemailer";
|
||||||
|
|
||||||
|
// Connexion à la base de données
|
||||||
|
const db = drizzle(process.env.DATABASE_URL);
|
||||||
|
|
||||||
|
// Récupérer la configuration SMTP
|
||||||
|
const config = await db
|
||||||
|
.select()
|
||||||
|
.from(emailConfig)
|
||||||
|
.where(eq(emailConfig.active, true))
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
if (config.length === 0) {
|
||||||
|
console.error("❌ Aucune configuration email active trouvée");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const emailConf = config[0];
|
||||||
|
|
||||||
|
console.log("📧 Configuration SMTP trouvée:");
|
||||||
|
console.log(" Provider:", emailConf.provider);
|
||||||
|
console.log(" Mode:", emailConf.mode);
|
||||||
|
console.log(" SMTP Host:", emailConf.smtpHost);
|
||||||
|
console.log(" SMTP Port:", emailConf.smtpPort);
|
||||||
|
console.log(" SMTP Secure:", emailConf.smtpSecure);
|
||||||
|
console.log(" SMTP User:", emailConf.smtpUser);
|
||||||
|
console.log(" From Email:", emailConf.fromEmail);
|
||||||
|
console.log(" From Name:", emailConf.fromName);
|
||||||
|
console.log("");
|
||||||
|
|
||||||
|
if (emailConf.provider !== "smtp") {
|
||||||
|
console.error("❌ Le provider n'est pas 'smtp' mais:", emailConf.provider);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!emailConf.smtpHost || !emailConf.smtpPort || !emailConf.smtpUser || !emailConf.smtpPassword) {
|
||||||
|
console.error("❌ Configuration SMTP incomplète");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("🔧 Création du transporteur SMTP...");
|
||||||
|
|
||||||
|
const transporterConfig = {
|
||||||
|
host: emailConf.smtpHost,
|
||||||
|
port: emailConf.smtpPort,
|
||||||
|
secure: emailConf.smtpSecure === "ssl",
|
||||||
|
auth: {
|
||||||
|
user: emailConf.smtpUser,
|
||||||
|
pass: emailConf.smtpPassword,
|
||||||
|
},
|
||||||
|
tls: {
|
||||||
|
rejectUnauthorized: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (emailConf.smtpSecure === "tls") {
|
||||||
|
transporterConfig.requireTLS = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(" Config:", JSON.stringify({
|
||||||
|
...transporterConfig,
|
||||||
|
auth: { user: transporterConfig.auth.user, pass: "***" }
|
||||||
|
}, null, 2));
|
||||||
|
|
||||||
|
const transporter = nodemailer.createTransport(transporterConfig);
|
||||||
|
|
||||||
|
console.log("");
|
||||||
|
console.log("🔍 Test de connexion SMTP...");
|
||||||
|
|
||||||
|
try {
|
||||||
|
await transporter.verify();
|
||||||
|
console.log("✅ Connexion SMTP réussie !");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("❌ Erreur de connexion SMTP:", error.message);
|
||||||
|
console.error("Détails:", error);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("");
|
||||||
|
console.log("📨 Envoi d'un email de test...");
|
||||||
|
|
||||||
|
const testEmail = process.argv[2] || "test@example.com";
|
||||||
|
|
||||||
|
try {
|
||||||
|
const info = await transporter.sendMail({
|
||||||
|
from: `"${emailConf.fromName}" <${emailConf.fromEmail}>`,
|
||||||
|
to: testEmail,
|
||||||
|
subject: "Test SMTP - Formation Manager Itinova",
|
||||||
|
html: `
|
||||||
|
<h1>✅ Test réussi !</h1>
|
||||||
|
<p>Cet email de test a été envoyé avec succès depuis votre configuration SMTP.</p>
|
||||||
|
<p>Votre configuration d'envoi d'emails fonctionne correctement.</p>
|
||||||
|
<hr>
|
||||||
|
<p><small>Configuration: ${emailConf.smtpHost}:${emailConf.smtpPort} (${emailConf.smtpSecure})</small></p>
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("✅ Email envoyé avec succès !");
|
||||||
|
console.log(" Message ID:", info.messageId);
|
||||||
|
console.log(" Response:", info.response);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("❌ Erreur lors de l'envoi:", error.message);
|
||||||
|
console.error("Détails:", error);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("");
|
||||||
|
console.log("🎉 Test terminé avec succès !");
|
||||||
|
process.exit(0);
|
||||||
15
todo.md
15
todo.md
@@ -755,4 +755,17 @@
|
|||||||
- [x] Corriger la fonction upsertEmailConfig pour mettre à jour au lieu de créer
|
- [x] Corriger la fonction upsertEmailConfig pour mettre à jour au lieu de créer
|
||||||
- [x] Forcer la mise à jour du provider dans la base de données
|
- [x] Forcer la mise à jour du provider dans la base de données
|
||||||
- [x] Vérifier que la configuration SMTP est bien enregistrée (provider=smtp)
|
- [x] Vérifier que la configuration SMTP est bien enregistrée (provider=smtp)
|
||||||
- [ ] Tester l'envoi d'email de test en mode production (après hard refresh)
|
- [x] Tester l'envoi d'email de test en mode production (correction du cache appliquée)
|
||||||
|
|
||||||
|
## Bug email de test SMTP non reçu
|
||||||
|
|
||||||
|
- [x] Vérifier les logs du serveur pour voir les erreurs SMTP
|
||||||
|
- [x] Vérifier que le mot de passe SMTP est correct
|
||||||
|
- [x] Identifier que le mail de simulation est envoyé au lieu de SMTP
|
||||||
|
- [x] Corriger le problème de cache React Query qui garde provider='resend'
|
||||||
|
- [x] Forcer l'invalidation du cache après enregistrement (utils.emailConfig.get.invalidate())
|
||||||
|
|
||||||
|
## Erreur "Unexpected token '<'" sur /admin/email-config
|
||||||
|
|
||||||
|
- [x] Vérifier les logs du serveur pour voir l'erreur exacte (erreur temporaire liée au redémarrage)
|
||||||
|
- [x] Tester le chargement de la page (fonctionne correctement)
|
||||||
|
|||||||
Reference in New Issue
Block a user