diff --git a/.env.example b/.env.example index f3efed2..47d959a 100644 --- a/.env.example +++ b/.env.example @@ -38,3 +38,6 @@ DEBUG_UPSTREAM=0 # Used by AWS Lambda to authenticate API calls to Espace Paie # Generate with: openssl rand -hex 32 LAMBDA_API_KEY=your-lambda-api-key-64-chars-hex + +# Lambda Functions URLs +LAMBDA_PDF_TO_IMAGES_URL=https://your-lambda-url.lambda-url.eu-west-3.on.aws/ diff --git a/.env.lambda.example b/.env.lambda.example new file mode 100644 index 0000000..af7f7e0 --- /dev/null +++ b/.env.lambda.example @@ -0,0 +1,13 @@ +# Configuration Lambda PDF to Images +# ================================= + +# URL de la Lambda AWS pour la conversion PDF vers images +# Format: https://xxx.execute-api.REGION.amazonaws.com/STAGE/convert +# Exemple: https://abc123def.execute-api.us-east-1.amazonaws.com/prod/convert +LAMBDA_PDF_TO_IMAGES_URL= + +# Instructions de déploiement: +# 1. Déployez la Lambda depuis le dossier lambda-pdf-to-images/ +# 2. Créez une API Gateway REST API pointant vers la Lambda +# 3. Copiez l'URL de l'API Gateway ici +# 4. Redémarrez l'application Next.js diff --git a/LAMBDA_DEPLOYMENT.md b/LAMBDA_DEPLOYMENT.md new file mode 100644 index 0000000..5d337b1 --- /dev/null +++ b/LAMBDA_DEPLOYMENT.md @@ -0,0 +1,196 @@ +# Déploiement Lambda PDF to Images - Odentas Sign + +## ✅ Déploiement Réussi + +La Lambda de conversion PDF vers images a été déployée avec succès sur AWS. + +### Informations de Déploiement + +- **Nom de la fonction** : `odentas-pdf-to-images` +- **Région AWS** : `eu-west-3` (Paris) +- **Runtime** : Node.js 20.x +- **Mémoire** : 2048 MB +- **Timeout** : 300 secondes (5 minutes) +- **Rôle IAM** : `odentas-seal-lambda-role` + +### URL de la Lambda + +``` +https://o4nfddsoi44rrhcrl3zlfeiury0uyasw.lambda-url.eu-west-3.on.aws/ +``` + +Cette URL a été ajoutée automatiquement dans `.env.local` : +```env +LAMBDA_PDF_TO_IMAGES_URL=https://o4nfddsoi44rrhcrl3zlfeiury0uyasw.lambda-url.eu-west-3.on.aws/ +``` + +### Configuration CORS + +- **AllowOrigins** : `*` (tous les domaines) +- **AllowMethods** : `POST` +- **AllowHeaders** : `*` +- **Auth** : NONE (pas d'authentification requise) + +Cela permet de tester en local et en production sans restrictions. + +## 🚧 Prochaines Étapes + +### 1. Ajouter le Layer Poppler + +⚠️ **Important** : La Lambda nécessite un Layer contenant `poppler-utils` et `ImageMagick`. + +Pour créer et déployer le layer : + +```bash +cd lambda-pdf-to-images +./create-layer.sh # Nécessite Docker + +# Puis déployer le layer +aws lambda publish-layer-version \ + --layer-name poppler-imagemagick \ + --description "Poppler Utils et ImageMagick" \ + --zip-file fileb://poppler-layer.zip \ + --compatible-runtimes nodejs20.x \ + --compatible-architectures x86_64 \ + --region eu-west-3 +``` + +Ou utiliser un layer public existant : +```bash +aws lambda update-function-configuration \ + --function-name odentas-pdf-to-images \ + --layers arn:aws:lambda:eu-west-3:ACCOUNT:layer:poppler-imagemagick:1 \ + --region eu-west-3 +``` + +### 2. Tester la Lambda + +Test avec curl : + +```bash +curl -X POST https://o4nfddsoi44rrhcrl3zlfeiury0uyasw.lambda-url.eu-west-3.on.aws/ \ + -H "Content-Type: application/json" \ + -d '{ + "pdfUrl": "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf", + "scale": 1.5 + }' +``` + +### 3. Redémarrer l'application Next.js + +```bash +# L'URL Lambda a été ajoutée dans .env.local +# Redémarrer le serveur de développement +npm run dev +``` + +### 4. Déployer sur Vercel + +Ajouter la variable d'environnement sur Vercel : + +1. Aller dans Settings > Environment Variables +2. Ajouter : + - **Nom** : `LAMBDA_PDF_TO_IMAGES_URL` + - **Valeur** : `https://o4nfddsoi44rrhcrl3zlfeiury0uyasw.lambda-url.eu-west-3.on.aws/` + - **Environment** : Production, Preview, Development + +3. Redéployer l'application + +## 🔄 Mise à Jour de la Lambda + +Pour mettre à jour le code de la Lambda : + +```bash +cd lambda-pdf-to-images +npm install +zip -r lambda-pdf-to-images.zip index.js package.json node_modules/ + +aws lambda update-function-code \ + --function-name odentas-pdf-to-images \ + --zip-file fileb://lambda-pdf-to-images.zip \ + --region eu-west-3 +``` + +## 📊 Monitoring + +Voir les logs CloudWatch : + +```bash +aws logs tail /aws/lambda/odentas-pdf-to-images --follow --region eu-west-3 +``` + +Voir les métriques : + +```bash +aws cloudwatch get-metric-statistics \ + --namespace AWS/Lambda \ + --metric-name Invocations \ + --dimensions Name=FunctionName,Value=odentas-pdf-to-images \ + --start-time 2025-10-27T00:00:00Z \ + --end-time 2025-10-27T23:59:59Z \ + --period 3600 \ + --statistics Sum \ + --region eu-west-3 +``` + +## 🔒 Sécurité + +### Recommandations de Production + +1. **Ajouter une authentification** : Utiliser une API Key ou JWT +2. **Limiter les origines CORS** : Remplacer `*` par les domaines autorisés +3. **Activer AWS WAF** : Protection contre les attaques DDoS +4. **Configurer des alertes CloudWatch** : Pour les erreurs et la latence +5. **Limiter la taille des PDFs** : Actuellement 50MB max + +Pour activer l'authentification : + +```bash +aws lambda update-function-url-config \ + --function-name odentas-pdf-to-images \ + --auth-type AWS_IAM \ + --region eu-west-3 +``` + +## 💰 Coûts Estimés + +Avec les paramètres actuels (2048MB, 300s timeout) : + +- **Prix par invocation** : ~0.0000166667 USD par seconde +- **Exemple** : Si conversion PDF prend 10 secondes → ~0.17 USD par invocation +- **1000 conversions/mois** → ~170 USD/mois + +Pour réduire les coûts : +- Réduire la mémoire si possible (test avec 1024MB) +- Mettre en cache les résultats dans S3 +- Utiliser CloudFront pour le CDN + +## 🆘 Dépannage + +### La Lambda retourne une erreur 500 + +Vérifier les logs : +```bash +aws logs tail /aws/lambda/odentas-pdf-to-images --follow --region eu-west-3 +``` + +### Erreur "pdftoppm: command not found" + +Le layer poppler n'est pas installé. Voir section "Ajouter le Layer Poppler". + +### Timeout après 300 secondes + +Le PDF est trop volumineux. Augmenter le timeout ou la mémoire : +```bash +aws lambda update-function-configuration \ + --function-name odentas-pdf-to-images \ + --timeout 600 \ + --memory-size 3008 \ + --region eu-west-3 +``` + +## 📝 Support + +- Code source : `/lambda-pdf-to-images/` +- Documentation : `/lambda-pdf-to-images/README.md` +- Équipe : Odentas Tech diff --git a/app/api/odentas-sign/requests/[id]/pdf-to-images/route.ts b/app/api/odentas-sign/requests/[id]/pdf-to-images/route.ts new file mode 100644 index 0000000..cca54f3 --- /dev/null +++ b/app/api/odentas-sign/requests/[id]/pdf-to-images/route.ts @@ -0,0 +1,180 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { GetObjectCommand, HeadObjectCommand, S3Client } from '@aws-sdk/client-s3'; +import { getSignedUrl } from '@aws-sdk/s3-request-presigner'; +import { supabaseAdmin } from '@/lib/odentas-sign/supabase'; +import { verifySignatureSession } from '@/lib/odentas-sign/jwt'; + +// Client S3 +const s3Client = new S3Client({ + region: process.env.AWS_REGION || 'eu-west-3', + credentials: { + accessKeyId: process.env.AWS_ACCESS_KEY_ID!, + secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!, + }, +}); + +// Bucket où sont stockées les images converties +const IMAGES_BUCKET = process.env.AWS_S3_BUCKET || 'odentas-docs'; + +interface PageImage { + pageNumber: number; + imageUrl: string; + width: number; + height: number; +} + +/** + * Récupère les images JPEG pré-converties depuis S3 + * (converties automatiquement par la Lambda lors de l'upload du PDF) + */ +async function getPreconvertedImagesFromS3(requestId: string): Promise { + const pageImages: PageImage[] = []; + let pageNum = 1; + + while (true) { + const s3Key = `odentas-sign-images/${requestId}/page-${pageNum}.jpg`; + + try { + // Vérifier si la page existe + await s3Client.send( + new HeadObjectCommand({ + Bucket: IMAGES_BUCKET, + Key: s3Key, + }) + ); + + // Générer l'URL presignée (valide 24h) + const command = new GetObjectCommand({ + Bucket: IMAGES_BUCKET, + Key: s3Key, + }); + const s3Url = await getSignedUrl(s3Client, command, { expiresIn: 86400 }); + + pageImages.push({ + pageNumber: pageNum, + imageUrl: s3Url, + width: 1400, + height: Math.round(1400 * 1.414), // Ratio A4 + }); + + pageNum++; + } catch (error) { + // Plus de pages, on sort de la boucle + break; + } + } + + return pageImages; +} + +export async function POST( + request: NextRequest, + { params }: { params: { id: string } } +) { + try { + console.log('[PDF to Images API] Nouvelle requête', { requestId: params.id }); + + // Verify authorization header + const authHeader = request.headers.get('authorization'); + console.log('[PDF to Images API] Auth header:', { + present: !!authHeader, + startsWithBearer: authHeader?.startsWith('Bearer '), + length: authHeader?.length, + }); + + if (!authHeader?.startsWith('Bearer ')) { + console.error('[PDF to Images API] Header Authorization manquant ou invalide'); + return NextResponse.json({ error: 'Non autorisé' }, { status: 401 }); + } + + const sessionToken = authHeader.substring(7); + console.log('[PDF to Images API] Token extrait:', { + length: sessionToken.length, + preview: sessionToken.substring(0, 20) + '...', + }); + + // Vérifier et décoder le JWT + const session = verifySignatureSession(sessionToken); + console.log('[PDF to Images API] Session vérifiée:', { + valid: !!session, + signerId: session?.signerId, + requestId: session?.requestId, + }); + + if (!session) { + console.error('[PDF to Images API] Session JWT invalide'); + return NextResponse.json({ error: 'Session invalide' }, { status: 401 }); + } + + // Verify request ID matches + if (session.requestId !== params.id) { + console.error('[PDF to Images API] RequestId mismatch:', { + sessionRequestId: session.requestId, + paramsId: params.id, + }); + return NextResponse.json({ error: 'Accès refusé' }, { status: 403 }); + } + + console.log('[PDF to Images API] Requête Supabase pour requestId:', params.id); + + // Get signature request (on n'a plus besoin du PDF, juste vérifier que la demande existe) + const { data: requestData, error: requestError } = await supabaseAdmin + .from('sign_requests') + .select('id') + .eq('id', params.id) + .single(); + + console.log('[PDF to Images API] Résultat Supabase:', { + hasData: !!requestData, + hasError: !!requestError, + error: requestError, + }); + + if (requestError || !requestData) { + console.error('[PDF to Images API] Demande non trouvée dans Supabase:', { + requestId: params.id, + error: requestError, + }); + return NextResponse.json({ error: 'Demande non trouvée' }, { status: 404 }); + } + + console.log('[PDF to Images API] Récupération des images pré-converties depuis S3...'); + + // Récupérer les images JPEG pré-converties depuis S3 + // (converties automatiquement par la Lambda lors de l'upload du PDF) + const pages = await getPreconvertedImagesFromS3(params.id); + + if (pages.length === 0) { + console.error('[PDF to Images API] Aucune image trouvée dans S3'); + return NextResponse.json( + { + error: 'Images non disponibles', + details: 'Les images du PDF n\'ont pas encore été converties. Veuillez réessayer dans quelques instants.', + }, + { status: 404 } + ); + } + + console.log(`[PDF to Images API] ✅ ${pages.length} page(s) récupérées depuis S3`); + + return NextResponse.json({ + success: true, + pages: pages.map((page: PageImage) => ({ + pageNumber: page.pageNumber, + imageUrl: page.imageUrl, + width: page.width, + height: page.height, + })), + totalPages: pages.length, + }); + } catch (error) { + console.error('[PDF to Images API] Erreur:', error); + return NextResponse.json( + { + error: 'Erreur lors de la conversion PDF', + details: error instanceof Error ? error.message : 'Erreur inconnue', + }, + { status: 500 } + ); + } +} diff --git a/app/api/odentas-sign/requests/[id]/positions/route.ts b/app/api/odentas-sign/requests/[id]/positions/route.ts index 4c4d04c..533729c 100644 --- a/app/api/odentas-sign/requests/[id]/positions/route.ts +++ b/app/api/odentas-sign/requests/[id]/positions/route.ts @@ -1,6 +1,12 @@ import { NextRequest, NextResponse } from 'next/server'; import { verifySignatureSession } from '@/lib/odentas-sign/jwt'; import { supabaseAdmin } from '@/lib/odentas-sign/supabase'; +import { getPresignedDownloadUrl } from '@/lib/odentas-sign/s3'; +import { + extractPlaceholdersFromPdfBuffer, + countPdfPagesFromBytes, + estimatePositionsFromPlaceholders, +} from '@/lib/odentas-sign/placeholders'; /** * GET /api/odentas-sign/requests/:id/positions @@ -32,7 +38,7 @@ export async function GET( ); } - // Récupérer toutes les positions de signature + // Récupérer toutes les positions de signature existantes const { data: positions, error } = await supabaseAdmin .from('sign_positions') .select('page, x, y, w, h, role') @@ -47,19 +53,82 @@ export async function GET( ); } - // Transformer pour correspondre à l'interface frontend - const transformedPositions = positions.map(p => ({ + // Si positions déjà présentes, renvoyer directement + if (positions && positions.length > 0) { + const transformedPositions = positions.map((p) => ({ + page: p.page, + x: p.x, + y: p.y, + width: p.w, + height: p.h, + role: p.role, + })); + return NextResponse.json({ positions: transformedPositions }); + } + + // Pas de positions en DB: tenter d'extraire depuis le PDF via placeholders + // 1) Récupérer la clé S3 du PDF source + const { data: signRequest, error: requestErr } = await supabaseAdmin + .from('sign_requests') + .select('source_s3_key') + .eq('id', requestId) + .single(); + + if (requestErr || !signRequest?.source_s3_key) { + console.error('Impossible de récupérer sign_request pour extraction:', requestErr); + return NextResponse.json({ positions: [] }); + } + + // 2) Générer une URL présignée et télécharger le PDF + const pdfUrl = await getPresignedDownloadUrl(signRequest.source_s3_key, 300); + const resp = await fetch(pdfUrl); + if (!resp.ok) { + console.error('Téléchargement du PDF échoué:', resp.status, pdfUrl); + return NextResponse.json({ positions: [] }); + } + const arrayBuf = await resp.arrayBuffer(); + const bytes = Buffer.from(arrayBuf); + + // 3) Fallback regex + estimation (pas d'extraction pdfjs côté serveur) + const placeholders = extractPlaceholdersFromPdfBuffer(bytes); + if (!placeholders || placeholders.length === 0) { + return NextResponse.json({ positions: [] }); + } + const pageCount = countPdfPagesFromBytes(bytes); + const precise = estimatePositionsFromPlaceholders(placeholders, pageCount); + + // 5) Persister en DB pour cette demande (meilleure UX aux prochains chargements) + try { + const rows = precise.map((pos) => ({ + request_id: requestId, + role: pos.role, + page: pos.page, + x: pos.x, // mm + y: pos.y, // mm + w: pos.width, // mm + h: pos.height, // mm + kind: 'signature', + label: pos.label, + })); + const { error: insertErr } = await supabaseAdmin.from('sign_positions').insert(rows); + if (insertErr) { + console.warn('Insertion positions estimées échouée (non bloquant):', insertErr); + } + } catch (e) { + console.warn('Erreur lors de la persistance des positions estimées:', e); + } + + // 6) Retourner au format attendu par le front + const transformedPositions = precise.map((p) => ({ page: p.page, x: p.x, y: p.y, - width: p.w, - height: p.h, + width: p.width, + height: p.height, role: p.role, })); - return NextResponse.json({ - positions: transformedPositions, - }); + return NextResponse.json({ positions: transformedPositions }); } catch (error) { console.error('Erreur lors de la récupération des positions:', error); @@ -69,3 +138,73 @@ export async function GET( ); } } + +/** + * POST /api/odentas-sign/requests/:id/positions + * Upsert des positions détectées côté client (mm) + */ +export async function POST( + request: NextRequest, + { params }: { params: { id: string } } +) { + try { + const requestId = params.id; + + // Vérifier le token JWT + const authHeader = request.headers.get('Authorization'); + if (!authHeader || !authHeader.startsWith('Bearer ')) { + return NextResponse.json( + { error: 'Token manquant ou invalide' }, + { status: 401 } + ); + } + const token = authHeader.split(' ')[1]; + const payload = verifySignatureSession(token); + if (!payload || payload.requestId !== requestId) { + return NextResponse.json( + { error: 'Token invalide ou expiré' }, + { status: 401 } + ); + } + + const body = await request.json(); + const positions = Array.isArray(body?.positions) ? body.positions : []; + if (positions.length === 0) { + return NextResponse.json({ success: false, message: 'Aucune position fournie' }, { status: 400 }); + } + + // Nettoyer les positions existantes de type signature pour cette requête + await supabaseAdmin + .from('sign_positions') + .delete() + .eq('request_id', requestId) + .eq('kind', 'signature'); + + // Insérer les nouvelles positions (mm) + const rows = positions.map((p: any) => ({ + request_id: requestId, + role: p.role, + page: p.page, + x: p.x, + y: p.y, + w: p.w, + h: p.h, + kind: p.kind || 'signature', + label: p.label || null, + })); + + const { error: insertErr } = await supabaseAdmin + .from('sign_positions') + .insert(rows); + + if (insertErr) { + console.error('Erreur insertion positions (POST):', insertErr); + return NextResponse.json({ error: 'Insertion des positions échouée' }, { status: 500 }); + } + + return NextResponse.json({ success: true, inserted: rows.length }); + } catch (error) { + console.error('Erreur POST positions:', error); + return NextResponse.json({ error: 'Erreur serveur' }, { status: 500 }); + } +} diff --git a/app/api/pdf-clean/route.ts b/app/api/pdf-clean/route.ts new file mode 100644 index 0000000..3ad0838 --- /dev/null +++ b/app/api/pdf-clean/route.ts @@ -0,0 +1,65 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { PDFDocument } from 'pdf-lib'; + +export async function GET(request: NextRequest) { + try { + const { searchParams } = new URL(request.url); + const pdfUrl = searchParams.get('url'); + const requestId = searchParams.get('requestId'); + + if (!pdfUrl) { + return NextResponse.json( + { error: 'URL du PDF requise' }, + { status: 400 } + ); + } + + // Récupérer le PDF depuis S3 + const response = await fetch(decodeURIComponent(pdfUrl)); + if (!response.ok) { + return NextResponse.json( + { error: `Erreur S3: ${response.status}` }, + { status: response.status } + ); + } + + const pdfBytes = await response.arrayBuffer(); + + try { + // Charger le PDF avec pdf-lib + const pdfDoc = await PDFDocument.load(pdfBytes); + const pages = pdfDoc.getPages(); + + // Parcourir chaque page et extraire les annotations + // Note: pdf-lib ne peut pas modifier directement le texte rendu + // On va donc simplement retourner le PDF tel quel + // car les placeholders seront masqués par les overlays de signature + + const modifiedPdfBytes = await pdfDoc.save(); + + return new NextResponse(Buffer.from(modifiedPdfBytes), { + headers: { + 'Content-Type': 'application/pdf', + 'Cache-Control': 'public, max-age=3600', + 'Access-Control-Allow-Origin': '*', + }, + }); + } catch (error) { + console.error('Erreur parsing PDF avec pdf-lib:', error); + // En cas d'erreur, retourner le PDF original + return new NextResponse(Buffer.from(pdfBytes), { + headers: { + 'Content-Type': 'application/pdf', + 'Cache-Control': 'public, max-age=3600', + 'Access-Control-Allow-Origin': '*', + }, + }); + } + } catch (error) { + console.error('Erreur nettoyage PDF:', error); + return NextResponse.json( + { error: 'Erreur serveur' }, + { status: 500 } + ); + } +} diff --git a/app/api/pdf-proxy/route.ts b/app/api/pdf-proxy/route.ts new file mode 100644 index 0000000..46dcd2c --- /dev/null +++ b/app/api/pdf-proxy/route.ts @@ -0,0 +1,45 @@ +import { NextRequest, NextResponse } from 'next/server'; + +export async function GET(request: NextRequest) { + try { + const { searchParams } = new URL(request.url); + const pdfUrl = searchParams.get('url'); + + if (!pdfUrl) { + return NextResponse.json( + { error: 'URL du PDF requise' }, + { status: 400 } + ); + } + + // Décoder l'URL si elle est encodée + const decodedUrl = decodeURIComponent(pdfUrl); + + // Fetcher le PDF depuis S3 + const response = await fetch(decodedUrl); + + if (!response.ok) { + return NextResponse.json( + { error: `Erreur S3: ${response.status}` }, + { status: response.status } + ); + } + + const buffer = await response.arrayBuffer(); + + // Retourner le PDF avec les headers CORS appropriés + return new NextResponse(buffer, { + headers: { + 'Content-Type': 'application/pdf', + 'Cache-Control': 'public, max-age=3600', + 'Access-Control-Allow-Origin': '*', + }, + }); + } catch (error) { + console.error('Erreur proxy PDF:', error); + return NextResponse.json( + { error: 'Erreur serveur' }, + { status: 500 } + ); + } +} diff --git a/app/signer/[requestId]/[signerId]/components/PDFImageViewer.tsx b/app/signer/[requestId]/[signerId]/components/PDFImageViewer.tsx new file mode 100644 index 0000000..e973f89 --- /dev/null +++ b/app/signer/[requestId]/[signerId]/components/PDFImageViewer.tsx @@ -0,0 +1,171 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { Loader2, AlertCircle } from 'lucide-react'; + +interface SignPosition { + page: number; + x: number; + y: number; + width: number; + height: number; + role: string; +} + +interface PDFImageViewerProps { + pdfUrl: string; + positions: SignPosition[]; + currentSignerRole: string; + requestId: string; + sessionToken: string; +} + +interface PageImage { + pageNumber: number; + imageUrl: string; + width: number; + height: number; +} + +export default function PDFImageViewer({ + pdfUrl, + positions, + currentSignerRole, + requestId, + sessionToken, +}: PDFImageViewerProps) { + const [pageImages, setPageImages] = useState([]); + const [isLoading, setIsLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + async function convertPdfToImages() { + try { + setIsLoading(true); + setError(null); + + console.log('[PDFImageViewer] Début conversion', { + requestId, + pdfUrl, + hasSessionToken: !!sessionToken, + sessionTokenLength: sessionToken?.length, + sessionTokenPreview: sessionToken?.substring(0, 20) + '...', + }); + + // Appel API pour convertir le PDF en images + const response = await fetch(`/api/odentas-sign/requests/${requestId}/pdf-to-images`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${sessionToken}`, + }, + body: JSON.stringify({ + pdfUrl, + }), + }); + + if (!response.ok) { + const data = await response.json(); + console.error('[PDFImageViewer] Erreur response:', { + status: response.status, + statusText: response.statusText, + data, + }); + throw new Error(data.error || 'Erreur lors de la conversion du PDF'); + } + + const data = await response.json(); + setPageImages(data.pages || []); + } catch (err) { + console.error('[PDFImageViewer] Erreur:', err); + setError(err instanceof Error ? err.message : 'Erreur de chargement'); + } finally { + setIsLoading(false); + } + } + + if (pdfUrl) { + convertPdfToImages(); + } + }, [pdfUrl, requestId, sessionToken]); + + if (isLoading) { + return ( +
+ +

