- 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)
107 lines
3 KiB
TypeScript
107 lines
3 KiB
TypeScript
import { NextRequest, NextResponse } from 'next/server';
|
|
import { supabaseAdmin } from '@/lib/odentas-sign/supabase';
|
|
import { verifySignatureSession, extractTokenFromHeader } from '@/lib/odentas-sign/jwt';
|
|
|
|
/**
|
|
* GET /api/odentas-sign/signers/[id]/status
|
|
*
|
|
* Récupère le statut d'un signataire (nécessite authentification)
|
|
*/
|
|
export async function GET(
|
|
request: NextRequest,
|
|
{ params }: { params: { id: string } }
|
|
) {
|
|
try {
|
|
const signerId = params.id;
|
|
|
|
// Vérifier le JWT de session (optionnel pour cette route, mais recommandé)
|
|
const authHeader = request.headers.get('authorization');
|
|
const token = extractTokenFromHeader(authHeader);
|
|
|
|
if (token) {
|
|
const session = verifySignatureSession(token);
|
|
if (session && session.signerId !== signerId) {
|
|
return NextResponse.json(
|
|
{ error: 'Accès non autorisé' },
|
|
{ status: 403 }
|
|
);
|
|
}
|
|
}
|
|
|
|
// Récupérer le signataire avec les infos de la demande
|
|
const { data: signer, error: signerError } = await supabaseAdmin
|
|
.from('signers')
|
|
.select(`
|
|
id,
|
|
role,
|
|
name,
|
|
email,
|
|
signed_at,
|
|
signature_image_s3,
|
|
request_id,
|
|
sign_requests(
|
|
id,
|
|
ref,
|
|
title,
|
|
status,
|
|
created_at
|
|
)
|
|
`)
|
|
.eq('id', signerId)
|
|
.single();
|
|
|
|
if (signerError || !signer) {
|
|
return NextResponse.json(
|
|
{ error: 'Signataire introuvable' },
|
|
{ status: 404 }
|
|
);
|
|
}
|
|
|
|
const signRequest = Array.isArray(signer.sign_requests) ? signer.sign_requests[0] : signer.sign_requests;
|
|
|
|
// Récupérer tous les signataires de cette demande pour calculer la progression
|
|
const { data: allSigners } = await supabaseAdmin
|
|
.from('signers')
|
|
.select('id, signed_at, role, name')
|
|
.eq('request_id', signer.request_id);
|
|
|
|
const totalSigners = allSigners?.length || 0;
|
|
const signedCount = allSigners?.filter(s => s.signed_at !== null).length || 0;
|
|
|
|
return NextResponse.json({
|
|
success: true,
|
|
signer: {
|
|
id: signer.id,
|
|
role: signer.role,
|
|
name: signer.name,
|
|
email: signer.email,
|
|
has_signed: signer.signed_at !== null,
|
|
signed_at: signer.signed_at,
|
|
},
|
|
request: {
|
|
id: signRequest.id,
|
|
ref: signRequest.ref,
|
|
title: signRequest.title,
|
|
status: signRequest.status,
|
|
created_at: signRequest.created_at,
|
|
progress: {
|
|
total: totalSigners,
|
|
signed: signedCount,
|
|
percentage: totalSigners > 0 ? Math.round((signedCount / totalSigners) * 100) : 0,
|
|
},
|
|
},
|
|
other_signers: allSigners?.map(s => ({
|
|
role: s.role,
|
|
name: s.name,
|
|
has_signed: s.signed_at !== null,
|
|
})) || [],
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('[SIGNER STATUS] Erreur:', error);
|
|
return NextResponse.json(
|
|
{ error: 'Erreur serveur', details: error instanceof Error ? error.message : String(error) },
|
|
{ status: 500 }
|
|
);
|
|
}
|
|
}
|