Checkpoint: Ajout de la possibilité de joindre un fichier dans l'envoi des rappels par email

Modifications apportées :
- Ajout des colonnes nomFichier, urlFichier, s3Key, typeFichier, tailleFichier dans la table rappels
- Création du composant FileUpload pour l'upload de fichiers vers S3 (max 10 Mo)
- Création de l'endpoint API /api/upload-file pour gérer l'upload
- Intégration du composant FileUpload dans les formulaires de création et modification de rappels
- Mise à jour des procédures tRPC create et update pour gérer les informations de fichier
- Modification des fonctions sendRappelJ7Email et sendRappelJ1Email pour inclure les pièces jointes
- Mise à jour du scheduler de rappels pour passer les informations de fichier lors de l'envoi
- Les fichiers sont téléchargés depuis S3 et convertis en base64 pour être attachés aux emails
This commit is contained in:
Manus
2026-01-08 06:00:27 -05:00
parent 6e25c00865
commit 5308b7921e
16 changed files with 2613 additions and 4 deletions

View File

@@ -0,0 +1,59 @@
import { Request, Response } from "express";
import multer from "multer";
import { storagePut } from "../storage";
import crypto from "crypto";
import path from "path";
// Configuration de multer pour l'upload en mémoire
const upload = multer({
storage: multer.memoryStorage(),
limits: {
fileSize: 10 * 1024 * 1024, // 10 Mo max
},
});
// Middleware pour l'upload d'un seul fichier
export const uploadSingle = upload.single("file");
// Handler pour l'upload de fichier vers S3
export async function handleFileUpload(req: Request, res: Response) {
try {
if (!req.file) {
return res.status(400).json({ error: "Aucun fichier fourni" });
}
const file = req.file;
// Générer un nom de fichier unique
const randomSuffix = crypto.randomBytes(8).toString("hex");
const ext = path.extname(file.originalname);
const baseName = path.basename(file.originalname, ext);
const safeBaseName = baseName.replace(/[^a-zA-Z0-9-_]/g, "_");
const fileName = `${safeBaseName}-${randomSuffix}${ext}`;
// Chemin S3 pour les pièces jointes de rappels
const s3Key = `rappels-attachments/${fileName}`;
// Upload vers S3
const { url, key } = await storagePut(
s3Key,
file.buffer,
file.mimetype
);
console.log(`[FileUpload] Fichier uploadé: ${fileName} (${file.size} octets)`);
return res.json({
url,
key,
filename: file.originalname,
mimetype: file.mimetype,
size: file.size,
});
} catch (error) {
console.error("[FileUpload] Erreur:", error);
return res.status(500).json({
error: "Erreur lors de l'upload du fichier"
});
}
}

View File

@@ -8,6 +8,7 @@ import { appRouter } from "../routers";
import { createContext } from "./context";
import localAuthRouter from "./localAuth";
import uploadImageRouter from "./uploadImage";
import uploadFileRouter from "./uploadFile";
import { serveStatic, setupVite } from "./vite";
import { initRappelScheduler } from "../rappelScheduler";
import { initRappelRetryScheduler } from "../rappelRetry";
@@ -43,6 +44,8 @@ async function startServer() {
app.use("/api/auth/local", localAuthRouter);
// Image upload under /api/upload-image
app.use("/api/upload-image", uploadImageRouter);
// File upload under /api/upload-file
app.use("/api/upload-file", uploadFileRouter);
// tRPC API
app.use(
"/api/trpc",

View File

@@ -0,0 +1,67 @@
import { Router } from "express";
import multer from "multer";
import { storagePut } from "../storage";
import { randomBytes } from "crypto";
import path from "path";
const router = Router();
// Configuration de multer pour gérer les uploads en mémoire
const upload = multer({
storage: multer.memoryStorage(),
limits: {
fileSize: 10 * 1024 * 1024, // 10MB max
},
});
/**
* Route d'upload de fichiers vers S3
* POST /api/upload-file
* Body: multipart/form-data avec un champ "file"
*/
router.post("/", upload.single("file"), async (req, res) => {
try {
if (!req.file) {
return res.status(400).json({
success: false,
message: "Aucun fichier fourni",
});
}
const file = req.file;
// Générer un nom de fichier unique
const randomSuffix = randomBytes(8).toString("hex");
const ext = path.extname(file.originalname);
const baseName = path.basename(file.originalname, ext);
const safeBaseName = baseName.replace(/[^a-zA-Z0-9-_]/g, "_");
const fileName = `rappels-attachments/${Date.now()}-${randomSuffix}-${safeBaseName}${ext}`;
// Upload vers S3
const result = await storagePut(
fileName,
file.buffer,
file.mimetype
);
console.log(`[UploadFile] Fichier uploadé: ${file.originalname} (${file.size} octets)`);
return res.json({
success: true,
url: result.url,
key: fileName,
filename: file.originalname,
mimetype: file.mimetype,
size: file.size,
message: "Fichier uploadé avec succès",
});
} catch (error) {
console.error("[UploadFile] Error:", error);
return res.status(500).json({
success: false,
message: "Erreur lors de l'upload du fichier",
});
}
});
export default router;