- Ajout colonne employee_docuseal_slug dans cddu_contracts - Stockage automatique du slug lors de création signature DocuSeal - Recherche directe par slug (+ rapide et fiable) - Bypass mode maintenance en localhost - Scripts de migration pour contrats existants (92 contrats migrés) - Logs détaillés dans verify-birthdate et check-status Fixes: Erreur 'Document introuvable' pour contrats anciens Performance: O(n) -> O(1) avec index sur employee_docuseal_slug
540 lines
No EOL
20 KiB
TypeScript
540 lines
No EOL
20 KiB
TypeScript
import { NextRequest, NextResponse } from 'next/server';
|
||
import { createSbServiceRole } from '@/lib/supabaseServer';
|
||
import { DynamoDBClient, PutItemCommand, UpdateItemCommand } from '@aws-sdk/client-dynamodb';
|
||
import { S3Client, GetObjectCommand, PutObjectCommand } from '@aws-sdk/client-s3';
|
||
import { sendUniversalEmailV2, renderUniversalEmailV2, EmailDataV2 } from '@/lib/emailTemplateService';
|
||
import { ENV } from '@/lib/cleanEnv';
|
||
import axios from 'axios';
|
||
|
||
// Configuration AWS
|
||
const region = ENV.AWS_REGION;
|
||
const dynamoDBClient = new DynamoDBClient({
|
||
region,
|
||
credentials: {
|
||
accessKeyId: ENV.AWS_ACCESS_KEY_ID,
|
||
secretAccessKey: ENV.AWS_SECRET_ACCESS_KEY
|
||
}
|
||
});
|
||
|
||
// Vérification des variables d'environnement au démarrage
|
||
console.log('🔧 Configuration DynamoDB:', {
|
||
region,
|
||
hasAccessKey: !!ENV.AWS_ACCESS_KEY_ID,
|
||
hasSecretKey: !!ENV.AWS_SECRET_ACCESS_KEY,
|
||
accessKeyLength: ENV.AWS_ACCESS_KEY_ID?.length,
|
||
secretKeyLength: ENV.AWS_SECRET_ACCESS_KEY?.length
|
||
});
|
||
const s3Client = new S3Client({ region });
|
||
// Envoi d'e-mails géré via le service de templates universels
|
||
|
||
export async function POST(request: NextRequest) {
|
||
console.log('=== DocuSeal Signature API ===');
|
||
|
||
try {
|
||
// Utiliser le service role pour les opérations serveur
|
||
const supabase = createSbServiceRole();
|
||
|
||
const data = await request.json();
|
||
console.log('Données reçues:', JSON.stringify(data, null, 2));
|
||
|
||
const {
|
||
contractId,
|
||
pdfS3Key,
|
||
employerEmail,
|
||
employeeEmail,
|
||
reference,
|
||
employeeName,
|
||
startDate,
|
||
role,
|
||
analytique,
|
||
structure,
|
||
signerFirstName,
|
||
employerCode,
|
||
employeeFirstName,
|
||
matricule,
|
||
contractType = 'CDDU',
|
||
skipEmployerEmail = false, // Nouveau paramètre pour éviter l'envoi d'email à l'employeur
|
||
orgId = null // ID de l'organisation pour récupérer la signature
|
||
} = data;
|
||
|
||
// Validation des champs requis
|
||
if (!contractId || !pdfS3Key || !employerEmail || !employeeEmail) {
|
||
return NextResponse.json(
|
||
{ error: 'Champs manquants: contractId, pdfS3Key, employerEmail, employeeEmail' },
|
||
{ status: 400 }
|
||
);
|
||
}
|
||
|
||
// 🔒 SÉCURITÉ : Validation du format des emails
|
||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||
|
||
if (!emailRegex.test(employeeEmail)) {
|
||
console.error('❌ [SÉCURITÉ] Format d\'email salarié invalide:', employeeEmail);
|
||
return NextResponse.json(
|
||
{ error: 'Format d\'email salarié invalide' },
|
||
{ status: 400 }
|
||
);
|
||
}
|
||
|
||
// Validation de l'email employeur avec fallback sécurisé
|
||
let validatedEmployerEmail = employerEmail;
|
||
if (!emailRegex.test(employerEmail)) {
|
||
console.warn('⚠️ [SÉCURITÉ] Email employeur invalide, utilisation du fallback:', employerEmail);
|
||
validatedEmployerEmail = "paie@odentas.fr";
|
||
console.log('✅ [SÉCURITÉ] Email employeur remplacé par:', validatedEmployerEmail);
|
||
}
|
||
|
||
// 🔒 SÉCURITÉ CRITIQUE : Vérifier que le contrat appartient bien à l'organisation spécifiée
|
||
console.log('🔒 [SÉCURITÉ] Vérification de la cohérence contrat/organisation...');
|
||
console.log('🔒 [SÉCURITÉ] contractId:', contractId);
|
||
console.log('🔒 [SÉCURITÉ] orgId fourni:', orgId);
|
||
|
||
const { data: contractVerification, error: contractVerifError } = await supabase
|
||
.from('cddu_contracts')
|
||
.select('org_id, employee_matricule, employee_name, contract_number')
|
||
.eq('id', contractId)
|
||
.single();
|
||
|
||
if (contractVerifError || !contractVerification) {
|
||
console.error('❌ [SÉCURITÉ] Contrat non trouvé:', contractVerifError);
|
||
return NextResponse.json(
|
||
{ error: 'Contrat introuvable' },
|
||
{ status: 404 }
|
||
);
|
||
}
|
||
|
||
console.log('🔒 [SÉCURITÉ] org_id du contrat en base:', contractVerification.org_id);
|
||
|
||
// Vérifier que l'org_id fourni correspond bien à celui du contrat
|
||
if (orgId && contractVerification.org_id !== orgId) {
|
||
console.error('❌ [SÉCURITÉ CRITIQUE] Tentative de signature avec une mauvaise organisation!');
|
||
console.error(' - org_id du contrat:', contractVerification.org_id);
|
||
console.error(' - org_id fourni:', orgId);
|
||
console.error(' - contrat:', contractVerification.contract_number);
|
||
console.error(' - salarié:', contractVerification.employee_name);
|
||
|
||
return NextResponse.json(
|
||
{
|
||
error: 'SÉCURITÉ : Le contrat n\'appartient pas à l\'organisation spécifiée',
|
||
details: 'Incohérence détectée entre le contrat et l\'organisation'
|
||
},
|
||
{ status: 403 }
|
||
);
|
||
}
|
||
|
||
// Vérifier également que le matricule fourni correspond (double vérification)
|
||
if (matricule && contractVerification.employee_matricule !== matricule) {
|
||
console.error('❌ [SÉCURITÉ] Incohérence détectée sur le matricule du salarié!');
|
||
console.error(' - Matricule du contrat:', contractVerification.employee_matricule);
|
||
console.error(' - Matricule fourni:', matricule);
|
||
|
||
return NextResponse.json(
|
||
{
|
||
error: 'SÉCURITÉ : Le matricule du salarié ne correspond pas au contrat',
|
||
details: 'Incohérence détectée entre les données'
|
||
},
|
||
{ status: 403 }
|
||
);
|
||
}
|
||
|
||
console.log('✅ [SÉCURITÉ] Vérification réussie - Le contrat appartient bien à l\'organisation');
|
||
console.log(' - Contrat:', contractVerification.contract_number);
|
||
console.log(' - Organisation:', contractVerification.org_id);
|
||
console.log(' - Salarié:', contractVerification.employee_name);
|
||
console.log(' - Matricule:', contractVerification.employee_matricule);
|
||
|
||
// Récupérer la signature base64 de l'organisation si disponible
|
||
let employerSignatureB64 = null;
|
||
if (orgId) {
|
||
console.log('🔍 [SIGNATURE] Recherche de la signature pour org_id:', orgId);
|
||
const { data: orgDetails } = await supabase
|
||
.from('organization_details')
|
||
.select('signature_b64')
|
||
.eq('org_id', orgId)
|
||
.maybeSingle();
|
||
|
||
if (orgDetails?.signature_b64) {
|
||
employerSignatureB64 = orgDetails.signature_b64;
|
||
console.log('✅ [SIGNATURE] Signature trouvée pour l\'employeur');
|
||
} else {
|
||
console.log('ℹ️ [SIGNATURE] Aucune signature trouvée pour l\'employeur');
|
||
}
|
||
}
|
||
|
||
// Extraire le submission_id depuis le nom du fichier PDF
|
||
// pdfS3Key format: "unsigned-contracts/contrat_cddu_REFERENCE.pdf"
|
||
// submission_id attendu: "contrats/contrat_cddu_REFERENCE" (format pour DynamoDB)
|
||
const pdfFileName = pdfS3Key.split('/').pop() || ''; // "contrat_cddu_REFERENCE.pdf"
|
||
const contractFileName = pdfFileName.replace('.pdf', ''); // "contrat_cddu_REFERENCE"
|
||
const submissionId = `contrats/${contractFileName}`; // "contrats/contrat_cddu_REFERENCE"
|
||
console.log('📝 [SUBMISSION] pdfS3Key:', pdfS3Key);
|
||
console.log('📝 [SUBMISSION] pdfFileName:', pdfFileName);
|
||
console.log('📝 [SUBMISSION] contractFileName:', contractFileName);
|
||
console.log('📝 [SUBMISSION] submission_id généré:', submissionId);
|
||
const formattedDate = formatDate(startDate);
|
||
|
||
// Étape 1 : Stocker les données du contrat dans DynamoDB
|
||
console.log('🗄️ [DYNAMODB] Tentative de stockage pour submission_id:', submissionId);
|
||
try {
|
||
await storeContractData(submissionId, {
|
||
employerEmail: validatedEmployerEmail,
|
||
employeeEmail,
|
||
reference,
|
||
salarie: employeeName,
|
||
date: startDate,
|
||
poste: role,
|
||
analytique,
|
||
structure,
|
||
prenom_signataire: signerFirstName,
|
||
code_employeur: employerCode,
|
||
prenom_salarie: employeeFirstName,
|
||
matricule,
|
||
typecontrat: contractType
|
||
});
|
||
console.log('✅ [DYNAMODB] Données du contrat stockées avec succès dans DynamoDB');
|
||
} catch (dynamoError) {
|
||
console.error('❌ [DYNAMODB] Erreur détaillée lors du stockage:', {
|
||
error: dynamoError,
|
||
message: dynamoError instanceof Error ? dynamoError.message : 'Erreur inconnue',
|
||
stack: dynamoError instanceof Error ? dynamoError.stack : undefined
|
||
});
|
||
// Continue le processus même si DynamoDB échoue
|
||
}
|
||
|
||
// Étape 2 : Récupération du PDF depuis S3
|
||
const getObjectCommand = new GetObjectCommand({
|
||
Bucket: ENV.AWS_S3_BUCKET,
|
||
Key: pdfS3Key,
|
||
});
|
||
|
||
const s3Object = await s3Client.send(getObjectCommand);
|
||
const bodyBytes = await s3Object.Body?.transformToByteArray();
|
||
|
||
if (!bodyBytes) {
|
||
throw new Error('Impossible de lire le contenu du fichier PDF depuis S3');
|
||
}
|
||
|
||
const pdfBase64 = Buffer.from(bodyBytes).toString('base64');
|
||
console.log('PDF récupéré depuis S3:', pdfS3Key);
|
||
|
||
// Extraire le nom de fichier depuis la clé S3 (exemple: "unsigned-contracts/contrat_cddu_6ET5Z3XW_DEMO002.pdf")
|
||
const docusealFileName = pdfS3Key.split('/').pop() || `contrat_${submissionId}.pdf`;
|
||
console.log('Nom de fichier extrait pour DocuSeal:', docusealFileName);
|
||
|
||
// Debug de la configuration DocuSeal
|
||
console.log('🔧 [DOCUSEAL] Configuration:', {
|
||
hasToken: !!ENV.DOCUSEAL_TOKEN,
|
||
tokenLength: ENV.DOCUSEAL_TOKEN.length,
|
||
tokenPreview: ENV.DOCUSEAL_TOKEN.substring(0, 10) + '...',
|
||
apiBase: ENV.DOCUSEAL_API_BASE,
|
||
apiBaseRaw: process.env.DOCUSEAL_API_BASE,
|
||
rawTokenLength: process.env.DOCUSEAL_TOKEN?.length
|
||
});
|
||
|
||
// Étape 3 : Création du template DocuSeal
|
||
const templateResponse = await axios.post(`${ENV.DOCUSEAL_API_BASE}/templates/pdf`, {
|
||
name: `CDDU - ${reference}`,
|
||
documents: [{ name: docusealFileName, file: pdfBase64 }]
|
||
}, {
|
||
headers: {
|
||
'X-Auth-Token': ENV.DOCUSEAL_TOKEN,
|
||
'Content-Type': 'application/json'
|
||
}
|
||
});
|
||
|
||
const templateId = templateResponse.data.id;
|
||
console.log('Template DocuSeal créé:', templateResponse.data);
|
||
|
||
// Étape 4 : Création de la soumission DocuSeal
|
||
console.log('🔧 [DOCUSEAL] Création de la soumission avec:', {
|
||
templateId,
|
||
hasEmployerSignature: !!employerSignatureB64,
|
||
signaturePreview: employerSignatureB64 ? employerSignatureB64.substring(0, 50) + '...' : 'N/A'
|
||
});
|
||
|
||
const submissionPayload = {
|
||
template_id: templateId,
|
||
send_email: false,
|
||
submitters: [
|
||
{
|
||
role: 'Employeur',
|
||
email: validatedEmployerEmail,
|
||
// Pré-remplir la signature de l'employeur si disponible
|
||
...(employerSignatureB64 && {
|
||
values: {
|
||
signature: employerSignatureB64
|
||
}
|
||
})
|
||
},
|
||
{
|
||
role: 'Salarié',
|
||
email: employeeEmail
|
||
}
|
||
]
|
||
};
|
||
|
||
console.log('📤 [DOCUSEAL] Payload complet:', JSON.stringify(submissionPayload, null, 2));
|
||
|
||
const submissionResponse = await axios.post(`${ENV.DOCUSEAL_API_BASE}/submissions`, submissionPayload, {
|
||
headers: {
|
||
'X-Auth-Token': ENV.DOCUSEAL_TOKEN,
|
||
'Content-Type': 'application/json'
|
||
}
|
||
});
|
||
|
||
console.log('Soumission DocuSeal créée:', submissionResponse.data);
|
||
if (employerSignatureB64) {
|
||
console.log('✅ [SIGNATURE] Signature employeur pré-remplie');
|
||
}
|
||
|
||
// Récupérer le submission_id DocuSeal
|
||
const docusealSubmissionId = submissionResponse.data[0].submission_id;
|
||
console.log(`DocuSeal submission_id: ${docusealSubmissionId}`);
|
||
|
||
// Mettre à jour DynamoDB avec le docusealSubID
|
||
console.log('🗄️ [DYNAMODB] Mise à jour avec docusealSubID:', docusealSubmissionId);
|
||
try {
|
||
await updateContractWithDocusealId(submissionId, docusealSubmissionId);
|
||
console.log('✅ [DYNAMODB] Mise à jour réussie avec docusealSubID');
|
||
} catch (updateError) {
|
||
console.error('❌ [DYNAMODB] Erreur lors de la mise à jour:', updateError);
|
||
}
|
||
|
||
// Récupérer les informations pour le lien de signature
|
||
const employerSubmission = submissionResponse.data.find((sub: any) => sub.role === 'Employeur');
|
||
const employeeSubmission = submissionResponse.data.find((sub: any) => sub.role === 'Salarié');
|
||
const embedCode = employerSubmission.slug;
|
||
const employeeSlug = employeeSubmission?.slug || null;
|
||
|
||
// Construire l'URL propre sans paramètres (les data-* sont ajoutés sur le composant HTML)
|
||
const signatureLink = `https://staging.paie.odentas.fr/odentas-sign?docuseal_id=${embedCode}`;
|
||
|
||
console.log('🔗 [SIGNATURE] Lien généré:', signatureLink);
|
||
console.log('🔗 [SIGNATURE] Slug salarié:', employeeSlug);
|
||
if (employerSignatureB64) {
|
||
console.log('✅ [SIGNATURE] Signature B64 disponible pour pré-remplissage côté client');
|
||
}
|
||
|
||
// Étape 5 : Envoi de l'email de signature via le template universel (employeur)
|
||
// Seulement si skipEmployerEmail est false (signature individuelle)
|
||
let messageId = '';
|
||
let emailLink = '';
|
||
|
||
if (!skipEmployerEmail) {
|
||
const emailData: EmailDataV2 = {
|
||
firstName: signerFirstName,
|
||
organizationName: structure,
|
||
employerCode: employerCode,
|
||
employeeName: employeeName,
|
||
documentType: contractType || 'Contrat',
|
||
contractReference: reference,
|
||
status: 'En attente',
|
||
ctaUrl: signatureLink,
|
||
};
|
||
|
||
// Rendu HTML pour archivage puis envoi
|
||
const rendered = await renderUniversalEmailV2({
|
||
type: 'signature-request-employer',
|
||
toEmail: validatedEmployerEmail,
|
||
subject: `Demande de signature – ${reference}`,
|
||
data: emailData,
|
||
});
|
||
|
||
messageId = await sendUniversalEmailV2({
|
||
type: 'signature-request-employer',
|
||
toEmail: validatedEmployerEmail,
|
||
subject: `Demande de signature – ${reference}`,
|
||
data: emailData,
|
||
});
|
||
|
||
// Étape 6 : Upload de l'email HTML rendu sur S3 et logging
|
||
const emailHtml = rendered.html;
|
||
emailLink = await uploadEmailToS3(emailHtml, messageId);
|
||
console.log('Email HTML uploadé sur S3:', emailLink);
|
||
} else {
|
||
console.log('⏭️ [EMAIL] Envoi d\'email employeur ignoré (mode bulk)');
|
||
}
|
||
|
||
// Étape 7 : Mise à jour du contrat dans Supabase avec les infos DocuSeal
|
||
console.log('🗄️ [SUPABASE] Début de la mise à jour du contrat:', {
|
||
contractId,
|
||
templateId,
|
||
docusealSubmissionId,
|
||
signatureLink,
|
||
employeeSlug
|
||
});
|
||
|
||
const supabaseResult = await supabase
|
||
.from('cddu_contracts')
|
||
.update({
|
||
docuseal_template_id: templateId,
|
||
docuseal_submission_id: docusealSubmissionId,
|
||
employee_docuseal_slug: employeeSlug,
|
||
signature_status: 'En attente',
|
||
signature_link: signatureLink,
|
||
updated_at: new Date().toISOString()
|
||
})
|
||
.eq('id', contractId);
|
||
|
||
console.log('🗄️ [SUPABASE] Résultat de la mise à jour:', {
|
||
error: supabaseResult.error,
|
||
status: supabaseResult.status,
|
||
statusText: supabaseResult.statusText,
|
||
count: supabaseResult.count
|
||
});
|
||
|
||
if (supabaseResult.error) {
|
||
console.error('❌ [SUPABASE] Erreur lors de la mise à jour:', supabaseResult.error);
|
||
throw new Error(`Erreur Supabase: ${supabaseResult.error.message}`);
|
||
}
|
||
|
||
console.log('✅ [SUPABASE] Contrat mis à jour avec succès dans Supabase');
|
||
|
||
return NextResponse.json({
|
||
success: true,
|
||
message: 'Signature électronique initiée avec succès',
|
||
data: {
|
||
templateId,
|
||
submissionId: docusealSubmissionId,
|
||
signatureLink,
|
||
emailLink
|
||
}
|
||
});
|
||
|
||
} catch (error) {
|
||
console.error('Erreur lors de l\'initiation de la signature:', error);
|
||
return NextResponse.json(
|
||
{
|
||
error: 'Erreur lors de l\'initiation de la signature électronique',
|
||
details: error instanceof Error ? error.message : 'Erreur inconnue'
|
||
},
|
||
{ status: 500 }
|
||
);
|
||
}
|
||
}
|
||
|
||
// Fonction pour formater la date
|
||
function formatDate(dateStr: string): string {
|
||
try {
|
||
const dateObj = new Date(dateStr);
|
||
if (isNaN(dateObj.getTime())) {
|
||
console.error('Date invalide:', dateStr);
|
||
return dateStr;
|
||
}
|
||
|
||
const day = String(dateObj.getUTCDate()).padStart(2, '0');
|
||
const month = String(dateObj.getUTCMonth() + 1).padStart(2, '0');
|
||
const year = dateObj.getUTCFullYear();
|
||
|
||
return `${day}/${month}/${year}`;
|
||
} catch (error) {
|
||
console.error('Erreur lors du formatage de la date:', error);
|
||
return dateStr;
|
||
}
|
||
}
|
||
|
||
// Fonction pour stocker les données du contrat dans DynamoDB
|
||
async function storeContractData(submissionId: string, data: any) {
|
||
console.log('🗄️ [DYNAMODB] storeContractData - Données à sauvegarder:', {
|
||
submissionId,
|
||
dataKeys: Object.keys(data),
|
||
data
|
||
});
|
||
|
||
const command = new PutItemCommand({
|
||
TableName: 'DocuSealNotification',
|
||
Item: {
|
||
submission_id: { S: submissionId },
|
||
employerEmail: { S: data.employerEmail || '' },
|
||
employeeEmail: { S: data.employeeEmail || '' },
|
||
reference: { S: data.reference || '' },
|
||
salarie: { S: data.salarie || '' },
|
||
date: { S: data.date || '' },
|
||
poste: { S: data.poste || '' },
|
||
analytique: { S: data.analytique || '' },
|
||
structure: { S: data.structure || '' },
|
||
prenom_signataire: { S: data.prenom_signataire || '' },
|
||
code_employeur: { S: data.code_employeur || '' },
|
||
prenom_salarie: { S: data.prenom_salarie || '' },
|
||
matricule: { S: data.matricule || '' },
|
||
typecontrat: { S: data.typecontrat || '' }
|
||
}
|
||
});
|
||
|
||
console.log('🗄️ [DYNAMODB] Commande DynamoDB préparée:', {
|
||
TableName: command.input.TableName,
|
||
submissionId: command.input.Item?.submission_id
|
||
});
|
||
|
||
try {
|
||
const result = await dynamoDBClient.send(command);
|
||
console.log('✅ [DYNAMODB] PutItem réussi:', result);
|
||
return result;
|
||
} catch (error) {
|
||
console.error('❌ [DYNAMODB] Erreur détaillée dans storeContractData:', {
|
||
error,
|
||
errorName: error instanceof Error ? error.name : 'Unknown',
|
||
errorMessage: error instanceof Error ? error.message : 'Unknown error',
|
||
errorCode: (error as any)?.code,
|
||
errorRequestId: (error as any)?.$metadata?.requestId
|
||
});
|
||
// Ne pas faire échouer tout le processus pour DynamoDB
|
||
console.log('⚠️ Continuing without DynamoDB storage...');
|
||
throw error; // Re-throw pour que le catch parent puisse logger
|
||
}
|
||
}
|
||
|
||
// Fonction pour mettre à jour l'enregistrement dans DynamoDB
|
||
async function updateContractWithDocusealId(submissionId: string, docusealSubID: string | number) {
|
||
console.log('🗄️ [DYNAMODB] updateContractWithDocusealId:', { submissionId, docusealSubID });
|
||
|
||
const command = new UpdateItemCommand({
|
||
TableName: 'DocuSealNotification',
|
||
Key: {
|
||
submission_id: { S: submissionId }
|
||
},
|
||
UpdateExpression: 'set docusealSubID = :docusealSubID',
|
||
ExpressionAttributeValues: {
|
||
':docusealSubID': { S: String(docusealSubID) } // Convertir en string
|
||
},
|
||
ReturnValues: 'UPDATED_NEW'
|
||
});
|
||
|
||
try {
|
||
const result = await dynamoDBClient.send(command);
|
||
console.log('✅ [DYNAMODB] UpdateItem réussi:', result);
|
||
return result;
|
||
} catch (error) {
|
||
console.error('❌ [DYNAMODB] Erreur détaillée dans updateContractWithDocusealId:', {
|
||
error,
|
||
errorName: error instanceof Error ? error.name : 'Unknown',
|
||
errorMessage: error instanceof Error ? error.message : 'Unknown error',
|
||
errorCode: (error as any)?.code,
|
||
errorRequestId: (error as any)?.$metadata?.requestId
|
||
});
|
||
// Ne pas faire échouer tout le processus pour DynamoDB
|
||
console.log('⚠️ Continuing without DynamoDB update...');
|
||
throw error; // Re-throw pour le catch parent
|
||
}
|
||
}
|
||
|
||
// Fonction pour uploader l'email HTML sur S3
|
||
async function uploadEmailToS3(emailHtml: string, key: string): Promise<string> {
|
||
const command = new PutObjectCommand({
|
||
Bucket: ENV.S3_BUCKET_NAME_EMAILS,
|
||
Key: `${key}.html`,
|
||
Body: emailHtml,
|
||
ContentType: 'text/html',
|
||
ACL: 'public-read'
|
||
});
|
||
|
||
try {
|
||
await s3Client.send(command);
|
||
return `https://${ENV.S3_BUCKET_NAME_EMAILS}.s3.amazonaws.com/${key}.html`;
|
||
} catch (error) {
|
||
console.error('Erreur lors de l\'upload de l\'email sur S3:', error);
|
||
throw new Error('Échec de l\'upload de l\'email HTML sur S3.');
|
||
}
|
||
}
|
||
|
||
// Anciennes fonctions HTML/SES supprimées au profit du système universel
|