espace-paie-odentas/app/api/odentas-sign/test/create-mock/route.ts
odentas b790faf12c feat: Implémentation complète du système Odentas Sign
- Remplacement de DocuSeal par solution souveraine Odentas Sign
- Système d'authentification OTP pour signataires (bcryptjs + JWT)
- 8 routes API: send-otp, verify-otp, sign, pdf-url, positions, status, webhook, signers
- Interface moderne avec canvas de signature et animations (framer-motion, confetti)
- Système de templates pour auto-détection des positions de signature (CDDU, RG, avenants)
- PDF viewer avec @react-pdf-viewer (compatible Next.js)
- Stockage S3: source/, signatures/, evidence/, signed/, certs/
- Tables Supabase: sign_requests, signers, sign_positions, sign_events, sign_assets
- Evidence bundle automatique (JSON metadata + timestamps)
- Templates emails: OTP et completion
- Scripts Lambda prêts: pades-sign (KMS seal) et tsaStamp (RFC3161)
- Mode test détecté automatiquement (emails whitelist)
- Tests complets avec PDF CDDU réel (2 signataires)
2025-10-27 19:03:07 +01:00

251 lines
6 KiB
TypeScript

import { NextRequest, NextResponse } from 'next/server';
import { supabaseAdmin, logSignEvent } from '@/lib/odentas-sign/supabase';
import { generateRequestRef } from '@/lib/odentas-sign/crypto';
import { uploadToS3, S3_PREFIXES } from '@/lib/odentas-sign/s3';
import type { CreateSignRequestInput } from '@/lib/odentas-sign/types';
/**
* POST /api/odentas-sign/test/create-mock
*
* Crée une demande de signature de test sans PDF réel
* Utilisé pour tester le workflow sans avoir de vrai contrat
*/
export async function POST(request: NextRequest) {
try {
const body = await request.json();
const { title, signerName, signerEmail } = body;
// Générer un PDF de test simple
const testPdfContent = generateMockPDF(title || 'Document de test');
// Upload du PDF de test vers S3
const testRef = `TEST-${Date.now()}`;
const testPdfKey = `${S3_PREFIXES.SOURCE}test/${testRef}.pdf`;
await uploadToS3({
key: testPdfKey,
body: Buffer.from(testPdfContent),
contentType: 'application/pdf',
metadata: {
test: 'true',
created_by: 'test-api',
},
});
console.log(`[TEST] PDF de test créé: ${testPdfKey}`);
// Créer la demande de signature
const ref = generateRequestRef(`TEST-${testRef}`);
const { data: signRequest, error: requestError } = await supabaseAdmin
.from('sign_requests')
.insert({
ref,
title: title || 'Document de test - Odentas Sign',
source_s3_key: testPdfKey,
status: 'pending',
})
.select()
.single();
if (requestError || !signRequest) {
console.error('[TEST] Erreur création sign_request:', requestError);
return NextResponse.json(
{ error: 'Erreur lors de la création de la demande de test', details: requestError },
{ status: 500 }
);
}
// Créer 2 signataires par défaut (Employeur + Salarié)
const signersData = [
{
request_id: signRequest.id,
role: 'Employeur',
name: 'Jean Dupont (Test)',
email: 'employeur-test@example.com',
otp_attempts: 0,
},
{
request_id: signRequest.id,
role: 'Salarié',
name: signerName || 'Marie Martin (Test)',
email: signerEmail || 'salarie-test@example.com',
otp_attempts: 0,
},
];
const { data: createdSigners, error: signersError } = await supabaseAdmin
.from('signers')
.insert(signersData)
.select();
if (signersError || !createdSigners) {
console.error('[TEST] Erreur création signers:', signersError);
await supabaseAdmin.from('sign_requests').delete().eq('id', signRequest.id);
return NextResponse.json(
{ error: 'Erreur lors de la création des signataires de test', details: signersError },
{ status: 500 }
);
}
// Créer des positions de signature standard
const positionsData = [
{
request_id: signRequest.id,
role: 'Employeur',
page: 1,
x: 100,
y: 650,
w: 200,
h: 60,
kind: 'signature',
label: 'Signature Employeur',
},
{
request_id: signRequest.id,
role: 'Salarié',
page: 1,
x: 350,
y: 650,
w: 200,
h: 60,
kind: 'signature',
label: 'Signature Salarié',
},
];
await supabaseAdmin.from('sign_positions').insert(positionsData);
// Logger l'événement
await logSignEvent({
requestId: signRequest.id,
event: 'test_request_created',
metadata: {
test: true,
pdf_key: testPdfKey,
},
});
// Générer les URLs
const baseUrl = process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000';
const signerUrls = createdSigners.map(signer => ({
signerId: signer.id,
role: signer.role,
name: signer.name,
email: signer.email,
signatureUrl: `${baseUrl}/signer/${signRequest.id}/${signer.id}`,
}));
console.log(`[TEST] ✅ Demande de test créée: ${ref}`);
return NextResponse.json({
success: true,
message: 'Demande de signature de test créée',
test_mode: true,
request: {
id: signRequest.id,
ref: signRequest.ref,
title: signRequest.title,
status: signRequest.status,
created_at: signRequest.created_at,
},
signers: signerUrls,
instructions: {
step1: 'Utilisez les URLs ci-dessus pour tester la signature',
step2: 'Le code OTP sera affiché dans les logs serveur (console)',
step3: 'Après signature, le webhook ne déclenchera PAS Odentas Seal',
note: 'Ceci est un environnement de test - aucun document légal ne sera créé',
},
}, { status: 201 });
} catch (error) {
console.error('[TEST] Erreur:', error);
return NextResponse.json(
{ error: 'Erreur serveur', details: error instanceof Error ? error.message : String(error) },
{ status: 500 }
);
}
}
/**
* Génère un PDF simple de test
*/
function generateMockPDF(title: string): string {
// PDF minimal valide (version 1.4)
const pdfContent = `%PDF-1.4
1 0 obj
<<
/Type /Catalog
/Pages 2 0 R
>>
endobj
2 0 obj
<<
/Type /Pages
/Kids [3 0 R]
/Count 1
>>
endobj
3 0 obj
<<
/Type /Page
/Parent 2 0 R
/MediaBox [0 0 612 792]
/Contents 4 0 R
/Resources <<
/Font <<
/F1 <<
/Type /Font
/Subtype /Type1
/BaseFont /Helvetica
>>
>>
>>
>>
endobj
4 0 obj
<<
/Length 200
>>
stream
BT
/F1 18 Tf
50 700 Td
(${title}) Tj
0 -30 Td
/F1 12 Tf
(Document de test - Odentas Sign) Tj
0 -20 Td
(Ce document est genere automatiquement pour tester) Tj
0 -20 Td
(le systeme de signature electronique.) Tj
0 -100 Td
(Signature Employeur: ___________________) Tj
0 -30 Td
(Signature Salarie: ___________________) Tj
ET
endstream
endobj
xref
0 5
0000000000 65535 f
0000000009 00000 n
0000000058 00000 n
0000000115 00000 n
0000000317 00000 n
trailer
<<
/Size 5
/Root 1 0 R
>>
startxref
566
%%EOF
`;
return pdfContent;
}