espace-paie-odentas/app/api/generate-contract-pdf/route.ts
odentas c6faceb038 feat: Préparation migration PDFMonkey vers Gotenberg
- Ajout helpers Handlebars pour remplacer filtres Liquid
- Conversion template CDDU de Liquid vers Handlebars
- Nouvelle API route /api/generate-contract-pdf pour Gotenberg
- Configuration Docker Compose pour auto-héberger Gotenberg
- Documentation complète de migration
- Variables d'environnement exemple

Note: Le bouton 'Créer PDF' utilise encore PDFMonkey.
Pour activer Gotenberg, modifier l'appel dans ContractEditor.tsx
2025-12-27 21:40:27 +01:00

163 lines
4.8 KiB
TypeScript

import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs';
import { cookies } from 'next/headers';
import { NextRequest, NextResponse } from 'next/server';
import Handlebars from 'handlebars';
import fs from 'fs/promises';
import path from 'path';
import { registerHandlebarsHelpers } from '@/lib/handlebars-helpers';
registerHandlebarsHelpers();
/**
* API Route pour générer un contrat PDF via Gotenberg
* POST /api/generate-contract-pdf
*
* Body attendu :
* {
* contractId: string,
* contractType: "cddu" | "rg" | "avenant",
* data: object (données du contrat)
* }
*/
export async function POST(request: NextRequest) {
try {
const supabase = createRouteHandlerClient({ cookies });
const {
data: { session },
} = await supabase.auth.getSession();
if (!session) {
return NextResponse.json(
{ error: 'Non authentifié' },
{ status: 401 }
);
}
const body = await request.json();
const { contractId, contractType, data } = body;
if (!contractId || !contractType || !data) {
return NextResponse.json(
{ error: 'Données manquantes (contractId, contractType, data)' },
{ status: 400 }
);
}
// Vérifier que l'utilisateur a accès à ce contrat
const { data: contract, error: contractError } = await supabase
.from('contracts')
.select('*')
.eq('id', contractId)
.single();
if (contractError || !contract) {
return NextResponse.json(
{ error: 'Contrat introuvable' },
{ status: 404 }
);
}
// Charger le template Handlebars selon le type de contrat
let templatePath: string;
switch (contractType) {
case 'cddu':
templatePath = path.join(process.cwd(), 'templates-contrats', 'cddu-handlebars.html');
break;
case 'rg':
templatePath = path.join(process.cwd(), 'templates-contrats', 'rg-handlebars.html');
break;
case 'avenant':
templatePath = path.join(process.cwd(), 'templates-contrats', 'avenant-handlebars.html');
break;
default:
return NextResponse.json(
{ error: 'Type de contrat invalide' },
{ status: 400 }
);
}
const templateContent = await fs.readFile(templatePath, 'utf-8');
const template = Handlebars.compile(templateContent);
// Générer le HTML avec les données
const html = template(data);
// Envoyer le HTML à Gotenberg pour conversion en PDF
const gotenbergUrl = process.env.GOTENBERG_URL || 'http://localhost:3001';
const formData = new FormData();
formData.append('files', new Blob([html], { type: 'text/html' }), 'index.html');
// Options Gotenberg
formData.append('marginTop', '1');
formData.append('marginBottom', '1');
formData.append('marginLeft', '1');
formData.append('marginRight', '1');
formData.append('paperWidth', '21');
formData.append('paperHeight', '29.7');
formData.append('preferCssPageSize', 'false');
const gotenbergResponse = await fetch(`${gotenbergUrl}/forms/chromium/convert/html`, {
method: 'POST',
body: formData,
});
if (!gotenbergResponse.ok) {
const errorText = await gotenbergResponse.text();
console.error('Erreur Gotenberg:', errorText);
return NextResponse.json(
{ error: 'Erreur lors de la génération du PDF', details: errorText },
{ status: 500 }
);
}
// Récupérer le PDF généré
const pdfBuffer = await gotenbergResponse.arrayBuffer();
const pdfBlob = new Blob([pdfBuffer], { type: 'application/pdf' });
// Uploader le PDF sur Supabase Storage
const fileName = `contract_${contractId}_${Date.now()}.pdf`;
const { data: uploadData, error: uploadError } = await supabase.storage
.from('contracts')
.upload(fileName, pdfBlob, {
contentType: 'application/pdf',
upsert: false,
});
if (uploadError) {
console.error('Erreur upload Supabase:', uploadError);
return NextResponse.json(
{ error: 'Erreur lors de l\'upload du PDF' },
{ status: 500 }
);
}
// Mettre à jour le contrat avec l'URL du PDF
const { data: publicUrl } = supabase.storage
.from('contracts')
.getPublicUrl(fileName);
const { error: updateError } = await supabase
.from('contracts')
.update({ pdf_url: publicUrl.publicUrl })
.eq('id', contractId);
if (updateError) {
console.error('Erreur mise à jour contrat:', updateError);
}
return NextResponse.json({
success: true,
pdfUrl: publicUrl.publicUrl,
fileName,
});
} catch (error) {
console.error('Erreur génération PDF:', error);
return NextResponse.json(
{ error: 'Erreur interne du serveur' },
{ status: 500 }
);
}
}