Checkpoint: Corrections complètes du système de pièces jointes pour les rappels :
- Upload de fichiers avec fallback automatique vers stockage local - Configuration Nginx pour uploads jusqu'à 10 Mo - Colonne "Pièce jointe" dans le tableau de gestion des rappels - Support des fichiers locaux et S3 dans l'envoi d'emails - Correction de l'encoding base64 pour SMTP/Nodemailer - Table logsrappels créée dans la base de données MySQL du VPS
This commit is contained in:
@@ -389,6 +389,7 @@ export default function AdminRappels() {
|
|||||||
<TableHead>Délai</TableHead>
|
<TableHead>Délai</TableHead>
|
||||||
<TableHead>Heure</TableHead>
|
<TableHead>Heure</TableHead>
|
||||||
<TableHead>Dates concernées</TableHead>
|
<TableHead>Dates concernées</TableHead>
|
||||||
|
<TableHead>Pièce jointe</TableHead>
|
||||||
<TableHead>Statut</TableHead>
|
<TableHead>Statut</TableHead>
|
||||||
<TableHead className="text-right">Actions</TableHead>
|
<TableHead className="text-right">Actions</TableHead>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
@@ -439,6 +440,15 @@ export default function AdminRappels() {
|
|||||||
<TableCell>
|
<TableCell>
|
||||||
{formatDatesInfo(rappel)}
|
{formatDatesInfo(rappel)}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
{rappel.nomFichier && rappel.urlFichier ? (
|
||||||
|
<Badge variant="outline" className="bg-blue-50 text-blue-700 border-blue-200">
|
||||||
|
📎 Oui
|
||||||
|
</Badge>
|
||||||
|
) : (
|
||||||
|
<span className="text-muted-foreground text-sm">Non</span>
|
||||||
|
)}
|
||||||
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<button
|
<button
|
||||||
onClick={() => handleToggleActif(rappel.id, rappel.actif)}
|
onClick={() => handleToggleActif(rappel.id, rappel.actif)}
|
||||||
@@ -473,7 +483,7 @@ export default function AdminRappels() {
|
|||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell colSpan={7} className="text-center text-muted-foreground">
|
<TableCell colSpan={8} className="text-center text-muted-foreground">
|
||||||
Aucun rappel configuré
|
Aucun rappel configuré
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ export async function sendViaSMTP(params: EmailParams, config: SMTPConfig): Prom
|
|||||||
const attachments = params.attachments?.map(att => ({
|
const attachments = params.attachments?.map(att => ({
|
||||||
filename: att.filename,
|
filename: att.filename,
|
||||||
content: att.content,
|
content: att.content,
|
||||||
|
encoding: 'base64' as const,
|
||||||
contentType: att.contentType,
|
contentType: att.contentType,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
@@ -291,10 +291,22 @@ export async function sendRappelEmail(params: {
|
|||||||
|
|
||||||
if (params.attachmentUrl && params.attachmentFilename && params.attachmentMimeType) {
|
if (params.attachmentUrl && params.attachmentFilename && params.attachmentMimeType) {
|
||||||
try {
|
try {
|
||||||
// Télécharger le fichier depuis S3 et le convertir en base64
|
let base64: string;
|
||||||
|
|
||||||
|
// Vérifier si c'est une URL locale (commence par /uploads/)
|
||||||
|
if (params.attachmentUrl.startsWith('/uploads/')) {
|
||||||
|
// Lire le fichier depuis le système de fichiers
|
||||||
|
const fs = await import('fs/promises');
|
||||||
|
const path = await import('path');
|
||||||
|
const filePath = path.join(process.cwd(), params.attachmentUrl);
|
||||||
|
const fileBuffer = await fs.readFile(filePath);
|
||||||
|
base64 = fileBuffer.toString('base64');
|
||||||
|
} else {
|
||||||
|
// Télécharger le fichier depuis une URL externe (S3)
|
||||||
const response = await fetch(params.attachmentUrl);
|
const response = await fetch(params.attachmentUrl);
|
||||||
const buffer = await response.arrayBuffer();
|
const arrayBuffer = await response.arrayBuffer();
|
||||||
const base64 = Buffer.from(buffer).toString('base64');
|
base64 = Buffer.from(arrayBuffer).toString('base64');
|
||||||
|
}
|
||||||
|
|
||||||
attachments.push({
|
attachments.push({
|
||||||
filename: params.attachmentFilename,
|
filename: params.attachmentFilename,
|
||||||
@@ -302,7 +314,7 @@ export async function sendRappelEmail(params: {
|
|||||||
contentType: params.attachmentMimeType,
|
contentType: params.attachmentMimeType,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[Email] Erreur lors du téléchargement de la pièce jointe:', error);
|
console.error('[Email] Erreur lors du chargement de la pièce jointe:', error);
|
||||||
// Continuer l'envoi sans la pièce jointe
|
// Continuer l'envoi sans la pièce jointe
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -389,10 +401,22 @@ export async function sendRappelJ7Email(params: {
|
|||||||
|
|
||||||
if (params.attachmentUrl && params.attachmentFilename && params.attachmentMimeType) {
|
if (params.attachmentUrl && params.attachmentFilename && params.attachmentMimeType) {
|
||||||
try {
|
try {
|
||||||
// Télécharger le fichier depuis S3 et le convertir en base64
|
let base64: string;
|
||||||
|
|
||||||
|
// Vérifier si c'est une URL locale (commence par /uploads/)
|
||||||
|
if (params.attachmentUrl.startsWith('/uploads/')) {
|
||||||
|
// Lire le fichier depuis le système de fichiers
|
||||||
|
const fs = await import('fs/promises');
|
||||||
|
const path = await import('path');
|
||||||
|
const filePath = path.join(process.cwd(), params.attachmentUrl);
|
||||||
|
const fileBuffer = await fs.readFile(filePath);
|
||||||
|
base64 = fileBuffer.toString('base64');
|
||||||
|
} else {
|
||||||
|
// Télécharger le fichier depuis une URL externe (S3)
|
||||||
const response = await fetch(params.attachmentUrl);
|
const response = await fetch(params.attachmentUrl);
|
||||||
const buffer = await response.arrayBuffer();
|
const arrayBuffer = await response.arrayBuffer();
|
||||||
const base64 = Buffer.from(buffer).toString('base64');
|
base64 = Buffer.from(arrayBuffer).toString('base64');
|
||||||
|
}
|
||||||
|
|
||||||
attachments.push({
|
attachments.push({
|
||||||
filename: params.attachmentFilename,
|
filename: params.attachmentFilename,
|
||||||
@@ -400,7 +424,7 @@ export async function sendRappelJ7Email(params: {
|
|||||||
contentType: params.attachmentMimeType,
|
contentType: params.attachmentMimeType,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[Email] Erreur lors du téléchargement de la pièce jointe:', error);
|
console.error('[Email] Erreur lors du chargement de la pièce jointe:', error);
|
||||||
// Continuer l'envoi sans la pièce jointe
|
// Continuer l'envoi sans la pièce jointe
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -490,10 +514,22 @@ export async function sendRappelJ1Email(params: {
|
|||||||
|
|
||||||
if (params.attachmentUrl && params.attachmentFilename && params.attachmentMimeType) {
|
if (params.attachmentUrl && params.attachmentFilename && params.attachmentMimeType) {
|
||||||
try {
|
try {
|
||||||
// Télécharger le fichier depuis S3 et le convertir en base64
|
let base64: string;
|
||||||
|
|
||||||
|
// Vérifier si c'est une URL locale (commence par /uploads/)
|
||||||
|
if (params.attachmentUrl.startsWith('/uploads/')) {
|
||||||
|
// Lire le fichier depuis le système de fichiers
|
||||||
|
const fs = await import('fs/promises');
|
||||||
|
const path = await import('path');
|
||||||
|
const filePath = path.join(process.cwd(), params.attachmentUrl);
|
||||||
|
const fileBuffer = await fs.readFile(filePath);
|
||||||
|
base64 = fileBuffer.toString('base64');
|
||||||
|
} else {
|
||||||
|
// Télécharger le fichier depuis une URL externe (S3)
|
||||||
const response = await fetch(params.attachmentUrl);
|
const response = await fetch(params.attachmentUrl);
|
||||||
const buffer = await response.arrayBuffer();
|
const arrayBuffer = await response.arrayBuffer();
|
||||||
const base64 = Buffer.from(buffer).toString('base64');
|
base64 = Buffer.from(arrayBuffer).toString('base64');
|
||||||
|
}
|
||||||
|
|
||||||
attachments.push({
|
attachments.push({
|
||||||
filename: params.attachmentFilename,
|
filename: params.attachmentFilename,
|
||||||
@@ -501,7 +537,7 @@ export async function sendRappelJ1Email(params: {
|
|||||||
contentType: params.attachmentMimeType,
|
contentType: params.attachmentMimeType,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[Email] Erreur lors du téléchargement de la pièce jointe:', error);
|
console.error('[Email] Erreur lors du chargement de la pièce jointe:', error);
|
||||||
// Continuer l'envoi sans la pièce jointe
|
// Continuer l'envoi sans la pièce jointe
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
5
todo.md
5
todo.md
@@ -848,3 +848,8 @@
|
|||||||
- [x] Corriger le problème du bouton de vidage de l'historique des rappels qui ne fonctionne plus (nom de table incorrect)
|
- [x] Corriger le problème du bouton de vidage de l'historique des rappels qui ne fonctionne plus (nom de table incorrect)
|
||||||
- [ ] Déployer l'intégralité du projet mis à jour sur le VPS (bouton de suppression des rappels et toutes les fonctionnalités manquantes)
|
- [ ] Déployer l'intégralité du projet mis à jour sur le VPS (bouton de suppression des rappels et toutes les fonctionnalités manquantes)
|
||||||
- [x] Configurer l'upload de fichiers pour utiliser le stockage local sur le VPS au lieu du service S3
|
- [x] Configurer l'upload de fichiers pour utiliser le stockage local sur le VPS au lieu du service S3
|
||||||
|
- [x] Corriger l'erreur d'upload de fichier depuis le navigateur sur le VPS (augmentation de la limite Nginx à 10 Mo)
|
||||||
|
- [x] Ajouter une colonne dans le tableau de gestion des rappels pour indiquer la présence d'une pièce jointe
|
||||||
|
- [x] Corriger la détection de la pièce jointe dans le tableau des rappels (vérification de nomFichier et urlFichier au lieu de pieceJointe)
|
||||||
|
- [x] Corriger l'envoi de la pièce jointe dans les emails de rappel (support des fichiers locaux et S3)
|
||||||
|
- [x] Corriger la corruption de la pièce jointe lors de l'envoi par email (ajout de l'encoding base64 pour SMTP/Nodemailer)
|
||||||
|
|||||||
Reference in New Issue
Block a user