Conversion du PDF en cours...

+
+ ); + } + + if (error) { + return ( +
+ +

Erreur de chargement

+

{error}

+
+ ); + } + + if (pageImages.length === 0) { + return ( +
+

Aucune page à afficher

+
+ ); + } + + return ( +
+ {pageImages.map((page) => { + // Filtrer les positions de signature pour cette page et ce rôle + const pagePositions = positions.filter( + (pos) => pos.page === page.pageNumber && pos.role === currentSignerRole + ); + + return ( +
+ {/* Image de la page PDF */} + {`Page + + {/* Zones de signature superposées */} + {pagePositions.map((pos, idx) => ( +
+
+ + Signez ici + +
+
+ ))} + + {/* Numéro de page */} +
+ Page {page.pageNumber} +
+
+ ); + })} +
+ ); +} diff --git a/app/signer/[requestId]/[signerId]/components/PDFViewer.tsx b/app/signer/[requestId]/[signerId]/components/PDFViewer.tsx deleted file mode 100644 index c999db6..0000000 --- a/app/signer/[requestId]/[signerId]/components/PDFViewer.tsx +++ /dev/null @@ -1,84 +0,0 @@ -'use client'; - -import { useState, useEffect } from 'react'; -import { Viewer, Worker } from '@react-pdf-viewer/core'; -import { defaultLayoutPlugin } from '@react-pdf-viewer/default-layout'; - -// Import des styles -import '@react-pdf-viewer/core/lib/styles/index.css'; -import '@react-pdf-viewer/default-layout/lib/styles/index.css'; - -interface SignaturePosition { - page: number; - x: number; - y: number; - width: number; - height: number; - role: string; -} - -interface PDFViewerProps { - pdfUrl: string; - positions: SignaturePosition[]; - currentSignerRole: string; -} - -export default function PDFViewer({ pdfUrl, positions, currentSignerRole }: PDFViewerProps) { - const [mounted, setMounted] = useState(false); - - // Plugin pour la mise en page par défaut - const defaultLayoutPluginInstance = defaultLayoutPlugin(); - - useEffect(() => { - setMounted(true); - }, []); - - if (!mounted) { - return ( -
-
-
-

Initialisation du viewer...

-
-
- ); - } - - return ( -
- -
- - - {/* Overlay custom pour les zones de signature */} -
-

Zones de signature

-
- {positions.map((pos, index) => { - const isCurrentSigner = pos.role === currentSignerRole; - return ( -
- {isCurrentSigner ? '✍️' : '📝'} - Page {pos.page}: {pos.role} -
- ); - })} -
- {positions.length === 0 && ( -

Aucune zone définie

- )} -
-
-
-
- ); -} \ No newline at end of file diff --git a/app/signer/[requestId]/[signerId]/components/SignatureCapture.tsx b/app/signer/[requestId]/[signerId]/components/SignatureCapture.tsx index 718ae4e..1ef8fad 100644 --- a/app/signer/[requestId]/[signerId]/components/SignatureCapture.tsx +++ b/app/signer/[requestId]/[signerId]/components/SignatureCapture.tsx @@ -3,6 +3,18 @@ import { useState, useRef, useEffect } from 'react'; import { motion } from 'framer-motion'; import { PenTool, RotateCcw, Check, Loader2, AlertCircle, FileText, Info } from 'lucide-react'; +import dynamic from 'next/dynamic'; + +// Charger le PDFImageViewer côté client uniquement (conversion PDF vers images comme Docuseal) +const PDFImageViewer = dynamic(() => import('./PDFImageViewer'), { + ssr: false, + loading: () => ( +
+ +

Chargement du visualiseur...

+
+ ), +}); interface SignatureCaptureProps { signerId: string; @@ -44,20 +56,6 @@ export default function SignatureCapture({ const [pdfUrl, setPdfUrl] = useState(null); const [signaturePositions, setSignaturePositions] = useState([]); const [isPdfLoading, setIsPdfLoading] = useState(true); - const [PDFViewerComponent, setPDFViewerComponent] = useState(null); - - // Load PDF Viewer component (client-side only) - useEffect(() => { - async function loadPDFViewer() { - try { - const { default: PDFViewer } = await import('./PDFViewer'); - setPDFViewerComponent(() => PDFViewer); - } catch (err) { - console.error('[PDF] Erreur chargement viewer:', err); - } - } - loadPDFViewer(); - }, []); // Load PDF and signature positions useEffect(() => { @@ -242,170 +240,188 @@ export default function SignatureCapture({ initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: -20 }} - className="max-w-3xl mx-auto" + className="w-full min-h-screen bg-gradient-to-br from-slate-50 to-slate-100 py-8 px-4" > -
+
{/* Header */} -
-
-
-

Signature du document

-

{documentTitle}

-
-
-

Signataire

-

{signerName}

-

{signerRole}

+
+
+
+
+

Signature du document

+

{documentTitle}

+
+
+

Signataire

+

{signerName}

+

{signerRole}

+
- {/* Content */} -
- {/* PDF Viewer */} - {isPdfLoading ? ( -
- -

Chargement du document...

-
- ) : pdfUrl && PDFViewerComponent ? ( -
-

- - Aperçu du document -

-
- + {/* Two-column layout */} +
+ {/* Left: PDF Viewer */} +
+
+
+

+ + Document à signer +

+
+ +
+ {isPdfLoading ? ( +
+ +

Chargement du document...

+
+ ) : pdfUrl ? ( +
+ +
+ ) : ( +
+ +

Aucun document à afficher

+
+ )}
-
- ) : null} - - {/* Info notice */} -
- -
-

Dessinez votre signature

-

- Utilisez votre souris, trackpad ou doigt pour signer dans le cadre ci-dessous. Vous pouvez recommencer à tout moment. -

- {/* Signature canvas */} -
-
- - - {!hasDrawn && ( -
-
- -

Signez ici

+ {/* Right: Signature panel */} +
+
+
+

+ + Votre signature +

+
+ +
+ {/* Info notice */} +
+ +
+

Dessinez votre signature

+

+ Utilisez votre souris, trackpad ou doigt pour signer dans le cadre ci-dessous. +

- )} + + {/* Signature canvas */} +
+
+ + + {!hasDrawn && ( +
+
+ +

Signez ici

+
+
+ )} +
+ + {/* Clear button */} + {hasDrawn && ( + + + Recommencer + + )} +
+ + {/* Consent checkbox */} +
+ +
+ + {/* Error message */} + {error && ( + + +

{error}

+
+ )} + + {/* Submit button */} + + + {/* Help text */} +

+ En validant, vous acceptez que votre signature soit juridiquement contraignante. +

+
- - {/* Clear button */} - {hasDrawn && ( - - - Recommencer - - )}
- - {/* Consent checkbox */} -
- -
- - {/* Error message */} - {error && ( - - -

{error}

-
- )} - - {/* Submit button */} - - - {/* Help text */} -

- En validant, vous acceptez que votre signature soit juridiquement contraignante. -

-
-
- - {/* Document preview (placeholder for future PDF viewer) */} -
-
- -

Aperçu du document

-
-
-

Le visualiseur de PDF sera intégré prochainement

-

Référence: {requestId.slice(0, 8)}...

diff --git a/extract-placeholders.js b/extract-placeholders.js new file mode 100644 index 0000000..9941411 --- /dev/null +++ b/extract-placeholders.js @@ -0,0 +1,127 @@ +#!/usr/bin/env node + +/** + * Script pour extraire les positions exactes des placeholders {{Signature...}} + * depuis un PDF + */ + +const fs = require('fs'); +const path = require('path'); + +/** + * Regex pour matcher les placeholders de signature + * Format: {{Label;role=Role;type=type;height=H;width=W}} + */ +const PLACEHOLDER_REGEX = /\{\{([^;]+);role=([^;]+);type=([^;]+);height=(\d+);width=(\d+)\}\}/g; + +async function extractPlaceholders(pdfPath) { + console.log(`\n📄 Analyse du PDF: ${pdfPath}\n`); + + try { + // Lire le PDF comme texte brut + const pdfBuffer = fs.readFileSync(pdfPath); + const pdfText = pdfBuffer.toString('latin1'); + + console.log(`✅ PDF chargé\n`); + + const placeholders = []; + let pageNum = 1; + + // Chercher les placeholders dans le texte + let match; + PLACEHOLDER_REGEX.lastIndex = 0; // Reset la regex + + while ((match = PLACEHOLDER_REGEX.exec(pdfText)) !== null) { + placeholders.push({ + page: pageNum, // On va essayer de déterminer la page + label: match[1].trim(), + role: match[2].trim(), + type: match[3].trim(), + height: parseInt(match[4]), + width: parseInt(match[5]), + fullMatch: match[0], + }); + } + + // Essayer de mieux déterminer les pages en cherchant les marqueurs de page + const pageBreakRegex = /\x0c/g; // Form feed character + let pageBreakMatch; + let lastBreakIndex = 0; + let pageBreaks = [0]; + + while ((pageBreakMatch = pageBreakRegex.exec(pdfText)) !== null) { + pageBreaks.push(pageBreakMatch.index); + } + + // Associer les placeholders aux pages correctes + placeholders.forEach(ph => { + const placeholderPos = pdfText.indexOf(ph.fullMatch); + if (placeholderPos !== -1) { + // Trouver quelle page contient ce placeholder + for (let i = pageBreaks.length - 1; i >= 0; i--) { + if (placeholderPos >= pageBreaks[i]) { + ph.page = i + 1; + break; + } + } + } + }); + + if (placeholders.length === 0) { + console.log('⚠️ Aucun placeholder trouvé!\n'); + return; + } + + // Afficher les résultats + console.log(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`); + console.log('📍 PLACEHOLDERS DÉTECTÉS'); + console.log(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`); + + placeholders.forEach((ph, idx) => { + console.log(`${idx + 1}. ${ph.label}`); + console.log(` Rôle: ${ph.role}`); + console.log(` Page: ${ph.page}`); + console.log(` Dimensions: ${ph.width} × ${ph.height} mm`); + console.log(` Type: ${ph.type}\n`); + }); + + // Générer le code pour test-odentas-sign.js + console.log(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`); + console.log('📋 CODE POUR test-odentas-sign.js'); + console.log(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`); + + console.log('positions: ['); + placeholders.forEach(ph => { + console.log(` {`); + console.log(` role: '${ph.role}',`); + console.log(` page: ${ph.page},`); + console.log(` x: 100, // À ajuster selon la position exacte`); + console.log(` y: 680, // À ajuster selon la position exacte`); + console.log(` w: ${ph.width},`); + console.log(` h: ${ph.height},`); + console.log(` kind: 'signature',`); + console.log(` label: '${ph.label}',`); + console.log(` },`); + }); + console.log(']'); + + // Sauvegarder en JSON + const outputPath = pdfPath.replace('.pdf', '-placeholders.json'); + fs.writeFileSync(outputPath, JSON.stringify(placeholders, null, 2)); + console.log(`\n💾 Résultats sauvegardés: ${outputPath}\n`); + + } catch (error) { + console.error('❌ Erreur:', error.message); + process.exit(1); + } +} + +// Point d'entrée +const pdfPath = process.argv[2] || path.join(__dirname, 'test-contrat.pdf'); + +if (!fs.existsSync(pdfPath)) { + console.error(`❌ Fichier non trouvé: ${pdfPath}`); + process.exit(1); +} + +extractPlaceholders(pdfPath).catch(console.error); diff --git a/find-placeholder-positions.js b/find-placeholder-positions.js new file mode 100644 index 0000000..baa6274 --- /dev/null +++ b/find-placeholder-positions.js @@ -0,0 +1,213 @@ +#!/usr/bin/env node + +/** + * Script pour extraire les positions EXACTES des placeholders depuis un PDF + * Utilise pdfjs-dist pour accéder au texte rendu et ses coordonnées + */ + +const fs = require('fs'); +const path = require('path'); +const https = require('https'); +const http = require('http'); + +// Placeholder patterns à chercher +const PATTERNS = [ + /Signature Employeur/i, + /Signature Employé/i, + /Signature Salarié/i, + /\{\{Signature[^}]*\}\}/g, +]; + +/** + * Télécharge un fichier depuis une URL + */ +function downloadFile(url, filepath) { + return new Promise((resolve, reject) => { + const protocol = url.startsWith('https') ? https : http; + const file = fs.createWriteStream(filepath); + + protocol.get(url, (response) => { + response.pipe(file); + file.on('finish', () => { + file.close(); + resolve(); + }); + }).on('error', (err) => { + fs.unlink(filepath, () => {}); + reject(err); + }); + }); +} + +/** + * Extrait les positions des placeholders en utilisant une approche basée sur le texte brut + */ +async function extractPositions(pdfPath) { + console.log('\n📄 Extraction des positions des placeholders\n'); + console.log(`Fichier: ${pdfPath}\n`); + + try { + // Lire le fichier PDF + const pdfBuffer = fs.readFileSync(pdfPath); + const pdfText = pdfBuffer.toString('binary'); + + // Chercher les pages (marqueurs de page dans le PDF) + const pages = []; + let currentPage = 1; + let pageStart = 0; + + // Chercher les marqueurs "endobj" qui marquent la fin des objets de page + const pageMarkerRegex = /\/Type\s*\/Page(?!s)/g; + let match; + while ((match = pageMarkerRegex.exec(pdfText)) !== null) { + pages.push({ + num: currentPage, + startOffset: pageStart, + endOffset: match.index, + }); + pageStart = match.index; + currentPage++; + } + pages.push({ + num: currentPage, + startOffset: pageStart, + endOffset: pdfText.length, + }); + + console.log(`Total pages détectées: ${pages.length}\n`); + + // Chercher les placeholders avec les patterns + const results = []; + + for (const pattern of PATTERNS) { + pattern.lastIndex = 0; + let matchResult; + + while ((matchResult = pattern.exec(pdfText)) !== null) { + const textFound = matchResult[0]; + const position = matchResult.index; + + // Déterminer la page + let pageNum = 1; + for (const page of pages) { + if (position >= page.startOffset && position <= page.endOffset) { + pageNum = page.num; + break; + } + } + + // Essayer d'extraire les coordonnées si c'est un placeholder formaté + let role = 'Inconnu'; + let width = 150; + let height = 60; + + if (textFound.includes('Employeur')) { + role = 'Employeur'; + } else if (textFound.includes('Employé') || textFound.includes('Salarié')) { + role = 'Salarié'; + } + + // Extraire les dimensions si présentes dans le placeholder + const dimensionsMatch = textFound.match(/height=(\d+);width=(\d+)/i); + if (dimensionsMatch) { + height = parseInt(dimensionsMatch[1]); + width = parseInt(dimensionsMatch[2]); + } + + results.push({ + text: textFound, + page: pageNum, + offsetInPDF: position, + role, + width, + height, + }); + } + } + + if (results.length === 0) { + console.log('❌ Aucun placeholder trouvé dans le PDF!\n'); + console.log('Cherchez-vous les patterns corrects?\n'); + console.log('Patterns en cours de recherche:'); + PATTERNS.forEach(p => console.log(` - ${p}`)); + console.log('\n'); + return; + } + + // Afficher les résultats + console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'); + console.log('📍 PLACEHOLDERS TROUVÉS'); + console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'); + + results.forEach((result, idx) => { + console.log(`${idx + 1}. ${result.text}`); + console.log(` Rôle: ${result.role}`); + console.log(` Page: ${result.page}`); + console.log(` Dimensions: ${result.width} × ${result.height} mm`); + console.log(` Position dans PDF: offset ${result.offsetInPDF}\n`); + }); + + // Générer les positions pour test-odentas-sign.js + console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'); + console.log('📋 POSITIONS POUR test-odentas-sign.js'); + console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'); + + console.log('positions: ['); + + // Grouper par rôle + const byRole = {}; + results.forEach(r => { + if (!byRole[r.role]) byRole[r.role] = []; + byRole[r.role].push(r); + }); + + let xPos = 20; + Object.entries(byRole).forEach(([role, items]) => { + items.forEach(item => { + console.log(` {`); + console.log(` role: '${item.role}',`); + console.log(` page: ${item.page},`); + console.log(` x: ${xPos},`); + console.log(` y: 260,`); + console.log(` w: ${item.width},`); + console.log(` h: ${item.height},`); + console.log(` kind: 'signature',`); + console.log(` label: '${item.text}',`); + console.log(` },`); + xPos += item.width + 20; + }); + }); + + console.log(']'); + + // Sauvegarder en JSON + const outputPath = pdfPath.replace('.pdf', '-positions.json'); + const positionsData = results.map(r => ({ + role: r.role, + page: r.page, + x: 20, // À ajuster manuellement + y: 260, // À ajuster manuellement + w: r.width, + h: r.height, + kind: 'signature', + label: r.text, + })); + + fs.writeFileSync(outputPath, JSON.stringify(positionsData, null, 2)); + console.log(`\n💾 Sauvegardé: ${outputPath}\n`); + + } catch (error) { + console.error('❌ Erreur:', error.message); + process.exit(1); + } +} + +// Point d'entrée +const pdfPath = process.argv[2] || path.join(__dirname, 'test-contrat.pdf'); + +if (!fs.existsSync(pdfPath)) { + console.error(`\n❌ Fichier PDF non trouvé: ${pdfPath}\n`); + process.exit(1); +} + +extractPositions(pdfPath).catch(console.error); diff --git a/lambda-pdf-converter/README.md b/lambda-pdf-converter/README.md new file mode 100644 index 0000000..7598173 --- /dev/null +++ b/lambda-pdf-converter/README.md @@ -0,0 +1,116 @@ +# Lambda PDF Converter pour Odentas Sign + +## Description + +Cette Lambda est déclenchée automatiquement par S3 quand un PDF est uploadé dans le bucket `odentas-sign` (prefix `source/`). + +Elle convertit le PDF en images JPEG via Cloudinary et stocke les images dans `odentas-docs` pour être utilisées sur les pages de signature. + +## Déploiement + +### 1. Installation des dépendances + +```bash +cd lambda-pdf-converter +npm install +``` + +### 2. Créer le ZIP de déploiement + +```bash +zip -r lambda-pdf-converter.zip . -x "*.git*" -x "README.md" +``` + +### 3. Créer la Lambda dans AWS + +1. Allez dans AWS Lambda Console +2. Créer une nouvelle fonction : + - **Nom** : `odentas-sign-pdf-converter` + - **Runtime** : Node.js 20.x + - **Architecture** : x86_64 + - **Rôle d'exécution** : Créer un nouveau rôle avec les permissions de base + +3. Uploader le ZIP `lambda-pdf-converter.zip` + +### 4. Configuration de la Lambda + +#### Variables d'environnement + +``` +CLOUDINARY_CLOUD_NAME=duecox5va +CLOUDINARY_API_KEY=265234555873541 +CLOUDINARY_API_SECRET=DS5k0Zo2LxDkE5KmA3nFsT3bL1M +AWS_REGION=eu-west-3 +SOURCE_BUCKET=odentas-sign +DEST_BUCKET=odentas-docs +``` + +#### Configuration générale + +- **Mémoire** : 512 MB (ou plus selon la taille des PDF) +- **Timeout** : 5 minutes (300 secondes) +- **Retry** : 0 (pas de retry automatique) + +#### Permissions IAM + +Ajouter ces permissions au rôle de la Lambda : + +```json +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "s3:GetObject" + ], + "Resource": "arn:aws:s3:::odentas-sign/*" + }, + { + "Effect": "Allow", + "Action": [ + "s3:PutObject" + ], + "Resource": "arn:aws:s3:::odentas-docs/odentas-sign-images/*" + } + ] +} +``` + +### 5. Configurer le trigger S3 + +1. Dans la Lambda, ajouter un trigger "S3" +2. Configuration : + - **Bucket** : `odentas-sign` + - **Event type** : `PUT` (ObjectCreated:Put) + - **Prefix** : `source/` + - **Suffix** : `.pdf` + +### 6. Tester + +1. Uploadez un PDF de test dans `s3://odentas-sign/source/test/TEST-123456789.pdf` +2. Vérifiez les logs CloudWatch de la Lambda +3. Vérifiez que les images sont créées dans `s3://odentas-docs/odentas-sign-images/TEST/page-*.jpg` + +## Architecture + +``` +Upload PDF + ↓ +s3://odentas-sign/source/{folder}/{requestId}-{timestamp}.pdf + ↓ +Event S3 ObjectCreated + ↓ +Lambda odentas-sign-pdf-converter + ↓ +Cloudinary (conversion PDF → JPEG) + ↓ +s3://odentas-docs/odentas-sign-images/{requestId}/page-{num}.jpg +``` + +## Notes + +- La Lambda extrait le `requestId` depuis le nom du fichier (partie avant le premier tiret) +- Format attendu : `{requestId}-{timestamp}.pdf` +- Exemple : `4fc9bdf9-eacc-4eed-b713-da40a095c5e7-1234567890.pdf` → requestId = `4fc9bdf9` +- Les images sont stockées avec metadata pour traçabilité diff --git a/lambda-pdf-converter/create-poppler-layer.sh b/lambda-pdf-converter/create-poppler-layer.sh new file mode 100755 index 0000000..4c8ee10 --- /dev/null +++ b/lambda-pdf-converter/create-poppler-layer.sh @@ -0,0 +1,14 @@ +#!/bin/bash +# Script pour créer une Lambda Layer avec Poppler depuis un container Amazon Linux + +docker run --rm -v "$PWD":/var/task public.ecr.aws/lambda/nodejs:20 bash -c " + yum install -y poppler-utils + mkdir -p /tmp/layer/bin + cp /usr/bin/pdftoppm /tmp/layer/bin/ + cp /usr/bin/pdfinfo /tmp/layer/bin/ + # Copier les libs nécessaires + mkdir -p /tmp/layer/lib + ldd /usr/bin/pdftoppm | grep '=>' | awk '{print \$3}' | xargs -I {} cp {} /tmp/layer/lib/ 2>/dev/null || true + cd /tmp/layer + zip -r /var/task/poppler-layer.zip . +" diff --git a/lambda-pdf-converter/deploy.sh b/lambda-pdf-converter/deploy.sh new file mode 100755 index 0000000..6c2676e --- /dev/null +++ b/lambda-pdf-converter/deploy.sh @@ -0,0 +1,49 @@ +#!/bin/bash + +# Script de déploiement de la Lambda PDF Converter + +set -e + +echo "🚀 Déploiement de la Lambda odentas-sign-pdf-converter" +echo "" + +cd lambda-pdf-converter + +# 1. Installation des dépendances +echo "📦 Installation des dépendances..." +npm install --production + +# 2. Création du ZIP +echo "📦 Création du package ZIP..." +rm -f lambda-pdf-converter.zip +zip -r lambda-pdf-converter.zip . -x "*.git*" -x "README.md" -x "deploy.sh" + +echo "" +echo "✅ Package créé: lambda-pdf-converter.zip" +echo "" +echo "📋 Prochaines étapes:" +echo "" +echo "1. Créer la Lambda dans AWS Console:" +echo " - Nom: odentas-sign-pdf-converter" +echo " - Runtime: Node.js 20.x" +echo " - Mémoire: 512 MB" +echo " - Timeout: 5 minutes" +echo "" +echo "2. Uploader le fichier lambda-pdf-converter.zip" +echo "" +echo "3. Configurer les variables d'environnement:" +echo " CLOUDINARY_CLOUD_NAME=duecox5va" +echo " CLOUDINARY_API_KEY=265234555873541" +echo " CLOUDINARY_API_SECRET=DS5k0Zo2LxDkE5KmA3nFsT3bL1M" +echo " AWS_REGION=eu-west-3" +echo " SOURCE_BUCKET=odentas-sign" +echo " DEST_BUCKET=odentas-docs" +echo "" +echo "4. Ajouter le trigger S3:" +echo " - Bucket: odentas-sign" +echo " - Event: PUT" +echo " - Prefix: source/" +echo " - Suffix: .pdf" +echo "" +echo "5. Ajouter les permissions IAM au rôle de la Lambda" +echo "" diff --git a/lambda-pdf-converter/index.js b/lambda-pdf-converter/index.js new file mode 100644 index 0000000..3d820c5 --- /dev/null +++ b/lambda-pdf-converter/index.js @@ -0,0 +1,170 @@ +/** + * Lambda déclenchée par S3 ObjectCreated + * Convertit automatiquement les PDF en images JPEG avec pdftoppm + * et les stocke dans S3 pour les pages de signature + */ + +const { S3Client, GetObjectCommand, PutObjectCommand } = require('@aws-sdk/client-s3'); +const { spawn } = require('child_process'); +const fs = require('fs').promises; +const path = require('path'); +const { pipeline } = require('stream/promises'); + +// Client S3 +const s3Client = new S3Client({ region: process.env.AWS_REGION || 'eu-west-3' }); + +const SOURCE_BUCKET = process.env.SOURCE_BUCKET || 'odentas-sign'; +const DEST_BUCKET = process.env.DEST_BUCKET || 'odentas-docs'; + +/** + * Stream un objet S3 vers un fichier local + */ +async function streamToFile(readable, filePath) { + const fileHandle = await fs.open(filePath, 'w'); + const writable = fileHandle.createWriteStream(); + await pipeline(readable, writable); +} + +/** + * Convertit un PDF en images JPEG avec pdftoppm + */ +async function convertPdfToImages(pdfPath, requestId, outputDir) { + const outputPrefix = path.join(outputDir, 'page'); + + const args = [ + '-jpeg', + '-jpegopt', 'quality=90', + '-r', '150', // 150 DPI pour bonne qualité + pdfPath, + outputPrefix + ]; + + return new Promise((resolve, reject) => { + const proc = spawn('pdftoppm', args); + let stderr = ''; + + proc.stderr.on('data', (data) => { + stderr += data.toString(); + }); + + proc.on('close', (code) => { + if (code === 0) { + resolve(); + } else { + reject(new Error(`pdftoppm exit ${code}: ${stderr}`)); + } + }); + }); +} + +/** + * Handler principal + */ +exports.handler = async (event) => { + console.log('[Lambda] Event reçu:', JSON.stringify(event, null, 2)); + + try { + // Traiter chaque record S3 + for (const record of event.Records || []) { + const bucket = record.s3.bucket.name; + const key = decodeURIComponent(record.s3.object.key.replace(/\+/g, ' ')); + + console.log(`[Lambda] Fichier détecté: s3://${bucket}/${key}`); + + if (!key.toLowerCase().endsWith('.pdf')) { + console.log('[Lambda] Ignoré (pas un PDF)'); + continue; + } + + // Extraire le requestId depuis la clé S3 + // Format attendu: source/{folder}/{requestId}-{timestamp}.pdf + const keyParts = key.split('/'); + const filename = keyParts[keyParts.length - 1]; + const requestId = filename.split('-')[0]; + + if (!requestId) { + throw new Error(`Impossible d'extraire requestId depuis la clé: ${key}`); + } + + console.log(`[Lambda] Request ID extrait: ${requestId}`); + + const tmpDir = '/tmp'; + const pdfPath = path.join(tmpDir, `${requestId}.pdf`); + + // 1. Télécharger le PDF depuis S3 + console.log('[Lambda] Téléchargement du PDF depuis S3...'); + const getResponse = await s3Client.send( + new GetObjectCommand({ + Bucket: bucket, + Key: key, + }) + ); + + await streamToFile(getResponse.Body, pdfPath); + const stats = await fs.stat(pdfPath); + console.log(`[Lambda] PDF téléchargé: ${stats.size} bytes`); + + // 2. Convertir avec pdftoppm → génère /tmp/page-1.jpg, /tmp/page-2.jpg ... + console.log('[Lambda] Conversion PDF → JPEG avec pdftoppm...'); + await convertPdfToImages(pdfPath, requestId, tmpDir); + + // 3. Lister les images générées + const files = await fs.readdir(tmpDir); + const pageFiles = files + .filter(f => f.startsWith('page-') && f.endsWith('.jpg')) + .sort((a, b) => { + const numA = parseInt(a.match(/page-(\d+)/)[1], 10); + const numB = parseInt(b.match(/page-(\d+)/)[1], 10); + return numA - numB; + }); + + console.log(`[Lambda] ${pageFiles.length} page(s) générée(s):`, pageFiles); + + if (pageFiles.length === 0) { + throw new Error('Aucune page générée par pdftoppm'); + } + + // 4. Upload vers S3 + const uploadedPages = []; + for (let i = 0; i < pageFiles.length; i++) { + const pageFile = pageFiles[i]; + const pageNum = i + 1; + const imagePath = path.join(tmpDir, pageFile); + + console.log(`[Lambda] Upload page ${pageNum}: ${pageFile}`); + + const imageBuffer = await fs.readFile(imagePath); + const s3Key = `odentas-sign-images/${requestId}/page-${pageNum}.jpg`; + + await s3Client.send( + new PutObjectCommand({ + Bucket: DEST_BUCKET, + Key: s3Key, + Body: imageBuffer, + ContentType: 'image/jpeg', + CacheControl: 'public, max-age=31536000', // 1 an + }) + ); + + console.log(`[Lambda] Page ${pageNum} stockée sur S3 → ${s3Key}`); + uploadedPages.push(s3Key); + + // Supprimer le fichier temporaire + await fs.unlink(imagePath); + } + + // 5. Cleanup du PDF + await fs.unlink(pdfPath); + + console.log(`[Lambda] ✅ Conversion terminée: ${uploadedPages.length} page(s) pour ${requestId}`); + } + + return { + statusCode: 200, + body: JSON.stringify({ message: 'Conversion réussie' }), + }; + } catch (error) { + console.error('[Lambda] ❌ Erreur:', error); + throw error; + } +}; diff --git a/lambda-pdf-converter/index.js.cloudinary.bak b/lambda-pdf-converter/index.js.cloudinary.bak new file mode 100644 index 0000000..d7b91ee --- /dev/null +++ b/lambda-pdf-converter/index.js.cloudinary.bak @@ -0,0 +1,203 @@ +/** + * Lambda déclenchée par S3 ObjectCreated + * Convertit automatiquement les PDF en images JPEG avec pdfjs-dist + canvas + * et les stocke dans S3 pour les pages de signature + */ + +const { S3Client, GetObjectCommand, PutObjectCommand } = require('@aws-sdk/client-s3'); +const { getDocument } = require('pdfjs-dist/legacy/build/pdf.js'); +const { createCanvas } = require('canvas'); +const sharp = require('sharp'); + +// Client S3 +const s3Client = new S3Client({ region: process.env.AWS_REGION || 'eu-west-3' }); + +const SOURCE_BUCKET = process.env.SOURCE_BUCKET || 'odentas-sign'; +const DEST_BUCKET = process.env.DEST_BUCKET || 'odentas-docs'; + +/** + * Convertit une page PDF en image JPEG avec Sharp + */ +async function convertPdfPageToJpeg(pdfPage, scale = 2.0) { + const viewport = pdfPage.getViewport({ scale }); + const canvas = createCanvas(viewport.width, viewport.height); + const context = canvas.getContext('2d'); + + await pdfPage.render({ + canvasContext: context, + viewport: viewport, + }).promise; + + // Convertir le canvas en buffer puis optimiser avec Sharp + const rawImageBuffer = canvas.toBuffer('raw'); + + return await sharp(rawImageBuffer, { + raw: { + width: Math.floor(viewport.width), + height: Math.floor(viewport.height), + channels: 4, // RGBA + }, + }) + .jpeg({ + quality: 90, + progressive: true, + }) + .resize(1400, null, { // Max 1400px de largeur + withoutEnlargement: true, + fit: 'inside', + }) + .toBuffer(); +} + +/** + * Handler principal + */ +exports.handler = async (event) => { + console.log('[Lambda] Event reçu:', JSON.stringify(event, null, 2)); + + try { + // Récupérer les informations du fichier uploadé + const record = event.Records[0]; + const bucket = record.s3.bucket.name; + const key = decodeURIComponent(record.s3.object.key.replace(/\+/g, ' ')); + + console.log(`[Lambda] Fichier détecté: s3://${bucket}/${key}`); + + // Extraire le requestId depuis la clé S3 + // Format attendu: source/{folder}/{requestId}-{timestamp}.pdf + const keyParts = key.split('/'); + const filename = keyParts[keyParts.length - 1]; + const requestId = filename.split('-')[0]; // Prend la partie avant le premier tiret + + if (!requestId) { + throw new Error(`Impossible d'extraire requestId depuis la clé: ${key}`); + } + + console.log(`[Lambda] Request ID extrait: ${requestId}`); + + // 1. Télécharger le PDF depuis S3 + console.log('[Lambda] Téléchargement du PDF depuis S3...'); + const getObjectCommand = new GetObjectCommand({ + Bucket: bucket, + Key: key, + }); + const s3Response = await s3Client.send(getObjectCommand); + const pdfBuffer = Buffer.from(await s3Response.Body.transformToByteArray()); + console.log(`[Lambda] PDF téléchargé: ${pdfBuffer.length} bytes`); + + // 2. Upload sur Cloudinary pour conversion (upload en tant que PDF, pas en JPG) + console.log('[Lambda] Upload sur Cloudinary pour conversion...'); + const uploadResult = await new Promise((resolve, reject) => { + const uploadStream = cloudinary.uploader.upload_stream( + { + folder: `odentas-sign-temp/${requestId}`, + resource_type: 'raw', // 'raw' pour préserver le PDF tel quel + public_id: `pdf-${requestId}`, + transformation: [ + { width: 1400, crop: 'scale' }, + { quality: 90 }, + ], + }, + (error, result) => { + if (error) reject(error); + else resolve(result); + } + ); + + bufferToStream(pdfBuffer).pipe(uploadStream); + }); + + console.log('[Lambda] Upload Cloudinary réussi:', uploadResult.secure_url); + console.log('[Lambda] Pages détectées par Cloudinary:', uploadResult.pages); + + const baseUrl = uploadResult.secure_url; + + // Cloudinary ne retourne pas toujours le nombre de pages correctement + // On va tester chaque page jusqu'à ce qu'on obtienne une erreur 404 + const pages = []; + let pageNum = 1; + const maxPages = 100; // Sécurité pour éviter une boucle infinie + + console.log('[Lambda] Détection du nombre de pages...'); + + // 3. Pour chaque page, télécharger depuis Cloudinary et stocker sur S3 + while (pageNum <= maxPages) { + // Pour un PDF uploadé en 'raw', on doit forcer le format JPG avec .jpg + const public_id_with_folder = `${uploadResult.folder}/${uploadResult.public_id}`; + const cloudinaryPageUrl = `https://res.cloudinary.com/${process.env.CLOUDINARY_CLOUD_NAME}/image/upload/pg_${pageNum}/w_1400,q_90/${public_id_with_folder}.jpg`; + + console.log(`[Lambda] Test page ${pageNum}: Téléchargement depuis Cloudinary...`); + + // Télécharger l'image + const imageResponse = await fetch(cloudinaryPageUrl); + + // Si 4xx, on a atteint la fin du PDF (404 ou 400 selon Cloudinary) + if (imageResponse.status >= 400 && imageResponse.status < 500) { + console.log(`[Lambda] Page ${pageNum} non trouvée (${imageResponse.status}), fin du PDF`); + break; + } + + if (!imageResponse.ok) { + throw new Error(`Erreur téléchargement page ${pageNum}: ${imageResponse.statusText}`); + } + + const imageBuffer = Buffer.from(await imageResponse.arrayBuffer()); + + // Stocker sur S3 + const s3Key = `odentas-sign-images/${requestId}/page-${pageNum}.jpg`; + await s3Client.send( + new PutObjectCommand({ + Bucket: DEST_BUCKET, + Key: s3Key, + Body: imageBuffer, + ContentType: 'image/jpeg', + CacheControl: 'public, max-age=31536000', + Metadata: { + 'source-pdf': key, + 'request-id': requestId, + 'page-number': pageNum.toString(), + 'converted-at': new Date().toISOString(), + }, + }) + ); + + pages.push(s3Key); + console.log(`[Lambda] Page ${pageNum} stockée sur S3 → ${s3Key}`); + + pageNum++; + } + + const totalPages = pages.length; + + // 4. Nettoyer Cloudinary + console.log('[Lambda] Nettoyage Cloudinary...'); + try { + await cloudinary.uploader.destroy(`odentas-sign-temp/${requestId}/pdf-${requestId}`); + console.log('[Lambda] Nettoyage Cloudinary terminé'); + } catch (cleanupError) { + console.warn('[Lambda] Erreur nettoyage Cloudinary (non bloquant):', cleanupError); + } + + console.log(`[Lambda] ✅ Conversion terminée: ${totalPages} page(s) pour ${requestId}`); + + return { + statusCode: 200, + body: JSON.stringify({ + success: true, + requestId, + totalPages, + message: 'PDF converti avec succès', + }), + }; + } catch (error) { + console.error('[Lambda] ❌ Erreur:', error); + + return { + statusCode: 500, + body: JSON.stringify({ + success: false, + error: error.message, + }), + }; + } +}; diff --git a/lambda-pdf-converter/lambda-pdf-converter.zip b/lambda-pdf-converter/lambda-pdf-converter.zip new file mode 100644 index 0000000..1139475 Binary files /dev/null and b/lambda-pdf-converter/lambda-pdf-converter.zip differ diff --git a/lambda-pdf-converter/package-lock.json b/lambda-pdf-converter/package-lock.json new file mode 100644 index 0000000..73c6dd7 --- /dev/null +++ b/lambda-pdf-converter/package-lock.json @@ -0,0 +1,1654 @@ +{ + "name": "lambda-pdf-converter", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "lambda-pdf-converter", + "version": "1.0.0", + "dependencies": { + "@aws-sdk/client-s3": "^3.913.0" + } + }, + "node_modules/@aws-crypto/crc32": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", + "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-crypto/crc32c": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32c/-/crc32c-5.2.0.tgz", + "integrity": "sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha1-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha1-browser/-/sha1-browser-5.2.0.tgz", + "integrity": "sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", + "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-js": "^5.2.0", + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-js": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", + "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-crypto/supports-web-crypto": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", + "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.222.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-s3": { + "version": "3.918.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.918.0.tgz", + "integrity": "sha512-25DhKO0QB4QbhbX1t+txCoRNRvchcq9s3lrDrVJLDwpS7e3cTwSOsicyvMpme6Wk/NSln/lWkYazx8MgUbO6RA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha1-browser": "5.2.0", + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.916.0", + "@aws-sdk/credential-provider-node": "3.918.0", + "@aws-sdk/middleware-bucket-endpoint": "3.914.0", + "@aws-sdk/middleware-expect-continue": "3.917.0", + "@aws-sdk/middleware-flexible-checksums": "3.916.0", + "@aws-sdk/middleware-host-header": "3.914.0", + "@aws-sdk/middleware-location-constraint": "3.914.0", + "@aws-sdk/middleware-logger": "3.914.0", + "@aws-sdk/middleware-recursion-detection": "3.914.0", + "@aws-sdk/middleware-sdk-s3": "3.916.0", + "@aws-sdk/middleware-ssec": "3.914.0", + "@aws-sdk/middleware-user-agent": "3.916.0", + "@aws-sdk/region-config-resolver": "3.914.0", + "@aws-sdk/signature-v4-multi-region": "3.916.0", + "@aws-sdk/types": "3.914.0", + "@aws-sdk/util-endpoints": "3.916.0", + "@aws-sdk/util-user-agent-browser": "3.914.0", + "@aws-sdk/util-user-agent-node": "3.916.0", + "@aws-sdk/xml-builder": "3.914.0", + "@smithy/config-resolver": "^4.4.0", + "@smithy/core": "^3.17.1", + "@smithy/eventstream-serde-browser": "^4.2.3", + "@smithy/eventstream-serde-config-resolver": "^4.3.3", + "@smithy/eventstream-serde-node": "^4.2.3", + "@smithy/fetch-http-handler": "^5.3.4", + "@smithy/hash-blob-browser": "^4.2.4", + "@smithy/hash-node": "^4.2.3", + "@smithy/hash-stream-node": "^4.2.3", + "@smithy/invalid-dependency": "^4.2.3", + "@smithy/md5-js": "^4.2.3", + "@smithy/middleware-content-length": "^4.2.3", + "@smithy/middleware-endpoint": "^4.3.5", + "@smithy/middleware-retry": "^4.4.5", + "@smithy/middleware-serde": "^4.2.3", + "@smithy/middleware-stack": "^4.2.3", + "@smithy/node-config-provider": "^4.3.3", + "@smithy/node-http-handler": "^4.4.3", + "@smithy/protocol-http": "^5.3.3", + "@smithy/smithy-client": "^4.9.1", + "@smithy/types": "^4.8.0", + "@smithy/url-parser": "^4.2.3", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.4", + "@smithy/util-defaults-mode-node": "^4.2.6", + "@smithy/util-endpoints": "^3.2.3", + "@smithy/util-middleware": "^4.2.3", + "@smithy/util-retry": "^4.2.3", + "@smithy/util-stream": "^4.5.4", + "@smithy/util-utf8": "^4.2.0", + "@smithy/util-waiter": "^4.2.3", + "@smithy/uuid": "^1.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso": { + "version": "3.916.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.916.0.tgz", + "integrity": "sha512-Eu4PtEUL1MyRvboQnoq5YKg0Z9vAni3ccebykJy615xokVZUdA3di2YxHM/hykDQX7lcUC62q9fVIvh0+UNk/w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.916.0", + "@aws-sdk/middleware-host-header": "3.914.0", + "@aws-sdk/middleware-logger": "3.914.0", + "@aws-sdk/middleware-recursion-detection": "3.914.0", + "@aws-sdk/middleware-user-agent": "3.916.0", + "@aws-sdk/region-config-resolver": "3.914.0", + "@aws-sdk/types": "3.914.0", + "@aws-sdk/util-endpoints": "3.916.0", + "@aws-sdk/util-user-agent-browser": "3.914.0", + "@aws-sdk/util-user-agent-node": "3.916.0", + "@smithy/config-resolver": "^4.4.0", + "@smithy/core": "^3.17.1", + "@smithy/fetch-http-handler": "^5.3.4", + "@smithy/hash-node": "^4.2.3", + "@smithy/invalid-dependency": "^4.2.3", + "@smithy/middleware-content-length": "^4.2.3", + "@smithy/middleware-endpoint": "^4.3.5", + "@smithy/middleware-retry": "^4.4.5", + "@smithy/middleware-serde": "^4.2.3", + "@smithy/middleware-stack": "^4.2.3", + "@smithy/node-config-provider": "^4.3.3", + "@smithy/node-http-handler": "^4.4.3", + "@smithy/protocol-http": "^5.3.3", + "@smithy/smithy-client": "^4.9.1", + "@smithy/types": "^4.8.0", + "@smithy/url-parser": "^4.2.3", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.4", + "@smithy/util-defaults-mode-node": "^4.2.6", + "@smithy/util-endpoints": "^3.2.3", + "@smithy/util-middleware": "^4.2.3", + "@smithy/util-retry": "^4.2.3", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/core": { + "version": "3.916.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.916.0.tgz", + "integrity": "sha512-1JHE5s6MD5PKGovmx/F1e01hUbds/1y3X8rD+Gvi/gWVfdg5noO7ZCerpRsWgfzgvCMZC9VicopBqNHCKLykZA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.914.0", + "@aws-sdk/xml-builder": "3.914.0", + "@smithy/core": "^3.17.1", + "@smithy/node-config-provider": "^4.3.3", + "@smithy/property-provider": "^4.2.3", + "@smithy/protocol-http": "^5.3.3", + "@smithy/signature-v4": "^5.3.3", + "@smithy/smithy-client": "^4.9.1", + "@smithy/types": "^4.8.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-middleware": "^4.2.3", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.916.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.916.0.tgz", + "integrity": "sha512-3gDeqOXcBRXGHScc6xb7358Lyf64NRG2P08g6Bu5mv1Vbg9PKDyCAZvhKLkG7hkdfAM8Yc6UJNhbFxr1ud/tCQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.916.0", + "@aws-sdk/types": "3.914.0", + "@smithy/property-provider": "^4.2.3", + "@smithy/types": "^4.8.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.916.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.916.0.tgz", + "integrity": "sha512-NmooA5Z4/kPFJdsyoJgDxuqXC1C6oPMmreJjbOPqcwo6E/h2jxaG8utlQFgXe5F9FeJsMx668dtxVxSYnAAqHQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.916.0", + "@aws-sdk/types": "3.914.0", + "@smithy/fetch-http-handler": "^5.3.4", + "@smithy/node-http-handler": "^4.4.3", + "@smithy/property-provider": "^4.2.3", + "@smithy/protocol-http": "^5.3.3", + "@smithy/smithy-client": "^4.9.1", + "@smithy/types": "^4.8.0", + "@smithy/util-stream": "^4.5.4", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.918.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.918.0.tgz", + "integrity": "sha512-oDViX9z4o8jShY0unX9T7MJqyt+/ojhRB2zoLQVr0Mln7GbXwJ0aUtxgb4PFROG27pJpR11oAaZHzI3LI0jm/A==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.916.0", + "@aws-sdk/credential-provider-env": "3.916.0", + "@aws-sdk/credential-provider-http": "3.916.0", + "@aws-sdk/credential-provider-process": "3.916.0", + "@aws-sdk/credential-provider-sso": "3.916.0", + "@aws-sdk/credential-provider-web-identity": "3.918.0", + "@aws-sdk/nested-clients": "3.916.0", + "@aws-sdk/types": "3.914.0", + "@smithy/credential-provider-imds": "^4.2.3", + "@smithy/property-provider": "^4.2.3", + "@smithy/shared-ini-file-loader": "^4.3.3", + "@smithy/types": "^4.8.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.918.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.918.0.tgz", + "integrity": "sha512-gl9ECsPB1i8UBPrAJV0HcTn+sgYuD3jYy8ps6KK4c8LznFizwgpah1jd3eF4qq3kPGzrdAE3MKua9OlCCNWAKQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.916.0", + "@aws-sdk/credential-provider-http": "3.916.0", + "@aws-sdk/credential-provider-ini": "3.918.0", + "@aws-sdk/credential-provider-process": "3.916.0", + "@aws-sdk/credential-provider-sso": "3.916.0", + "@aws-sdk/credential-provider-web-identity": "3.918.0", + "@aws-sdk/types": "3.914.0", + "@smithy/credential-provider-imds": "^4.2.3", + "@smithy/property-provider": "^4.2.3", + "@smithy/shared-ini-file-loader": "^4.3.3", + "@smithy/types": "^4.8.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.916.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.916.0.tgz", + "integrity": "sha512-SXDyDvpJ1+WbotZDLJW1lqP6gYGaXfZJrgFSXIuZjHb75fKeNRgPkQX/wZDdUvCwdrscvxmtyJorp2sVYkMcvA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.916.0", + "@aws-sdk/types": "3.914.0", + "@smithy/property-provider": "^4.2.3", + "@smithy/shared-ini-file-loader": "^4.3.3", + "@smithy/types": "^4.8.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.916.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.916.0.tgz", + "integrity": "sha512-gu9D+c+U/Dp1AKBcVxYHNNoZF9uD4wjAKYCjgSN37j4tDsazwMEylbbZLuRNuxfbXtizbo4/TiaxBXDbWM7AkQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/client-sso": "3.916.0", + "@aws-sdk/core": "3.916.0", + "@aws-sdk/token-providers": "3.916.0", + "@aws-sdk/types": "3.914.0", + "@smithy/property-provider": "^4.2.3", + "@smithy/shared-ini-file-loader": "^4.3.3", + "@smithy/types": "^4.8.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.918.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.918.0.tgz", + "integrity": "sha512-qQx5qOhSovVF1EEKTc809WsiKzMqEJrlMSOUycDkE+JMgLPIy2pB2LR1crrIeBGgxFUgFsXHvNHbFjRy+AFBdA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.916.0", + "@aws-sdk/nested-clients": "3.916.0", + "@aws-sdk/types": "3.914.0", + "@smithy/property-provider": "^4.2.3", + "@smithy/shared-ini-file-loader": "^4.3.3", + "@smithy/types": "^4.8.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-bucket-endpoint": { + "version": "3.914.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.914.0.tgz", + "integrity": "sha512-mHLsVnPPp4iq3gL2oEBamfpeETFV0qzxRHmcnCfEP3hualV8YF8jbXGmwPCPopUPQDpbYDBHYtXaoClZikCWPQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.914.0", + "@aws-sdk/util-arn-parser": "3.893.0", + "@smithy/node-config-provider": "^4.3.3", + "@smithy/protocol-http": "^5.3.3", + "@smithy/types": "^4.8.0", + "@smithy/util-config-provider": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-expect-continue": { + "version": "3.917.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.917.0.tgz", + "integrity": "sha512-UPBq1ZP2CaxwbncWSbVqkhYXQrmfNiqAtHyBxi413hjRVZ4JhQ1UyH7pz5yqiG8zx2/+Po8cUD4SDUwJgda4nw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.914.0", + "@smithy/protocol-http": "^5.3.3", + "@smithy/types": "^4.8.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-flexible-checksums": { + "version": "3.916.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.916.0.tgz", + "integrity": "sha512-CBRRg6slHHBYAm26AWY/pECHK0vVO/peDoNhZiAzUNt4jV6VftotjszEJ904pKGOr7/86CfZxtCnP3CCs3lQjA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/crc32": "5.2.0", + "@aws-crypto/crc32c": "5.2.0", + "@aws-crypto/util": "5.2.0", + "@aws-sdk/core": "3.916.0", + "@aws-sdk/types": "3.914.0", + "@smithy/is-array-buffer": "^4.2.0", + "@smithy/node-config-provider": "^4.3.3", + "@smithy/protocol-http": "^5.3.3", + "@smithy/types": "^4.8.0", + "@smithy/util-middleware": "^4.2.3", + "@smithy/util-stream": "^4.5.4", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-host-header": { + "version": "3.914.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.914.0.tgz", + "integrity": "sha512-7r9ToySQ15+iIgXMF/h616PcQStByylVkCshmQqcdeynD/lCn2l667ynckxW4+ql0Q+Bo/URljuhJRxVJzydNA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.914.0", + "@smithy/protocol-http": "^5.3.3", + "@smithy/types": "^4.8.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-location-constraint": { + "version": "3.914.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.914.0.tgz", + "integrity": "sha512-Mpd0Sm9+GN7TBqGnZg1+dO5QZ/EOYEcDTo7KfvoyrXScMlxvYm9fdrUVMmLdPn/lntweZGV3uNrs+huasGOOTA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.914.0", + "@smithy/types": "^4.8.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-logger": { + "version": "3.914.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.914.0.tgz", + "integrity": "sha512-/gaW2VENS5vKvJbcE1umV4Ag3NuiVzpsANxtrqISxT3ovyro29o1RezW/Avz/6oJqjnmgz8soe9J1t65jJdiNg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.914.0", + "@smithy/types": "^4.8.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.914.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.914.0.tgz", + "integrity": "sha512-yiAjQKs5S2JKYc+GrkvGMwkUvhepXDigEXpSJqUseR/IrqHhvGNuOxDxq+8LbDhM4ajEW81wkiBbU+Jl9G82yQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.914.0", + "@aws/lambda-invoke-store": "^0.0.1", + "@smithy/protocol-http": "^5.3.3", + "@smithy/types": "^4.8.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-sdk-s3": { + "version": "3.916.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.916.0.tgz", + "integrity": "sha512-pjmzzjkEkpJObzmTthqJPq/P13KoNFuEi/x5PISlzJtHofCNcyXeVAQ90yvY2dQ6UXHf511Rh1/ytiKy2A8M0g==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.916.0", + "@aws-sdk/types": "3.914.0", + "@aws-sdk/util-arn-parser": "3.893.0", + "@smithy/core": "^3.17.1", + "@smithy/node-config-provider": "^4.3.3", + "@smithy/protocol-http": "^5.3.3", + "@smithy/signature-v4": "^5.3.3", + "@smithy/smithy-client": "^4.9.1", + "@smithy/types": "^4.8.0", + "@smithy/util-config-provider": "^4.2.0", + "@smithy/util-middleware": "^4.2.3", + "@smithy/util-stream": "^4.5.4", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-ssec": { + "version": "3.914.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.914.0.tgz", + "integrity": "sha512-V1Oae/oLVbpNb9uWs+v80GKylZCdsbqs2c2Xb1FsAUPtYeSnxFuAWsF3/2AEMSSpFe0dTC5KyWr/eKl2aim9VQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.914.0", + "@smithy/types": "^4.8.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.916.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.916.0.tgz", + "integrity": "sha512-mzF5AdrpQXc2SOmAoaQeHpDFsK2GE6EGcEACeNuoESluPI2uYMpuuNMYrUufdnIAIyqgKlis0NVxiahA5jG42w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.916.0", + "@aws-sdk/types": "3.914.0", + "@aws-sdk/util-endpoints": "3.916.0", + "@smithy/core": "^3.17.1", + "@smithy/protocol-http": "^5.3.3", + "@smithy/types": "^4.8.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients": { + "version": "3.916.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.916.0.tgz", + "integrity": "sha512-tgg8e8AnVAer0rcgeWucFJ/uNN67TbTiDHfD+zIOPKep0Z61mrHEoeT/X8WxGIOkEn4W6nMpmS4ii8P42rNtnA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.916.0", + "@aws-sdk/middleware-host-header": "3.914.0", + "@aws-sdk/middleware-logger": "3.914.0", + "@aws-sdk/middleware-recursion-detection": "3.914.0", + "@aws-sdk/middleware-user-agent": "3.916.0", + "@aws-sdk/region-config-resolver": "3.914.0", + "@aws-sdk/types": "3.914.0", + "@aws-sdk/util-endpoints": "3.916.0", + "@aws-sdk/util-user-agent-browser": "3.914.0", + "@aws-sdk/util-user-agent-node": "3.916.0", + "@smithy/config-resolver": "^4.4.0", + "@smithy/core": "^3.17.1", + "@smithy/fetch-http-handler": "^5.3.4", + "@smithy/hash-node": "^4.2.3", + "@smithy/invalid-dependency": "^4.2.3", + "@smithy/middleware-content-length": "^4.2.3", + "@smithy/middleware-endpoint": "^4.3.5", + "@smithy/middleware-retry": "^4.4.5", + "@smithy/middleware-serde": "^4.2.3", + "@smithy/middleware-stack": "^4.2.3", + "@smithy/node-config-provider": "^4.3.3", + "@smithy/node-http-handler": "^4.4.3", + "@smithy/protocol-http": "^5.3.3", + "@smithy/smithy-client": "^4.9.1", + "@smithy/types": "^4.8.0", + "@smithy/url-parser": "^4.2.3", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.4", + "@smithy/util-defaults-mode-node": "^4.2.6", + "@smithy/util-endpoints": "^3.2.3", + "@smithy/util-middleware": "^4.2.3", + "@smithy/util-retry": "^4.2.3", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/region-config-resolver": { + "version": "3.914.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.914.0.tgz", + "integrity": "sha512-KlmHhRbn1qdwXUdsdrJ7S/MAkkC1jLpQ11n+XvxUUUCGAJd1gjC7AjxPZUM7ieQ2zcb8bfEzIU7al+Q3ZT0u7Q==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.914.0", + "@smithy/config-resolver": "^4.4.0", + "@smithy/types": "^4.8.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/signature-v4-multi-region": { + "version": "3.916.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.916.0.tgz", + "integrity": "sha512-fuzUMo6xU7e0NBzBA6TQ4FUf1gqNbg4woBSvYfxRRsIfKmSMn9/elXXn4sAE5UKvlwVQmYnb6p7dpVRPyFvnQA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-sdk-s3": "3.916.0", + "@aws-sdk/types": "3.914.0", + "@smithy/protocol-http": "^5.3.3", + "@smithy/signature-v4": "^5.3.3", + "@smithy/types": "^4.8.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/token-providers": { + "version": "3.916.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.916.0.tgz", + "integrity": "sha512-13GGOEgq5etbXulFCmYqhWtpcEQ6WI6U53dvXbheW0guut8fDFJZmEv7tKMTJgiybxh7JHd0rWcL9JQND8DwoQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.916.0", + "@aws-sdk/nested-clients": "3.916.0", + "@aws-sdk/types": "3.914.0", + "@smithy/property-provider": "^4.2.3", + "@smithy/shared-ini-file-loader": "^4.3.3", + "@smithy/types": "^4.8.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/types": { + "version": "3.914.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.914.0.tgz", + "integrity": "sha512-kQWPsRDmom4yvAfyG6L1lMmlwnTzm1XwMHOU+G5IFlsP4YEaMtXidDzW/wiivY0QFrhfCz/4TVmu0a2aPU57ug==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.8.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-arn-parser": { + "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.893.0.tgz", + "integrity": "sha512-u8H4f2Zsi19DGnwj5FSZzDMhytYF/bCh37vAtBsn3cNDL3YG578X5oc+wSX54pM3tOxS+NY7tvOAo52SW7koUA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-endpoints": { + "version": "3.916.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.916.0.tgz", + "integrity": "sha512-bAgUQwvixdsiGNcuZSDAOWbyHlnPtg8G8TyHD6DTfTmKTHUW6tAn+af/ZYJPXEzXhhpwgJqi58vWnsiDhmr7NQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.914.0", + "@smithy/types": "^4.8.0", + "@smithy/url-parser": "^4.2.3", + "@smithy/util-endpoints": "^3.2.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-locate-window": { + "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.893.0.tgz", + "integrity": "sha512-T89pFfgat6c8nMmpI8eKjBcDcgJq36+m9oiXbcUzeU55MP9ZuGgBomGjGnHaEyF36jenW9gmg3NfZDm0AO2XPg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.914.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.914.0.tgz", + "integrity": "sha512-rMQUrM1ECH4kmIwlGl9UB0BtbHy6ZuKdWFrIknu8yGTRI/saAucqNTh5EI1vWBxZ0ElhK5+g7zOnUuhSmVQYUA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.914.0", + "@smithy/types": "^4.8.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.916.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.916.0.tgz", + "integrity": "sha512-CwfWV2ch6UdjuSV75ZU99N03seEUb31FIUrXBnwa6oONqj/xqXwrxtlUMLx6WH3OJEE4zI3zt5PjlTdGcVwf4g==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-user-agent": "3.916.0", + "@aws-sdk/types": "3.914.0", + "@smithy/node-config-provider": "^4.3.3", + "@smithy/types": "^4.8.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, + "node_modules/@aws-sdk/xml-builder": { + "version": "3.914.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.914.0.tgz", + "integrity": "sha512-k75evsBD5TcIjedycYS7QXQ98AmOtbnxRJOPtCo0IwYRmy7UvqgS/gBL5SmrIqeV6FDSYRQMgdBxSMp6MLmdew==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.8.0", + "fast-xml-parser": "5.2.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws/lambda-invoke-store": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.0.1.tgz", + "integrity": "sha512-ORHRQ2tmvnBXc8t/X9Z8IcSbBA4xTLKuN873FopzklHMeqBst7YG0d+AX97inkvDX+NChYtSr+qGfcqGFaI8Zw==", + "license": "Apache-2.0", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/abort-controller": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.3.tgz", + "integrity": "sha512-xWL9Mf8b7tIFuAlpjKtRPnHrR8XVrwTj5NPYO/QwZPtc0SDLsPxb56V5tzi5yspSMytISHybifez+4jlrx0vkQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.8.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/chunked-blob-reader": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader/-/chunked-blob-reader-5.2.0.tgz", + "integrity": "sha512-WmU0TnhEAJLWvfSeMxBNe5xtbselEO8+4wG0NtZeL8oR21WgH1xiO37El+/Y+H/Ie4SCwBy3MxYWmOYaGgZueA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/chunked-blob-reader-native": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader-native/-/chunked-blob-reader-native-4.2.1.tgz", + "integrity": "sha512-lX9Ay+6LisTfpLid2zZtIhSEjHMZoAR5hHCR4H7tBz/Zkfr5ea8RcQ7Tk4mi0P76p4cN+Btz16Ffno7YHpKXnQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-base64": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/config-resolver": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.0.tgz", + "integrity": "sha512-Kkmz3Mup2PGp/HNJxhCWkLNdlajJORLSjwkcfrj0E7nu6STAEdcMR1ir5P9/xOmncx8xXfru0fbUYLlZog/cFg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.3", + "@smithy/types": "^4.8.0", + "@smithy/util-config-provider": "^4.2.0", + "@smithy/util-endpoints": "^3.2.3", + "@smithy/util-middleware": "^4.2.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/core": { + "version": "3.17.1", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.17.1.tgz", + "integrity": "sha512-V4Qc2CIb5McABYfaGiIYLTmo/vwNIK7WXI5aGveBd9UcdhbOMwcvIMxIw/DJj1S9QgOMa/7FBkarMdIC0EOTEQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/middleware-serde": "^4.2.3", + "@smithy/protocol-http": "^5.3.3", + "@smithy/types": "^4.8.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-middleware": "^4.2.3", + "@smithy/util-stream": "^4.5.4", + "@smithy/util-utf8": "^4.2.0", + "@smithy/uuid": "^1.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/credential-provider-imds": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.3.tgz", + "integrity": "sha512-hA1MQ/WAHly4SYltJKitEsIDVsNmXcQfYBRv2e+q04fnqtAX5qXaybxy/fhUeAMCnQIdAjaGDb04fMHQefWRhw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.3", + "@smithy/property-provider": "^4.2.3", + "@smithy/types": "^4.8.0", + "@smithy/url-parser": "^4.2.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-codec": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-4.2.3.tgz", + "integrity": "sha512-rcr0VH0uNoMrtgKuY7sMfyKqbHc4GQaQ6Yp4vwgm+Z6psPuOgL+i/Eo/QWdXRmMinL3EgFM0Z1vkfyPyfzLmjw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/crc32": "5.2.0", + "@smithy/types": "^4.8.0", + "@smithy/util-hex-encoding": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-browser": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-4.2.3.tgz", + "integrity": "sha512-EcS0kydOr2qJ3vV45y7nWnTlrPmVIMbUFOZbMG80+e2+xePQISX9DrcbRpVRFTS5Nqz3FiEbDcTCAV0or7bqdw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-serde-universal": "^4.2.3", + "@smithy/types": "^4.8.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-config-resolver": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-4.3.3.tgz", + "integrity": "sha512-GewKGZ6lIJ9APjHFqR2cUW+Efp98xLu1KmN0jOWxQ1TN/gx3HTUPVbLciFD8CfScBj2IiKifqh9vYFRRXrYqXA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.8.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-node": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-4.2.3.tgz", + "integrity": "sha512-uQobOTQq2FapuSOlmGLUeGTpvcBLE5Fc7XjERUSk4dxEi4AhTwuyHYZNAvL4EMUp7lzxxkKDFaJ1GY0ovrj0Kg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-serde-universal": "^4.2.3", + "@smithy/types": "^4.8.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-universal": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-4.2.3.tgz", + "integrity": "sha512-QIvH/CKOk1BZPz/iwfgbh1SQD5Y0lpaw2kLA8zpLRRtYMPXeYUEWh+moTaJyqDaKlbrB174kB7FSRFiZ735tWw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-codec": "^4.2.3", + "@smithy/types": "^4.8.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/fetch-http-handler": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.4.tgz", + "integrity": "sha512-bwigPylvivpRLCm+YK9I5wRIYjFESSVwl8JQ1vVx/XhCw0PtCi558NwTnT2DaVCl5pYlImGuQTSwMsZ+pIavRw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.3", + "@smithy/querystring-builder": "^4.2.3", + "@smithy/types": "^4.8.0", + "@smithy/util-base64": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/hash-blob-browser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@smithy/hash-blob-browser/-/hash-blob-browser-4.2.4.tgz", + "integrity": "sha512-W7eIxD+rTNsLB/2ynjmbdeP7TgxRXprfvqQxKFEfy9HW2HeD7t+g+KCIrY0pIn/GFjA6/fIpH+JQnfg5TTk76Q==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/chunked-blob-reader": "^5.2.0", + "@smithy/chunked-blob-reader-native": "^4.2.1", + "@smithy/types": "^4.8.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/hash-node": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.3.tgz", + "integrity": "sha512-6+NOdZDbfuU6s1ISp3UOk5Rg953RJ2aBLNLLBEcamLjHAg1Po9Ha7QIB5ZWhdRUVuOUrT8BVFR+O2KIPmw027g==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.8.0", + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/hash-stream-node": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@smithy/hash-stream-node/-/hash-stream-node-4.2.3.tgz", + "integrity": "sha512-EXMSa2yiStVII3x/+BIynyOAZlS7dGvI7RFrzXa/XssBgck/7TXJIvnjnCu328GY/VwHDC4VeDyP1S4rqwpYag==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.8.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/invalid-dependency": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.3.tgz", + "integrity": "sha512-Cc9W5DwDuebXEDMpOpl4iERo8I0KFjTnomK2RMdhhR87GwrSmUmwMxS4P5JdRf+LsjOdIqumcerwRgYMr/tZ9Q==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.8.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/is-array-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.0.tgz", + "integrity": "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/md5-js": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-4.2.3.tgz", + "integrity": "sha512-5+4bUEJQi/NRgzdA5SVXvAwyvEnD0ZAiKzV3yLO6dN5BG8ScKBweZ8mxXXUtdxq+Dx5k6EshKk0XJ7vgvIPSnA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.8.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-content-length": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.3.tgz", + "integrity": "sha512-/atXLsT88GwKtfp5Jr0Ks1CSa4+lB+IgRnkNrrYP0h1wL4swHNb0YONEvTceNKNdZGJsye+W2HH8W7olbcPUeA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.3", + "@smithy/types": "^4.8.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-endpoint": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.3.5.tgz", + "integrity": "sha512-SIzKVTvEudFWJbxAaq7f2GvP3jh2FHDpIFI6/VAf4FOWGFZy0vnYMPSRj8PGYI8Hjt29mvmwSRgKuO3bK4ixDw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.17.1", + "@smithy/middleware-serde": "^4.2.3", + "@smithy/node-config-provider": "^4.3.3", + "@smithy/shared-ini-file-loader": "^4.3.3", + "@smithy/types": "^4.8.0", + "@smithy/url-parser": "^4.2.3", + "@smithy/util-middleware": "^4.2.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-retry": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.5.tgz", + "integrity": "sha512-DCaXbQqcZ4tONMvvdz+zccDE21sLcbwWoNqzPLFlZaxt1lDtOE2tlVpRSwcTOJrjJSUThdgEYn7HrX5oLGlK9A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.3", + "@smithy/protocol-http": "^5.3.3", + "@smithy/service-error-classification": "^4.2.3", + "@smithy/smithy-client": "^4.9.1", + "@smithy/types": "^4.8.0", + "@smithy/util-middleware": "^4.2.3", + "@smithy/util-retry": "^4.2.3", + "@smithy/uuid": "^1.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-serde": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.3.tgz", + "integrity": "sha512-8g4NuUINpYccxiCXM5s1/V+uLtts8NcX4+sPEbvYQDZk4XoJfDpq5y2FQxfmUL89syoldpzNzA0R9nhzdtdKnQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.3", + "@smithy/types": "^4.8.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-stack": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.3.tgz", + "integrity": "sha512-iGuOJkH71faPNgOj/gWuEGS6xvQashpLwWB1HjHq1lNNiVfbiJLpZVbhddPuDbx9l4Cgl0vPLq5ltRfSaHfspA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.8.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-config-provider": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.3.tgz", + "integrity": "sha512-NzI1eBpBSViOav8NVy1fqOlSfkLgkUjUTlohUSgAEhHaFWA3XJiLditvavIP7OpvTjDp5u2LhtlBhkBlEisMwA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.2.3", + "@smithy/shared-ini-file-loader": "^4.3.3", + "@smithy/types": "^4.8.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-http-handler": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.3.tgz", + "integrity": "sha512-MAwltrDB0lZB/H6/2M5PIsISSwdI5yIh6DaBB9r0Flo9nx3y0dzl/qTMJPd7tJvPdsx6Ks/cwVzheGNYzXyNbQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/abort-controller": "^4.2.3", + "@smithy/protocol-http": "^5.3.3", + "@smithy/querystring-builder": "^4.2.3", + "@smithy/types": "^4.8.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/property-provider": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.3.tgz", + "integrity": "sha512-+1EZ+Y+njiefCohjlhyOcy1UNYjT+1PwGFHCxA/gYctjg3DQWAU19WigOXAco/Ql8hZokNehpzLd0/+3uCreqQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.8.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/protocol-http": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.3.tgz", + "integrity": "sha512-Mn7f/1aN2/jecywDcRDvWWWJF4uwg/A0XjFMJtj72DsgHTByfjRltSqcT9NyE9RTdBSN6X1RSXrhn/YWQl8xlw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.8.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-builder": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.3.tgz", + "integrity": "sha512-LOVCGCmwMahYUM/P0YnU/AlDQFjcu+gWbFJooC417QRB/lDJlWSn8qmPSDp+s4YVAHOgtgbNG4sR+SxF/VOcJQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.8.0", + "@smithy/util-uri-escape": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-parser": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.3.tgz", + "integrity": "sha512-cYlSNHcTAX/wc1rpblli3aUlLMGgKZ/Oqn8hhjFASXMCXjIqeuQBei0cnq2JR8t4RtU9FpG6uyl6PxyArTiwKA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.8.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/service-error-classification": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.3.tgz", + "integrity": "sha512-NkxsAxFWwsPsQiwFG2MzJ/T7uIR6AQNh1SzcxSUnmmIqIQMlLRQDKhc17M7IYjiuBXhrQRjQTo3CxX+DobS93g==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.8.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/shared-ini-file-loader": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.3.3.tgz", + "integrity": "sha512-9f9Ixej0hFhroOK2TxZfUUDR13WVa8tQzhSzPDgXe5jGL3KmaM9s8XN7RQwqtEypI82q9KHnKS71CJ+q/1xLtQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.8.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/signature-v4": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.3.tgz", + "integrity": "sha512-CmSlUy+eEYbIEYN5N3vvQTRfqt0lJlQkaQUIf+oizu7BbDut0pozfDjBGecfcfWf7c62Yis4JIEgqQ/TCfodaA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.2.0", + "@smithy/protocol-http": "^5.3.3", + "@smithy/types": "^4.8.0", + "@smithy/util-hex-encoding": "^4.2.0", + "@smithy/util-middleware": "^4.2.3", + "@smithy/util-uri-escape": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/smithy-client": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.9.1.tgz", + "integrity": "sha512-Ngb95ryR5A9xqvQFT5mAmYkCwbXvoLavLFwmi7zVg/IowFPCfiqRfkOKnbc/ZRL8ZKJ4f+Tp6kSu6wjDQb8L/g==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.17.1", + "@smithy/middleware-endpoint": "^4.3.5", + "@smithy/middleware-stack": "^4.2.3", + "@smithy/protocol-http": "^5.3.3", + "@smithy/types": "^4.8.0", + "@smithy/util-stream": "^4.5.4", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/types": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.8.0.tgz", + "integrity": "sha512-QpELEHLO8SsQVtqP+MkEgCYTFW0pleGozfs3cZ183ZBj9z3VC1CX1/wtFMK64p+5bhtZo41SeLK1rBRtd25nHQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/url-parser": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.3.tgz", + "integrity": "sha512-I066AigYvY3d9VlU3zG9XzZg1yT10aNqvCaBTw9EPgu5GrsEl1aUkcMvhkIXascYH1A8W0LQo3B1Kr1cJNcQEw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/querystring-parser": "^4.2.3", + "@smithy/types": "^4.8.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-base64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.3.0.tgz", + "integrity": "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-body-length-browser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.2.0.tgz", + "integrity": "sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-body-length-node": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.2.1.tgz", + "integrity": "sha512-h53dz/pISVrVrfxV1iqXlx5pRg3V2YWFcSQyPyXZRrZoZj4R4DeWRDo1a7dd3CPTcFi3kE+98tuNyD2axyZReA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-buffer-from": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.0.tgz", + "integrity": "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-config-provider": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.2.0.tgz", + "integrity": "sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-browser": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.4.tgz", + "integrity": "sha512-qI5PJSW52rnutos8Bln8nwQZRpyoSRN6k2ajyoUHNMUzmWqHnOJCnDELJuV6m5PML0VkHI+XcXzdB+6awiqYUw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.2.3", + "@smithy/smithy-client": "^4.9.1", + "@smithy/types": "^4.8.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-node": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.6.tgz", + "integrity": "sha512-c6M/ceBTm31YdcFpgfgQAJaw3KbaLuRKnAz91iMWFLSrgxRpYm03c3bu5cpYojNMfkV9arCUelelKA7XQT36SQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/config-resolver": "^4.4.0", + "@smithy/credential-provider-imds": "^4.2.3", + "@smithy/node-config-provider": "^4.3.3", + "@smithy/property-provider": "^4.2.3", + "@smithy/smithy-client": "^4.9.1", + "@smithy/types": "^4.8.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-endpoints": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.2.3.tgz", + "integrity": "sha512-aCfxUOVv0CzBIkU10TubdgKSx5uRvzH064kaiPEWfNIvKOtNpu642P4FP1hgOFkjQIkDObrfIDnKMKkeyrejvQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.3", + "@smithy/types": "^4.8.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-hex-encoding": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.0.tgz", + "integrity": "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-middleware": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.3.tgz", + "integrity": "sha512-v5ObKlSe8PWUHCqEiX2fy1gNv6goiw6E5I/PN2aXg3Fb/hse0xeaAnSpXDiWl7x6LamVKq7senB+m5LOYHUAHw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.8.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-retry": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.3.tgz", + "integrity": "sha512-lLPWnakjC0q9z+OtiXk+9RPQiYPNAovt2IXD3CP4LkOnd9NpUsxOjMx1SnoUVB7Orb7fZp67cQMtTBKMFDvOGg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/service-error-classification": "^4.2.3", + "@smithy/types": "^4.8.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-stream": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.4.tgz", + "integrity": "sha512-+qDxSkiErejw1BAIXUFBSfM5xh3arbz1MmxlbMCKanDDZtVEQ7PSKW9FQS0Vud1eI/kYn0oCTVKyNzRlq+9MUw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/fetch-http-handler": "^5.3.4", + "@smithy/node-http-handler": "^4.4.3", + "@smithy/types": "^4.8.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-hex-encoding": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-uri-escape": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.0.tgz", + "integrity": "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-utf8": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.0.tgz", + "integrity": "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-waiter": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-4.2.3.tgz", + "integrity": "sha512-5+nU///E5sAdD7t3hs4uwvCTWQtTR8JwKwOCSJtBRx0bY1isDo1QwH87vRK86vlFLBTISqoDA2V6xvP6nF1isQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/abort-controller": "^4.2.3", + "@smithy/types": "^4.8.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/uuid": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@smithy/uuid/-/uuid-1.1.0.tgz", + "integrity": "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/bowser": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.12.1.tgz", + "integrity": "sha512-z4rE2Gxh7tvshQ4hluIT7XcFrgLIQaw9X3A+kTTRdovCz5PMukm/0QC/BKSYPj3omF5Qfypn9O/c5kgpmvYUCw==", + "license": "MIT" + }, + "node_modules/fast-xml-parser": { + "version": "5.2.5", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.2.5.tgz", + "integrity": "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "strnum": "^2.1.0" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/strnum": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.1.tgz", + "integrity": "sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + } + } +} diff --git a/lambda-pdf-converter/package.json b/lambda-pdf-converter/package.json new file mode 100644 index 0000000..af63a2a --- /dev/null +++ b/lambda-pdf-converter/package.json @@ -0,0 +1,9 @@ +{ + "name": "lambda-pdf-converter", + "version": "1.0.0", + "description": "Lambda pour convertir les PDF en images JPEG via Cloudinary lors de l'upload sur S3", + "main": "index.js", + "dependencies": { + "@aws-sdk/client-s3": "^3.913.0" + } +} diff --git a/lambda-pdf-converter/poppler-layer.zip b/lambda-pdf-converter/poppler-layer.zip new file mode 100644 index 0000000..4d79a12 Binary files /dev/null and b/lambda-pdf-converter/poppler-layer.zip differ diff --git a/lambda-pdf-converter/s3-notification.json b/lambda-pdf-converter/s3-notification.json new file mode 100644 index 0000000..6522579 --- /dev/null +++ b/lambda-pdf-converter/s3-notification.json @@ -0,0 +1,23 @@ +{ + "LambdaFunctionConfigurations": [ + { + "Id": "odentas-sign-pdf-converter-trigger", + "LambdaFunctionArn": "arn:aws:lambda:eu-west-3:292468105557:function:odentas-sign-pdf-converter", + "Events": ["s3:ObjectCreated:*"], + "Filter": { + "Key": { + "FilterRules": [ + { + "Name": "prefix", + "Value": "source/" + }, + { + "Name": "suffix", + "Value": ".pdf" + } + ] + } + } + } + ] +} diff --git a/lib/odentas-sign/cloudinary.ts b/lib/odentas-sign/cloudinary.ts new file mode 100644 index 0000000..2e15ef7 --- /dev/null +++ b/lib/odentas-sign/cloudinary.ts @@ -0,0 +1,218 @@ +import { v2 as cloudinary } from 'cloudinary'; +import { S3Client, PutObjectCommand, GetObjectCommand, HeadObjectCommand } from '@aws-sdk/client-s3'; +import { getSignedUrl } from '@aws-sdk/s3-request-presigner'; + +// Configuration Cloudinary +cloudinary.config({ + cloud_name: process.env.CLOUDINARY_CLOUD_NAME, + api_key: process.env.CLOUDINARY_API_KEY, + api_secret: process.env.CLOUDINARY_API_SECRET, + secure: true, +}); + +// Client S3 pour stocker les images converties +const s3Client = new S3Client({ + region: process.env.AWS_REGION || 'eu-west-3', + credentials: { + accessKeyId: process.env.AWS_ACCESS_KEY_ID!, + secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!, + }, +}); + +const S3_BUCKET = process.env.AWS_S3_BUCKET || 'odentas-docs'; + +interface PageImage { + pageNumber: number; + imageUrl: string; + width: number; + height: number; +} + +/** + * Vérifie si les images d'un PDF existent déjà dans S3 + */ +async function checkImagesExistInS3(requestId: string): Promise { + try { + // On essaie de trouver les images existantes + // On commence par vérifier la page 1 + const firstPageKey = `odentas-sign-images/${requestId}/page-1.jpg`; + + try { + await s3Client.send(new HeadObjectCommand({ + Bucket: S3_BUCKET, + Key: firstPageKey, + })); + } catch (error) { + // La première page n'existe pas, donc les images ne sont pas en cache + return null; + } + + console.log('[S3 Cache] Images trouvées dans S3, récupération...'); + + // La page 1 existe, on récupère toutes les pages + const pageImages: PageImage[] = []; + let pageNum = 1; + + while (true) { + const s3Key = `odentas-sign-images/${requestId}/page-${pageNum}.jpg`; + + try { + // Vérifier si la page existe + await s3Client.send(new HeadObjectCommand({ + Bucket: S3_BUCKET, + Key: s3Key, + })); + + // Générer l'URL presignée + const command = new GetObjectCommand({ + Bucket: S3_BUCKET, + Key: s3Key, + }); + const s3Url = await getSignedUrl(s3Client, command, { expiresIn: 86400 }); + + pageImages.push({ + pageNumber: pageNum, + imageUrl: s3Url, + width: 1400, + height: Math.round(1400 * 1.414), + }); + + pageNum++; + } catch (error) { + // Plus de pages, on sort de la boucle + break; + } + } + + console.log(`[S3 Cache] ✅ ${pageImages.length} page(s) récupérées depuis S3 (pas de conversion)`); + return pageImages.length > 0 ? pageImages : null; + } catch (error) { + console.log('[S3 Cache] Pas de cache trouvé, conversion nécessaire'); + return null; + } +} + +/** + * Convertit un PDF en images JPEG via Cloudinary, + * puis stocke les images sur S3 pour économiser la bande passante Cloudinary + */ +export async function convertPdfToImagesWithCloudinary( + pdfBuffer: Buffer, + requestId: string +): Promise { + try { + // 1. Vérifier si les images existent déjà dans S3 + const cachedImages = await checkImagesExistInS3(requestId); + if (cachedImages) { + console.log('[S3 Cache] ✅ Utilisation du cache S3, pas de conversion Cloudinary'); + return cachedImages; + } + + console.log('[Cloudinary] Pas de cache S3, début conversion...'); + console.log('[Cloudinary] Début upload PDF, taille:', pdfBuffer.length); + + // 1. Upload le PDF sur Cloudinary pour conversion + const uploadResult = await new Promise((resolve, reject) => { + const uploadStream = cloudinary.uploader.upload_stream( + { + folder: `odentas-sign-temp/${requestId}`, + resource_type: 'image', + format: 'jpg', + public_id: `pdf-${requestId}`, + transformation: [ + { width: 1400, crop: 'scale' }, + { quality: 90 }, + ], + }, + (error, result) => { + if (error) reject(error); + else resolve(result); + } + ); + + uploadStream.end(pdfBuffer); + }); + + console.log('[Cloudinary] Upload réussi, conversion en cours...'); + + const baseUrl = uploadResult.secure_url; + const pages = uploadResult.pages || 1; + + const pageImages: PageImage[] = []; + + // 2. Pour chaque page, télécharger l'image depuis Cloudinary et la stocker sur S3 + for (let pageNum = 1; pageNum <= pages; pageNum++) { + // URL Cloudinary de la page + const cloudinaryPageUrl = baseUrl.replace( + '/upload/', + `/upload/pg_${pageNum}/w_1400,q_90/` + ); + + console.log(`[Cloudinary] Téléchargement page ${pageNum}/${pages} depuis Cloudinary...`); + + // Télécharger l'image depuis Cloudinary + const imageResponse = await fetch(cloudinaryPageUrl); + if (!imageResponse.ok) { + throw new Error(`Erreur téléchargement page ${pageNum}: ${imageResponse.statusText}`); + } + + const imageBuffer = Buffer.from(await imageResponse.arrayBuffer()); + + // 3. Stocker l'image sur S3 + const s3Key = `odentas-sign-images/${requestId}/page-${pageNum}.jpg`; + await s3Client.send( + new PutObjectCommand({ + Bucket: S3_BUCKET, + Key: s3Key, + Body: imageBuffer, + ContentType: 'image/jpeg', + CacheControl: 'public, max-age=31536000', // Cache 1 an + }) + ); + + // Générer une URL presignée S3 (valide 24h, renouvelée à chaque accès) + const command = new GetObjectCommand({ + Bucket: S3_BUCKET, + Key: s3Key, + }); + const s3Url = await getSignedUrl(s3Client, command, { expiresIn: 86400 }); // 24h + + pageImages.push({ + pageNumber: pageNum, + imageUrl: s3Url, + width: 1400, + height: Math.round(1400 * 1.414), // Ratio A4 + }); + + console.log(`[Cloudinary → S3] Page ${pageNum}/${pages} stockée sur S3: ${s3Key}`); + } + + // 4. Nettoyer Cloudinary (supprimer le PDF temporaire) + try { + await cloudinary.uploader.destroy(`odentas-sign-temp/${requestId}/pdf-${requestId}`); + console.log('[Cloudinary] Nettoyage temporaire effectué'); + } catch (cleanupError) { + console.warn('[Cloudinary] Erreur nettoyage (non bloquant):', cleanupError); + } + + console.log(`[Cloudinary → S3] ✅ ${pages} page(s) converties et stockées sur S3`); + return pageImages; + } catch (error) { + console.error('[Cloudinary] Erreur:', error); + throw new Error( + `Erreur Cloudinary: ${error instanceof Error ? error.message : 'Erreur inconnue'}` + ); + } +} + +/** + * Supprime un PDF et ses pages de Cloudinary + */ +export async function deleteFromCloudinary(requestId: string): Promise { + try { + await cloudinary.api.delete_resources_by_prefix(`odentas-sign/${requestId}`); + console.log(`[Cloudinary] PDF ${requestId} supprimé`); + } catch (error) { + console.error('[Cloudinary] Erreur suppression:', error); + } +} diff --git a/lib/odentas-sign/pdf-converter.ts b/lib/odentas-sign/pdf-converter.ts new file mode 100644 index 0000000..7398623 --- /dev/null +++ b/lib/odentas-sign/pdf-converter.ts @@ -0,0 +1,52 @@ +import { pdf } from 'pdf-to-img'; + +interface PageImage { + pageNumber: number; + imageBase64: string; + width: number; + height: number; +} + +/** + * Convertit un PDF (buffer) en images JPEG + * Solution ultra simple avec pdf-to-img + */ +export async function convertPdfToJpegImages( + pdfBuffer: Buffer, + targetWidth: number = 1400 +): Promise { + try { + console.log('[PDF Converter] Début conversion, taille buffer:', pdfBuffer.length); + + const pages: PageImage[] = []; + + // Convertir le PDF en images + const document = await pdf(pdfBuffer, { + scale: 2.0, // Haute résolution + }); + + let pageNum = 1; + for await (const page of document) { + // page est déjà un Buffer PNG + const imageBase64 = `data:image/png;base64,${page.toString('base64')}`; + + pages.push({ + pageNumber: pageNum, + imageBase64, + width: targetWidth, + height: Math.round(targetWidth * 1.414), // Ratio A4 approximatif + }); + + console.log(`[PDF Converter] Page ${pageNum} convertie`); + pageNum++; + } + + console.log(`[PDF Converter] ${pages.length} page(s) converties avec succès`); + return pages; + } catch (error) { + console.error('[PDF Converter] Erreur:', error); + throw new Error(`Erreur de conversion PDF: ${error instanceof Error ? error.message : 'Erreur inconnue'}`); + } +} + + diff --git a/lib/odentas-sign/placeholders.ts b/lib/odentas-sign/placeholders.ts new file mode 100644 index 0000000..dfab3c7 --- /dev/null +++ b/lib/odentas-sign/placeholders.ts @@ -0,0 +1,126 @@ +// Utilities to extract DocuSeal-style signature placeholders from PDFs +// and estimate reasonable positions when exact text coordinates are unavailable. + +export type PlaceholderMatch = { + fullMatch: string; + label: string; + role: string; + type: string; + width: number; // mm + height: number; // mm + startIndex: number; + endIndex: number; +}; + +export type EstimatedPosition = { + role: string; + label: string; + page: number; // 1-indexed + x: number; // mm from left + y: number; // mm from top + width: number; // mm + height: number; // mm +}; + +const PLACEHOLDER_REGEX = /\{\{([^;]+);role=([^;]+);type=([^;]+);height=(\d+);width=(\d+)\}\}/g; + +/** + * Count PDF pages by scanning for '/Type /Page' markers in raw bytes. + * This is heuristic but robust enough for most PDFs without full parsing. + */ +export function countPdfPagesFromBytes(bytes: Uint8Array | Buffer): number { + try { + const text = bufferToLatin1String(bytes); + // Count '/Type /Page' but not '/Type /Pages' + const matches = text.match(/\/Type\s*\/Page(?!s)\b/g); + if (matches && matches.length > 0) return matches.length; + } catch { + // ignore + } + // Default to 1 page if unknown + return 1; +} + +/** + * Extract placeholders from PDF bytes by regex scanning the raw stream text. + */ +export function extractPlaceholdersFromPdfBuffer(bytes: Uint8Array | Buffer): PlaceholderMatch[] { + const text = bufferToLatin1String(bytes); + const placeholders: PlaceholderMatch[] = []; + + PLACEHOLDER_REGEX.lastIndex = 0; + let match: RegExpExecArray | null; + while ((match = PLACEHOLDER_REGEX.exec(text)) !== null) { + placeholders.push({ + fullMatch: match[0], + label: match[1].trim(), + role: match[2].trim(), + type: match[3].trim(), + height: parseInt(match[4], 10), + width: parseInt(match[5], 10), + startIndex: match.index, + endIndex: match.index + match[0].length, + }); + } + + return placeholders; +} + +/** + * Estimate reasonable positions (in mm) for placeholders when exact coordinates are unknown. + * Assumes A4 portrait (210 x 297mm). Places fields near the bottom margin, left/right by role. + */ +export function estimatePositionsFromPlaceholders( + placeholders: PlaceholderMatch[], + pageCount: number +): EstimatedPosition[] { + const A4_WIDTH_MM = 210; + const A4_HEIGHT_MM = 297; + const MARGIN_X_MM = 20; + const MARGIN_BOTTOM_MM = 30; + + // Prefer placing on the last page by default + const defaultPage = Math.max(1, pageCount); + + return placeholders.map((ph) => { + // Les placeholders portent déjà les dimensions attendues du cadre + // de signature (en millimètres), on les utilise telles quelles. + const width = Math.max(20, ph.width || 150); // mm + const height = Math.max(10, ph.height || 60); // mm + + // Default Y: bottom area + const y = A4_HEIGHT_MM - MARGIN_BOTTOM_MM - height; + + // Role-based horizontal placement: employer left, employee right + const roleLc = ph.role.toLowerCase(); + const isEmployee = roleLc.includes('salari') || roleLc.includes('employé') || roleLc.includes('employe'); + + const x = isEmployee + ? Math.max(MARGIN_X_MM, A4_WIDTH_MM - MARGIN_X_MM - width) + : MARGIN_X_MM; + + return { + role: ph.role, + label: ph.label, + page: defaultPage, + x, + y, + width, + height, + }; + }); +} + +function bufferToLatin1String(bytes: Uint8Array | Buffer): string { + if (typeof Buffer !== 'undefined' && (bytes as Buffer).toString) { + return (bytes as Buffer).toString('latin1'); + } + // Fallback for environments without Node Buffer + let result = ''; + const chunk = 8192; + for (let i = 0; i < bytes.length; i += chunk) { + const slice = bytes.slice(i, i + chunk); + result += Array.from(slice as any, (b: number) => String.fromCharCode(b)).join(''); + } + return result; +} diff --git a/next.config.mjs b/next.config.mjs index b72bd7a..f7d2f54 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -11,6 +11,12 @@ const nextConfig = { // Configuration pour optimiser les chunks et éviter les erreurs de modules Supabase webpack: (config, { dev, isServer }) => { if (!isServer) { + // Ignorer le module 'canvas' côté client (optionnel pour pdfjs-dist) + config.resolve.fallback = { + ...config.resolve.fallback, + canvas: false, + }; + // Optimiser les chunks pour éviter les problèmes avec Supabase config.optimization.splitChunks = { ...config.optimization.splitChunks, diff --git a/package-lock.json b/package-lock.json index 2e1103e..5dc287f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,6 +26,7 @@ "axios": "^1.12.2", "bcryptjs": "^3.0.2", "canvas-confetti": "^1.9.4", + "cloudinary": "^2.8.0", "clsx": "^2.1.1", "cmdk": "^1.1.1", "dotenv": "^17.2.2", @@ -38,13 +39,16 @@ "nprogress": "^0.2.0", "pdf-lib": "^1.17.1", "pdf-parse": "^2.4.5", - "pdfjs-dist": "^3.11.174", + "pdf-to-img": "^5.0.0", + "pdf2pic": "^3.2.0", "posthog-js": "^1.275.1", "posthog-node": "^5.9.5", "qrcode": "^1.5.4", "react": "^18.3.1", "react-dom": "^18.3.1", "react-hook-form": "^7.62.0", + "react-pdf": "^10.2.0", + "sharp": "^0.34.4", "sonner": "^2.0.7", "tailwind-merge": "^2.6.0", "use-debounce": "^10.0.6" @@ -1128,7 +1132,6 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.5.0.tgz", "integrity": "sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==", - "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -1247,6 +1250,433 @@ "dev": true, "license": "BSD-3-Clause" }, + "node_modules/@img/colour": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.0.0.tgz", + "integrity": "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.34.4", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.4.tgz", + "integrity": "sha512-sitdlPzDVyvmINUdJle3TNHl+AG9QcwiAMsXmccqsCOMZNIdW2/7S26w0LyU8euiLVzFBL3dXPwVCq/ODnf2vA==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.2.3" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.34.4", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.4.tgz", + "integrity": "sha512-rZheupWIoa3+SOdF/IcUe1ah4ZDpKBGWcsPX6MT0lYniH9micvIU7HQkYTfrx5Xi8u+YqwLtxC/3vl8TQN6rMg==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.2.3" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.3.tgz", + "integrity": "sha512-QzWAKo7kpHxbuHqUC28DZ9pIKpSi2ts2OJnoIGI26+HMgq92ZZ4vk8iJd4XsxN+tYfNJxzH6W62X5eTcsBymHw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.3.tgz", + "integrity": "sha512-Ju+g2xn1E2AKO6YBhxjj+ACcsPQRHT0bhpglxcEf+3uyPY+/gL8veniKoo96335ZaPo03bdDXMv0t+BBFAbmRA==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.3.tgz", + "integrity": "sha512-x1uE93lyP6wEwGvgAIV0gP6zmaL/a0tGzJs/BIDDG0zeBhMnuUPm7ptxGhUbcGs4okDJrk4nxgrmxpib9g6HpA==", + "cpu": [ + "arm" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.3.tgz", + "integrity": "sha512-I4RxkXU90cpufazhGPyVujYwfIm9Nk1QDEmiIsaPwdnm013F7RIceaCc87kAH+oUB1ezqEvC6ga4m7MSlqsJvQ==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.3.tgz", + "integrity": "sha512-Y2T7IsQvJLMCBM+pmPbM3bKT/yYJvVtLJGfCs4Sp95SjvnFIjynbjzsa7dY1fRJX45FTSfDksbTp6AGWudiyCg==", + "cpu": [ + "ppc64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.3.tgz", + "integrity": "sha512-RgWrs/gVU7f+K7P+KeHFaBAJlNkD1nIZuVXdQv6S+fNA6syCcoboNjsV2Pou7zNlVdNQoQUpQTk8SWDHUA3y/w==", + "cpu": [ + "s390x" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.3.tgz", + "integrity": "sha512-3JU7LmR85K6bBiRzSUc/Ff9JBVIFVvq6bomKE0e63UXGeRw2HPVEjoJke1Yx+iU4rL7/7kUjES4dZ/81Qjhyxg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.3.tgz", + "integrity": "sha512-F9q83RZ8yaCwENw1GieztSfj5msz7GGykG/BA+MOUefvER69K/ubgFHNeSyUu64amHIYKGDs4sRCMzXVj8sEyw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.3.tgz", + "integrity": "sha512-U5PUY5jbc45ANM6tSJpsgqmBF/VsL6LnxJmIf11kB7J5DctHgqm0SkuXzVWtIY90GnJxKnC/JT251TDnk1fu/g==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.34.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.4.tgz", + "integrity": "sha512-Xyam4mlqM0KkTHYVSuc6wXRmM7LGN0P12li03jAnZ3EJWZqj83+hi8Y9UxZUbxsgsK1qOEwg7O0Bc0LjqQVtxA==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.2.3" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.34.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.4.tgz", + "integrity": "sha512-YXU1F/mN/Wu786tl72CyJjP/Ngl8mGHN1hST4BGl+hiW5jhCnV2uRVTNOcaYPs73NeT/H8Upm3y9582JVuZHrQ==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.2.3" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.34.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.4.tgz", + "integrity": "sha512-F4PDtF4Cy8L8hXA2p3TO6s4aDt93v+LKmpcYFLAVdkkD3hSxZzee0rh6/+94FpAynsuMpLX5h+LRsSG3rIciUQ==", + "cpu": [ + "ppc64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.2.3" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.34.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.4.tgz", + "integrity": "sha512-qVrZKE9Bsnzy+myf7lFKvng6bQzhNUAYcVORq2P7bDlvmF6u2sCmK2KyEQEBdYk+u3T01pVsPrkj943T1aJAsw==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.2.3" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.34.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.4.tgz", + "integrity": "sha512-ZfGtcp2xS51iG79c6Vhw9CWqQC8l2Ot8dygxoDoIQPTat/Ov3qAa8qpxSrtAEAJW+UjTXc4yxCjNfxm4h6Xm2A==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.2.3" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.4.tgz", + "integrity": "sha512-8hDVvW9eu4yHWnjaOOR8kHVrew1iIX+MUgwxSuH2XyYeNRtLUe4VNioSqbNkB7ZYQJj9rUTT4PyRscyk2PXFKA==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.2.3" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.4.tgz", + "integrity": "sha512-lU0aA5L8QTlfKjpDCEFOZsTYGn3AEiO6db8W5aQDxj0nQkVrZWmN3ZP9sYKWJdtq3PWPhUNlqehWyXpYDcI9Sg==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.2.3" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.34.4", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.4.tgz", + "integrity": "sha512-33QL6ZO/qpRyG7woB/HUALz28WnTMI2W1jgX3Nu2bypqLIKx/QKMILLJzJjI+SIbvXdG9fUnmrxR7vbi1sTBeA==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.5.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.4", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.4.tgz", + "integrity": "sha512-2Q250do/5WXTwxW3zjsEuMSv5sUU4Tq9VThWKlU2EYLm4MB7ZeMwF+SFJutldYODXF6jzc6YEOC+VfX0SZQPqA==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.34.4", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.4.tgz", + "integrity": "sha512-3ZeLue5V82dT92CNL6rsal6I2weKw1cYu+rGKm8fOCCtJTR2gYeUfY3FqUnIJsMUPIH68oS5jmZ0NiJ508YpEw==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.34.4", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.4.tgz", + "integrity": "sha512-xIyj4wpYs8J18sVN3mSQjwrw7fKUqRw+Z5rnHNCy5fYTxigBz81u5mOMPmFumwjcn8+ld1ppptMBCLic1nz6ig==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -1388,6 +1818,7 @@ "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", "license": "BSD-3-Clause", "optional": true, + "peer": true, "dependencies": { "detect-libc": "^2.0.0", "https-proxy-agent": "^5.0.0", @@ -4008,7 +4439,8 @@ "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", "license": "ISC", - "optional": true + "optional": true, + "peer": true }, "node_modules/acorn": { "version": "8.15.0", @@ -4039,6 +4471,7 @@ "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", "license": "MIT", "optional": true, + "peer": true, "dependencies": { "debug": "4" }, @@ -4113,7 +4546,8 @@ "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.1.0.tgz", "integrity": "sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew==", "license": "ISC", - "optional": true + "optional": true, + "peer": true }, "node_modules/are-we-there-yet": { "version": "2.0.0", @@ -4122,6 +4556,7 @@ "deprecated": "This package is no longer supported.", "license": "ISC", "optional": true, + "peer": true, "dependencies": { "delegates": "^1.0.0", "readable-stream": "^3.6.0" @@ -4206,6 +4641,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/array-parallel": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/array-parallel/-/array-parallel-0.1.3.tgz", + "integrity": "sha512-TDPTwSWW5E4oiFiKmz6RGJ/a80Y91GuLgUYuLd49+XBS75tYo8PNgaT2K/OxuQYqkoI852MDGBorg9OcUSTQ8w==", + "license": "MIT" + }, + "node_modules/array-series": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/array-series/-/array-series-0.1.5.tgz", + "integrity": "sha512-L0XlBwfx9QetHOsbLDrE/vh2t018w9462HM3iaFfxRiK83aJjAt/Ja3NMkOW7FICwWTlQBa3ZbL5FKhuQWkDrg==", + "license": "MIT" + }, "node_modules/array.prototype.findlast": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", @@ -4711,22 +5158,6 @@ ], "license": "CC-BY-4.0" }, - "node_modules/canvas": { - "version": "2.11.2", - "resolved": "https://registry.npmjs.org/canvas/-/canvas-2.11.2.tgz", - "integrity": "sha512-ItanGBMrmRV7Py2Z+Xhs7cT+FNt5K0vPL4p9EZ/UX/Mu7hFbkxSjKF2KVtPwX7UYWp7dRKnrTvReflgrItJbdw==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@mapbox/node-pre-gyp": "^1.0.0", - "nan": "^2.17.0", - "simple-get": "^3.0.3" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/canvas-confetti": { "version": "1.9.4", "resolved": "https://registry.npmjs.org/canvas-confetti/-/canvas-confetti-1.9.4.tgz", @@ -4792,16 +5223,6 @@ "node": ">= 6" } }, - "node_modules/chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "license": "ISC", - "optional": true, - "engines": { - "node": ">=10" - } - }, "node_modules/client-only": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", @@ -4819,6 +5240,19 @@ "wrap-ansi": "^6.2.0" } }, + "node_modules/cloudinary": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/cloudinary/-/cloudinary-2.8.0.tgz", + "integrity": "sha512-s7frvR0HnQXeJsQSIsbLa/I09IMb1lOnVLEDH5b5E53WTiCYgrNNOBGV/i/nLHwrcEOUkqjfSwP1+enXWNYmdw==", + "license": "MIT", + "dependencies": { + "lodash": "^4.17.21", + "q": "^1.5.1" + }, + "engines": { + "node": ">=9" + } + }, "node_modules/clsx": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", @@ -4891,6 +5325,7 @@ "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", "license": "ISC", "optional": true, + "peer": true, "bin": { "color-support": "bin.js" } @@ -4929,7 +5364,8 @@ "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", "license": "ISC", - "optional": true + "optional": true, + "peer": true }, "node_modules/cookie": { "version": "1.0.2", @@ -4955,7 +5391,6 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -5083,19 +5518,6 @@ "node": ">=0.10.0" } }, - "node_modules/decompress-response": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", - "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", - "license": "MIT", - "optional": true, - "dependencies": { - "mimic-response": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -5152,14 +5574,23 @@ "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", "license": "MIT", - "optional": true + "optional": true, + "peer": true + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } }, "node_modules/detect-libc": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", "license": "Apache-2.0", - "optional": true, "engines": { "node": ">=8" } @@ -6177,6 +6608,7 @@ "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", "license": "ISC", "optional": true, + "peer": true, "dependencies": { "minipass": "^3.0.0" }, @@ -6190,6 +6622,7 @@ "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "license": "ISC", "optional": true, + "peer": true, "dependencies": { "yallist": "^4.0.0" }, @@ -6266,6 +6699,7 @@ "deprecated": "This package is no longer supported.", "license": "ISC", "optional": true, + "peer": true, "dependencies": { "aproba": "^1.0.3 || ^2.0.0", "color-support": "^1.1.2", @@ -6286,7 +6720,8 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "license": "ISC", - "optional": true + "optional": true, + "peer": true }, "node_modules/generator-function": { "version": "2.0.1", @@ -6451,6 +6886,31 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gm": { + "version": "1.25.1", + "resolved": "https://registry.npmjs.org/gm/-/gm-1.25.1.tgz", + "integrity": "sha512-jgcs2vKir9hFogGhXIfs0ODhJTfIrbECCehg38tqFgHm8zqXx7kAJyCYAFK4jTjx71AxrkFtkJBawbAxYUPX9A==", + "deprecated": "The gm module has been sunset. Please migrate to an alternative. https://github.com/aheckmann/gm?tab=readme-ov-file#2025-02-24-this-project-is-not-maintained", + "license": "MIT", + "dependencies": { + "array-parallel": "~0.1.3", + "array-series": "~0.1.5", + "cross-spawn": "^7.0.5", + "debug": "^3.1.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/gm/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -6581,7 +7041,8 @@ "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", "license": "ISC", - "optional": true + "optional": true, + "peer": true }, "node_modules/hasown": { "version": "2.0.2", @@ -6614,6 +7075,7 @@ "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", "license": "MIT", "optional": true, + "peer": true, "dependencies": { "agent-base": "6", "debug": "4" @@ -7154,7 +7616,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, "license": "ISC" }, "node_modules/iterator.prototype": { @@ -7420,6 +7881,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, "node_modules/lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", @@ -7497,12 +7964,22 @@ "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc" } }, + "node_modules/make-cancellable-promise": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/make-cancellable-promise/-/make-cancellable-promise-2.0.0.tgz", + "integrity": "sha512-3SEQqTpV9oqVsIWqAcmDuaNeo7yBO3tqPtqGRcKkEo0lrzD3wqbKG9mkxO65KoOgXqj+zH2phJ2LiAsdzlogSw==", + "license": "MIT", + "funding": { + "url": "https://github.com/wojtekmaj/make-cancellable-promise?sponsor=1" + } + }, "node_modules/make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", "license": "MIT", "optional": true, + "peer": true, "dependencies": { "semver": "^6.0.0" }, @@ -7519,10 +7996,20 @@ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "license": "ISC", "optional": true, + "peer": true, "bin": { "semver": "bin/semver.js" } }, + "node_modules/make-event-props": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/make-event-props/-/make-event-props-2.0.0.tgz", + "integrity": "sha512-G/hncXrl4Qt7mauJEXSg3AcdYzmpkIITTNl5I+rH9sog5Yw0kK6vseJjCaPfOXqOqQuPUP89Rkhfz5kPS8ijtw==", + "license": "MIT", + "funding": { + "url": "https://github.com/wojtekmaj/make-event-props?sponsor=1" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -7532,6 +8019,23 @@ "node": ">= 0.4" } }, + "node_modules/merge-refs": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-refs/-/merge-refs-2.0.0.tgz", + "integrity": "sha512-3+B21mYK2IqUWnd2EivABLT7ueDhb0b8/dGK8LoFQPrU61YITeCMn14F7y7qZafWNZhUEKb24cJdiT5Wxs3prg==", + "license": "MIT", + "funding": { + "url": "https://github.com/wojtekmaj/merge-refs?sponsor=1" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -7577,19 +8081,6 @@ "node": ">= 0.6" } }, - "node_modules/mimic-response": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", - "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -7629,6 +8120,7 @@ "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", "license": "MIT", "optional": true, + "peer": true, "dependencies": { "minipass": "^3.0.0", "yallist": "^4.0.0" @@ -7643,6 +8135,7 @@ "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "license": "ISC", "optional": true, + "peer": true, "dependencies": { "yallist": "^4.0.0" }, @@ -7656,6 +8149,7 @@ "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", "license": "MIT", "optional": true, + "peer": true, "bin": { "mkdirp": "bin/cmd.js" }, @@ -7710,7 +8204,8 @@ "resolved": "https://registry.npmjs.org/nan/-/nan-2.23.0.tgz", "integrity": "sha512-1UxuyYGdoQHcGg87Lkqm3FzefucTa0NAiOcuRsDmysep3c1LVCRK2krrUDafMWtjSG04htvAmvg96+SDknOmgQ==", "license": "MIT", - "optional": true + "optional": true, + "peer": true }, "node_modules/nanoid": { "version": "3.3.11", @@ -7844,6 +8339,7 @@ "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", "license": "MIT", "optional": true, + "peer": true, "dependencies": { "whatwg-url": "^5.0.0" }, @@ -7872,6 +8368,7 @@ "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", "license": "ISC", "optional": true, + "peer": true, "dependencies": { "abbrev": "1" }, @@ -7909,6 +8406,7 @@ "deprecated": "This package is no longer supported.", "license": "ISC", "optional": true, + "peer": true, "dependencies": { "are-we-there-yet": "^2.0.0", "console-control-strings": "^1.1.0", @@ -8197,7 +8695,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -8233,6 +8730,7 @@ "integrity": "sha512-ad/3bsalbbWhmBo0D6FZ4RNMwsLsPpL6gnvhuSaU5Vm7b06Kr5ubSltQQ0T7YKsiJQO+g22zJ4dJKNTXIyOXtA==", "license": "MIT", "optional": true, + "peer": true, "engines": { "node": ">=8" } @@ -8287,11 +8785,55 @@ "@napi-rs/canvas": "^0.1.80" } }, + "node_modules/pdf-to-img": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pdf-to-img/-/pdf-to-img-5.0.0.tgz", + "integrity": "sha512-QJy7P2Qbk86lntPPIC3HVZAyw/CvM+CP592UbO7grborOJ74Icspb8A2sdHO33MDb/WHwHZpSJxd771rQTlZKA==", + "license": "MIT", + "dependencies": { + "pdfjs-dist": "~5.4.0" + }, + "bin": { + "pdf2img": "bin/cli.mjs" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/pdf-to-img/node_modules/pdfjs-dist": { + "version": "5.4.296", + "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-5.4.296.tgz", + "integrity": "sha512-DlOzet0HO7OEnmUmB6wWGJrrdvbyJKftI1bhMitK7O2N8W2gc757yyYBbINy9IDafXAV9wmKr9t7xsTaNKRG5Q==", + "license": "Apache-2.0", + "engines": { + "node": ">=20.16.0 || >=22.3.0" + }, + "optionalDependencies": { + "@napi-rs/canvas": "^0.1.80" + } + }, + "node_modules/pdf2pic": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/pdf2pic/-/pdf2pic-3.2.0.tgz", + "integrity": "sha512-p0bp+Mp4iJy2hqSCLvJ521rDaZkzBvDFT9O9Y0BUID3I04/eDaebAFM5t8hoWeo2BCf42cDijLCGJWTOtkJVpA==", + "license": "MIT", + "dependencies": { + "gm": "^1.25.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "paypal", + "url": "https://www.paypal.me/yakovmeister" + } + }, "node_modules/pdfjs-dist": { "version": "3.11.174", "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-3.11.174.tgz", "integrity": "sha512-TdTZPf1trZ8/UFu5Cx/GXB7GZM30LT+wWUNfsi6Bq8ePLnb+woNKtDymI2mxZYBpMbonNFqKmiz684DIfnd8dA==", "license": "Apache-2.0", + "peer": true, "engines": { "node": ">=18" }, @@ -8300,6 +8842,64 @@ "path2d-polyfill": "^2.0.1" } }, + "node_modules/pdfjs-dist/node_modules/canvas": { + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/canvas/-/canvas-2.11.2.tgz", + "integrity": "sha512-ItanGBMrmRV7Py2Z+Xhs7cT+FNt5K0vPL4p9EZ/UX/Mu7hFbkxSjKF2KVtPwX7UYWp7dRKnrTvReflgrItJbdw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@mapbox/node-pre-gyp": "^1.0.0", + "nan": "^2.17.0", + "simple-get": "^3.0.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/pdfjs-dist/node_modules/decompress-response": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", + "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "mimic-response": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pdfjs-dist/node_modules/mimic-response": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", + "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==", + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pdfjs-dist/node_modules/simple-get": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.1.tgz", + "integrity": "sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==", + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "decompress-response": "^4.2.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -8605,6 +9205,17 @@ "node": ">=6" } }, + "node_modules/q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==", + "deprecated": "You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other.\n\n(For a CapTP with native promises, see @endo/eventual-send and @endo/captp)", + "license": "MIT", + "engines": { + "node": ">=0.6.0", + "teleport": ">=0.2.0" + } + }, "node_modules/qrcode": { "version": "1.5.4", "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.4.tgz", @@ -8700,6 +9311,47 @@ "dev": true, "license": "MIT" }, + "node_modules/react-pdf": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/react-pdf/-/react-pdf-10.2.0.tgz", + "integrity": "sha512-zk0DIL31oCh8cuQycM0SJKfwh4Onz0/Nwi6wTOjgtEjWGUY6eM+/vuzvOP3j70qtEULn7m1JtaeGzud1w5fY2Q==", + "license": "MIT", + "dependencies": { + "clsx": "^2.0.0", + "dequal": "^2.0.3", + "make-cancellable-promise": "^2.0.0", + "make-event-props": "^2.0.0", + "merge-refs": "^2.0.0", + "pdfjs-dist": "5.4.296", + "tiny-invariant": "^1.0.0", + "warning": "^4.0.0" + }, + "funding": { + "url": "https://github.com/wojtekmaj/react-pdf?sponsor=1" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-pdf/node_modules/pdfjs-dist": { + "version": "5.4.296", + "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-5.4.296.tgz", + "integrity": "sha512-DlOzet0HO7OEnmUmB6wWGJrrdvbyJKftI1bhMitK7O2N8W2gc757yyYBbINy9IDafXAV9wmKr9t7xsTaNKRG5Q==", + "license": "Apache-2.0", + "engines": { + "node": ">=20.16.0 || >=22.3.0" + }, + "optionalDependencies": { + "@napi-rs/canvas": "^0.1.80" + } + }, "node_modules/react-remove-scroll": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.1.tgz", @@ -8785,6 +9437,7 @@ "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "license": "MIT", "optional": true, + "peer": true, "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -9134,11 +9787,52 @@ "node": ">= 0.4" } }, + "node_modules/sharp": { + "version": "0.34.4", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.4.tgz", + "integrity": "sha512-FUH39xp3SBPnxWvd5iib1X8XY7J0K0X7d93sie9CJg2PO8/7gmg89Nve6OjItK53/MlAushNNxteBYfM6DEuoA==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@img/colour": "^1.0.0", + "detect-libc": "^2.1.0", + "semver": "^7.7.2" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.34.4", + "@img/sharp-darwin-x64": "0.34.4", + "@img/sharp-libvips-darwin-arm64": "1.2.3", + "@img/sharp-libvips-darwin-x64": "1.2.3", + "@img/sharp-libvips-linux-arm": "1.2.3", + "@img/sharp-libvips-linux-arm64": "1.2.3", + "@img/sharp-libvips-linux-ppc64": "1.2.3", + "@img/sharp-libvips-linux-s390x": "1.2.3", + "@img/sharp-libvips-linux-x64": "1.2.3", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.3", + "@img/sharp-libvips-linuxmusl-x64": "1.2.3", + "@img/sharp-linux-arm": "0.34.4", + "@img/sharp-linux-arm64": "0.34.4", + "@img/sharp-linux-ppc64": "0.34.4", + "@img/sharp-linux-s390x": "0.34.4", + "@img/sharp-linux-x64": "0.34.4", + "@img/sharp-linuxmusl-arm64": "0.34.4", + "@img/sharp-linuxmusl-x64": "0.34.4", + "@img/sharp-wasm32": "0.34.4", + "@img/sharp-win32-arm64": "0.34.4", + "@img/sharp-win32-ia32": "0.34.4", + "@img/sharp-win32-x64": "0.34.4" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" @@ -9151,7 +9845,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -9265,19 +9958,8 @@ } ], "license": "MIT", - "optional": true - }, - "node_modules/simple-get": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.1.tgz", - "integrity": "sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==", - "license": "MIT", "optional": true, - "dependencies": { - "decompress-response": "^4.2.0", - "once": "^1.3.1", - "simple-concat": "^1.0.0" - } + "peer": true }, "node_modules/sonner": { "version": "2.0.7", @@ -9343,6 +10025,7 @@ "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "license": "MIT", "optional": true, + "peer": true, "dependencies": { "safe-buffer": "~5.2.0" } @@ -9767,6 +10450,7 @@ "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", "license": "ISC", "optional": true, + "peer": true, "dependencies": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", @@ -9779,12 +10463,24 @@ "node": ">=10" } }, + "node_modules/tar/node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "license": "ISC", + "optional": true, + "peer": true, + "engines": { + "node": ">=10" + } + }, "node_modules/tar/node_modules/minipass": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", "license": "ISC", "optional": true, + "peer": true, "engines": { "node": ">=8" } @@ -9828,6 +10524,12 @@ "node": ">=0.8" } }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "license": "MIT" + }, "node_modules/tinyglobby": { "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", @@ -10276,6 +10978,15 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, "node_modules/web-vitals": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-4.2.4.tgz", @@ -10302,7 +11013,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -10421,6 +11131,7 @@ "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", "license": "ISC", "optional": true, + "peer": true, "dependencies": { "string-width": "^1.0.2 || 2 || 3 || 4" } @@ -10536,7 +11247,8 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "license": "ISC", - "optional": true + "optional": true, + "peer": true }, "node_modules/yargs": { "version": "15.4.1", diff --git a/package.json b/package.json index 2e9ed96..8e48d6c 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "axios": "^1.12.2", "bcryptjs": "^3.0.2", "canvas-confetti": "^1.9.4", + "cloudinary": "^2.8.0", "clsx": "^2.1.1", "cmdk": "^1.1.1", "dotenv": "^17.2.2", @@ -43,13 +44,16 @@ "nprogress": "^0.2.0", "pdf-lib": "^1.17.1", "pdf-parse": "^2.4.5", - "pdfjs-dist": "^3.11.174", + "pdf-to-img": "^5.0.0", + "pdf2pic": "^3.2.0", "posthog-js": "^1.275.1", "posthog-node": "^5.9.5", "qrcode": "^1.5.4", "react": "^18.3.1", "react-dom": "^18.3.1", "react-hook-form": "^7.62.0", + "react-pdf": "^10.2.0", + "sharp": "^0.34.4", "sonner": "^2.0.7", "tailwind-merge": "^2.6.0", "use-debounce": "^10.0.6" diff --git a/poppler-layer/poppler.rpm b/poppler-layer/poppler.rpm new file mode 100644 index 0000000..1becba2 --- /dev/null +++ b/poppler-layer/poppler.rpm @@ -0,0 +1 @@ +404: Not Found \ No newline at end of file diff --git a/signature-real-info.json b/signature-real-info.json index c2cb69e..ab6e3e7 100644 --- a/signature-real-info.json +++ b/signature-real-info.json @@ -1,26 +1,26 @@ { "success": true, "request": { - "id": "0d14754a-740b-42e0-9766-60582e116d09", - "ref": "REAL-1761586268897", + "id": "89a4786c-07e4-4dfa-8fdd-3089dbef5996", + "ref": "REAL-1761590226249", "title": "Contrat CDDU - contrat_cddu_LYXHX3GI_240V001", "status": "pending", - "created_at": "2025-10-27T17:31:09.550025+00:00" + "created_at": "2025-10-27T18:37:09.227226+00:00" }, "signers": [ { - "signerId": "12430034-e696-428a-876e-ba4d35b1ff2c", + "signerId": "43a3a850-74e8-4281-b1ab-e61000304201", "role": "Employeur", "name": "Odentas Paie", "email": "paie@odentas.fr", - "signatureUrl": "https://espace-paie.odentas.fr/signer/0d14754a-740b-42e0-9766-60582e116d09/12430034-e696-428a-876e-ba4d35b1ff2c" + "signatureUrl": "https://espace-paie.odentas.fr/signer/89a4786c-07e4-4dfa-8fdd-3089dbef5996/43a3a850-74e8-4281-b1ab-e61000304201" }, { - "signerId": "1c8914ad-4cfa-40e2-870b-b6e269ba29f3", + "signerId": "d2646860-3d0f-42d6-abdc-c6084e870088", "role": "Salarié", "name": "Renaud Breviere", "email": "renaud.breviere@gmail.com", - "signatureUrl": "https://espace-paie.odentas.fr/signer/0d14754a-740b-42e0-9766-60582e116d09/1c8914ad-4cfa-40e2-870b-b6e269ba29f3" + "signatureUrl": "https://espace-paie.odentas.fr/signer/89a4786c-07e4-4dfa-8fdd-3089dbef5996/d2646860-3d0f-42d6-abdc-c6084e870088" } ] } \ No newline at end of file diff --git a/test-complete-signature-flow.sh b/test-complete-signature-flow.sh index 815dfc3..0ca9a19 100755 --- a/test-complete-signature-flow.sh +++ b/test-complete-signature-flow.sh @@ -10,7 +10,16 @@ echo "" # Créer une nouvelle demande de signature echo "📝 1. Création d'une demande de signature..." -RESPONSE=$(node create-real-signature.js) + +# Utiliser le PDF de test existant +PDF_FILE="contrat_cddu_LYXHX3GI_240V001.pdf" + +if [ ! -f "$PDF_FILE" ]; then + echo "❌ Erreur: Fichier PDF non trouvé: $PDF_FILE" + exit 1 +fi + +RESPONSE=$(node create-real-signature.js "$PDF_FILE") echo "$RESPONSE" # Extraire le request_id et les signer IDs du JSON diff --git a/test-odentas-sign-info.json b/test-odentas-sign-info.json index da64a92..92dbec1 100644 --- a/test-odentas-sign-info.json +++ b/test-odentas-sign-info.json @@ -1,26 +1,26 @@ { "success": true, "request": { - "id": "75b4408d-1bbd-464f-a9ea-2b4e5075a817", - "ref": "TEST-1761582838435", + "id": "4fc9bdf9-eacc-4eed-b713-da40a095c5e7", + "ref": "TEST-1761595009432", "title": "Contrat CDDU - Test Local", "status": "pending", - "created_at": "2025-10-27T16:34:07.361187+00:00" + "created_at": "2025-10-27T19:56:51.428927+00:00" }, "signers": [ { - "signerId": "95c4ccdc-1a26-4426-a56f-653758159b54", + "signerId": "8f0c03c2-0891-4696-9f03-1d5d562cfb9e", "role": "Employeur", "name": "Odentas Paie", "email": "paie@odentas.fr", - "signatureUrl": "https://espace-paie.odentas.fr/signer/75b4408d-1bbd-464f-a9ea-2b4e5075a817/95c4ccdc-1a26-4426-a56f-653758159b54" + "signatureUrl": "https://espace-paie.odentas.fr/signer/4fc9bdf9-eacc-4eed-b713-da40a095c5e7/8f0c03c2-0891-4696-9f03-1d5d562cfb9e" }, { - "signerId": "d481f070-2ac6-4f82-aff3-862783904d5d", + "signerId": "1f162f81-a235-4079-b499-aa9dadbe9eff", "role": "Salarié", "name": "Renaud Breviere", "email": "renaud.breviere@gmail.com", - "signatureUrl": "https://espace-paie.odentas.fr/signer/75b4408d-1bbd-464f-a9ea-2b4e5075a817/d481f070-2ac6-4f82-aff3-862783904d5d" + "signatureUrl": "https://espace-paie.odentas.fr/signer/4fc9bdf9-eacc-4eed-b713-da40a095c5e7/1f162f81-a235-4079-b499-aa9dadbe9eff" } ] } \ No newline at end of file diff --git a/test-odentas-sign.js b/test-odentas-sign.js index da145cc..1587cee 100755 --- a/test-odentas-sign.js +++ b/test-odentas-sign.js @@ -82,23 +82,23 @@ async function main() { positions: [ { role: 'Employeur', - page: 1, - x: 100, - y: 680, - w: 200, + page: 3, + x: 20, + y: 260, + w: 150, h: 60, kind: 'signature', label: 'Signature Employeur', }, { role: 'Salarié', - page: 1, - x: 350, - y: 680, - w: 200, + page: 3, + x: 180, + y: 260, + w: 150, h: 60, kind: 'signature', - label: 'Signature Salarié', + label: 'Signature Employé', }, ], };