espace-paie-odentas/lib/odentas-sign/supabase.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

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);
}