- 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)
87 lines
2.2 KiB
TypeScript
87 lines
2.2 KiB
TypeScript
import { createClient } from '@supabase/supabase-js';
|
|
|
|
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!;
|
|
const supabaseServiceKey = process.env.SUPABASE_SERVICE_ROLE_KEY!;
|
|
|
|
if (!supabaseUrl || !supabaseServiceKey) {
|
|
throw new Error('Variables d\'environnement Supabase manquantes');
|
|
}
|
|
|
|
/**
|
|
* Client Supabase avec service role pour contourner les RLS
|
|
*/
|
|
export const supabaseAdmin = createClient(supabaseUrl, supabaseServiceKey, {
|
|
auth: {
|
|
autoRefreshToken: false,
|
|
persistSession: false,
|
|
},
|
|
});
|
|
|
|
/**
|
|
* Logger un événement dans sign_events
|
|
*/
|
|
export async function logSignEvent(params: {
|
|
requestId: string;
|
|
signerId?: string;
|
|
event: string;
|
|
ip?: string;
|
|
userAgent?: string;
|
|
metadata?: Record<string, any>;
|
|
}): Promise<void> {
|
|
const { requestId, signerId, event, ip, userAgent, metadata } = params;
|
|
|
|
const { error } = await supabaseAdmin
|
|
.from('sign_events')
|
|
.insert({
|
|
request_id: requestId,
|
|
signer_id: signerId || null,
|
|
event,
|
|
ip: ip || null,
|
|
user_agent: userAgent || null,
|
|
metadata: metadata || null,
|
|
});
|
|
|
|
if (error) {
|
|
console.error('[SUPABASE] Erreur lors du logging:', error);
|
|
throw error;
|
|
}
|
|
|
|
console.log(`[EVENT] ✅ ${event} (request: ${requestId}, signer: ${signerId || 'N/A'})`);
|
|
}
|
|
|
|
/**
|
|
* Récupère tous les événements d'une demande
|
|
*/
|
|
export async function getSignEvents(requestId: string) {
|
|
const { data, error } = await supabaseAdmin
|
|
.from('sign_events')
|
|
.select('*')
|
|
.eq('request_id', requestId)
|
|
.order('ts', { ascending: true });
|
|
|
|
if (error) {
|
|
console.error('[SUPABASE] Erreur récupération événements:', error);
|
|
throw error;
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
/**
|
|
* Vérifie si tous les signataires ont signé
|
|
*/
|
|
export async function checkAllSignersSigned(requestId: string): Promise<boolean> {
|
|
const { data: signers, error } = await supabaseAdmin
|
|
.from('signers')
|
|
.select('id, signed_at')
|
|
.eq('request_id', requestId);
|
|
|
|
if (error) {
|
|
console.error('[SUPABASE] Erreur vérification signataires:', error);
|
|
throw error;
|
|
}
|
|
|
|
if (!signers || signers.length === 0) return false;
|
|
|
|
return signers.every(s => s.signed_at !== null);
|
|
}
|