Submission Processor
Type de ressource : Cloudflare Worker (Microservice FaaS / Queue Consumer)
Fichier source :services/submission-processor/src/index.ts
Statut : Actif
1. Vue d’ensemble
Section titled “1. Vue d’ensemble”1.1. Description
Section titled “1.1. Description”Worker Cloudflare qui consomme les messages de la Queue submissions-queue et crée des documents dans Sanity. Traite les soumissions de formulaire de manière asynchrone via un Queue consumer. Inclut un endpoint de test direct (/test-direct) pour le développement local.
1.2. Objectif
Section titled “1.2. Objectif”- Consommer les soumissions de formulaire depuis la Queue Cloudflare
- Créer des documents Sanity pour chaque soumission
- Fournir un endpoint de test direct pour le développement local
- Gérer les erreurs avec retry exponentiel
1.3. Caractéristiques
Section titled “1.3. Caractéristiques”| Propriété | Valeur |
|---|---|
| Type | Microservice métier / Queue Consumer |
| Trigger | Queue consumer, HTTP fetch |
| Route publique | /health, /test-direct |
| Environnements | Development, Production |
| Consommateurs | Form-handler (producer), Sanity CMS |
2. Architecture et flux
Section titled “2. Architecture et flux”[Entrée : Queue Cloudflare] │ • Message de soumission ▼[Submission-processor Worker] │ • Queue consumer │ • Initialisation du client Sanity │ • Création du document Sanity ▼[Sanity CMS] │ • Document submission créé ▼[Confirmation (ack/retry)]2.2. Positionnement dans la chaîne Edge
Section titled “2.2. Positionnement dans la chaîne Edge”[Client] │ ▼[Astro Frontend] │ ▼[Form Handler Worker] │ • Producer Queue ▼[Queue Cloudflare] │ ▼[Submission-processor Worker] │ • Consumer Queue ▼[Sanity CMS]2.3. Composants impliqués
Section titled “2.3. Composants impliqués”| Composant | Rôle | Type |
|---|---|---|
| SUBMISSIONS_QUEUE | Queue asynchrone | Cloudflare Binding |
| Sanity Client | Client API Sanity | Bibliothèque (@sanity/client) |
| Sanity API | Stockage des soumissions | Service tiers |
| Form-handler | Producer de la Queue | Cloudflare Worker |
3. Configuration
Section titled “3. Configuration”3.1. Variables d’environnement
Section titled “3.1. Variables d’environnement”| Variable | Description | Obligatoire | Valeur par défaut |
|---|---|---|---|
ENVIRONMENT |
Environnement (dev/production) | ✅ | dev |
SANITY_PROJECT_ID |
ID du projet Sanity | ✅ | Injecté via secret |
SANITY_DATASET |
Nom du dataset Sanity | ✅ | Injecté via secret |
SANITY_API_WRITE_TOKEN |
Token d’écriture Sanity | ✅ | Injecté via secret |
3.2. Bindings Cloudflare
Section titled “3.2. Bindings Cloudflare”| Binding | Type | Nom | Description |
|---|---|---|---|
| SUBMISSIONS_QUEUE | Queue Consumer | SUBMISSIONS_QUEUE | Queue pour les soumissions |
3.3. Configuration Wrangler (wrangler.toml)
Section titled “3.3. Configuration Wrangler (wrangler.toml)”name = "amn-submission-processor"main = "src/index.ts"compatibility_date = "2025-07-18"compatibility_flags = ["nodejs_compat"]
[vars]ENVIRONMENT = "dev"
[env.dev]name = "amn-submission-processor-dev"vars = { ENVIRONMENT = "dev" }
[[env.dev.queues.consumers]]queue = "submissions-queue-dev"max_batch_size = 10max_batch_timeout = 30
[env.production]name = "amn-submission-processor-prod"vars = { ENVIRONMENT = "production" }
[[env.production.queues.consumers]]queue = "submissions-queue-prod"max_batch_size = 10max_batch_timeout = 303.4. Injection des secrets
Section titled “3.4. Injection des secrets”# Injection des secrets via Wrangler CLIwrangler secret put SANITY_PROJECT_ID --env productionwrangler secret put SANITY_DATASET --env productionwrangler secret put SANITY_API_WRITE_TOKEN --env productionou via le script @inject_secret_to_cloudflare.sh du projet.
4. Implémentation
Section titled “4. Implémentation”4.1. Code de production (src/index.ts)
Section titled “4.1. Code de production (src/index.ts)”import { createClient } from '@sanity/client'
export interface Env { ENVIRONMENT: string; SANITY_PROJECT_ID: string; SANITY_DATASET: string; SANITY_API_WRITE_TOKEN?: string;}
export interface SubmissionMessage { id: string; name: string; email: string; message: string; submittedAt: string; environment: string;}
export default { async fetch(request: Request, env: Env): Promise<Response> { const url = new URL(request.url);
if (url.pathname === '/health' && request.method === 'GET') { return new Response(JSON.stringify({ status: 'healthy', service: 'submission-processor', environment: env.ENVIRONMENT ?? 'dev', sanityProjectIdPrefix: env.SANITY_PROJECT_ID ? env.SANITY_PROJECT_ID.slice(0, 4) + '…' : 'NOT SET', sanityDataset: env.SANITY_DATASET ?? 'NOT SET', timestamp: new Date().toISOString(), }), { status: 200, headers: { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', }, }); }
// Test endpoint for direct Sanity integration (bypasses Queue for local testing) if (url.pathname === '/test-direct' && request.method === 'POST') { try { const data = await request.json() as { name: string; email: string; message: string };
const sanityClient = createClient({ projectId: env.SANITY_PROJECT_ID, dataset: env.SANITY_DATASET, apiVersion: '2024-01-01', token: env.SANITY_API_WRITE_TOKEN, useCdn: false, });
const result = await sanityClient.create({ _type: 'submission', name: data.name, email: data.email, message: data.message, status: 'pending', submittedAt: new Date().toISOString(), processedAt: new Date().toISOString(), });
return new Response(JSON.stringify({ success: true, sanityId: result._id, message: 'Direct test: submission stored in Sanity' }), { headers: { 'Content-Type': 'application/json' }, }); } catch (error) { console.error('Direct test error:', error); const errorMessage = error instanceof Error ? error.message : 'Unknown error'; return new Response(JSON.stringify({ success: false, error: errorMessage, }), { status: 500, headers: { 'Content-Type': 'application/json' }, }); } }
if (request.method === 'OPTIONS') { return new Response(null, { status: 204, headers: { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS', 'Access-Control-Allow-Headers': 'Content-Type', }, }); }
return new Response(JSON.stringify({ error: 'Not Found', hint: 'Try GET /health or POST /test-direct for direct Sanity testing' }), { status: 404, headers: { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', }, }); },
async queue(batch: MessageBatch<SubmissionMessage>, env: Env): Promise<void> { console.log(`Processing batch of ${batch.messages.length} submissions`);
// Initialize Sanity client const sanityClient = createClient({ projectId: env.SANITY_PROJECT_ID, dataset: env.SANITY_DATASET, apiVersion: '2024-01-01', token: env.SANITY_API_WRITE_TOKEN, useCdn: false, });
for (const message of batch.messages) { try { const submission = message.body as SubmissionMessage; console.log(`Processing submission ${submission.id} from ${submission.email}`);
// Create document in Sanity const result = await sanityClient.create({ _type: 'submission', name: submission.name, email: submission.email, message: submission.message, status: 'pending', submittedAt: submission.submittedAt, processedAt: new Date().toISOString(), });
console.log(`Successfully stored submission ${submission.id} in Sanity with ID: ${result._id}`);
// Acknowledge message message.ack();
} catch (error) { console.error(`Failed to process submission:`, error);
// Retry with exponential backoff message.retry({ delaySeconds: Math.min(60, Math.pow(2, batch.messages.indexOf(message))), }); } } },};4.2. Interfaces et types
Section titled “4.2. Interfaces et types”// Interface des bindingsexport interface Env { ENVIRONMENT: string; SANITY_PROJECT_ID: string; SANITY_DATASET: string; SANITY_API_WRITE_TOKEN?: string;}
// Type de message de soumissionexport interface SubmissionMessage { id: string; name: string; email: string; message: string; submittedAt: string; environment: string;}5. Sécurité
Section titled “5. Sécurité”5.1. Mécanismes de sécurité
Section titled “5.1. Mécanismes de sécurité”| Mécanisme | Implémentation | Rationale |
|---|---|---|
| Secrets injectés | Secrets Sanity via wrangler secret put | Protection des credentials |
| CORS | Headers CORS permissifs | Permettre les requêtes cross-origin |
| Masquage de secrets | Prefixe du project ID masqué dans /health | Protection contre l’exposition |
5.2. Validation des entrées
Section titled “5.2. Validation des entrées”Non applicable - les données sont déjà validées par form-handler.
5.3. Gestion des secrets
Section titled “5.3. Gestion des secrets”- ✅ Les secrets ne sont jamais dans le code source
- ✅ Injection via
wrangler secret putou@inject_secret_to_cloudflare.sh - ✅ Rotation régulière des secrets (tous les 90 jours recommandé)
6. Résilience et gestion des erreurs
Section titled “6. Résilience et gestion des erreurs”6.1. Stratégie de retry
Section titled “6.1. Stratégie de retry”| Scénario | Stratégie | Rationale |
|---|---|---|
| Sanity API error | Retry exponentiel (max 60s) | Tolérance aux pannes temporaires |
| Sanity API success | Ack message | Confirmation du traitement |
6.2. Safety Locks
Section titled “6.2. Safety Locks”Non implémenté - pas de lock configuré.
6.3. Gestion des timeouts
Section titled “6.3. Gestion des timeouts”- ✅ Timeout global : Dépend de Cloudflare (30s Free / 50s Paid)
- ✅ Timeout par service tiers : Dépend de Cloudflare
- ✅ Retry exponentiel pour les erreurs Sanity
7. Performance et limites
Section titled “7. Performance et limites”7.1. Limites Cloudflare Workers
Section titled “7.1. Limites Cloudflare Workers”| Limite | Valeur | Stratégie d’optimisation |
|---|---|---|
| Mémoire RAM | 128 MB | Pas de traitement lourd en mémoire |
| CPU time | 30s (Free) / 50s (Paid) | Batch size limité à 10 |
| Nombre de sous-requêtes | 50 | Limité par batch size |
| Taille requête/réponse | 100 MB | Payload de soumission limité |
7.2. Optimisations appliquées
Section titled “7.2. Optimisations appliquées”- ✅ Traitement par batch (max 10 messages)
- ✅ Retry exponentiel pour les erreurs
- ✅ Endpoint de test direct pour le développement local
8. Monitoring et observabilité
Section titled “8. Monitoring et observabilité”8.1. Logs structurés
Section titled “8.1. Logs structurés”// Logs console.log basiquesconsole.log(`Processing batch of ${batch.messages.length} submissions`);console.log(`Processing submission ${submission.id} from ${submission.email}`);console.log(`Successfully stored submission ${submission.id} in Sanity with ID: ${result._id}`);console.error(`Failed to process submission:`, error);8.2. Métriques clés
Section titled “8.2. Métriques clés”Non implémenté - aucune métrique ou alerte n’est configurée.
8.3. Commandes de debug
Section titled “8.3. Commandes de debug”# Logs en temps réelwrangler tail submission-processor
# Vérification des bindingswrangler deployments list
# Test de l'endpoint healthcurl http://localhost:8789/health
# Test de l'endpoint test-directcurl -X POST http://localhost:8789/test-direct \ -H "Content-Type: application/json" \ -d '{"name":"Test","email":"test@example.com","message":"Test message"}'9. Déploiement
Section titled “9. Déploiement”9.1. Pipeline de déploiement
Section titled “9.1. Pipeline de déploiement”# Buildnpm run build --workspace=services/submission-processor
# Déploiement stagingwrangler deploy --env staging
# Déploiement productionwrangler deploy --env production9.2. Rollback
Section titled “9.2. Rollback”# Liste des déploiementswrangler deployments list
# Rollback vers une version précédentewrangler deployments rollback --deployment-id=[id]9.3. Tests manuels
Section titled “9.3. Tests manuels”# Test HTTP (health check)curl http://localhost:8789/health
# Test HTTP (test-direct)curl -X POST http://localhost:8789/test-direct \ -H "Content-Type: application/json" \ -d '{"name":"Test","email":"test@example.com","message":"Test message"}'
# Test Queue (via wrangler)wrangler queues tail submissions-queue-dev10. Débogage et troubleshooting
Section titled “10. Débogage et troubleshooting”10.1. Problèmes courants
Section titled “10.1. Problèmes courants”| Symptôme | Cause probable | Solution |
|---|---|---|
| Sanity API error | Token invalide ou manquant | Vérifier les secrets injectés |
| Queue non consommée | Binding mal configuré | Vérifier wrangler.toml |
| Test-direct échoue | Credentials Sanity manquants | Vérifier .env local |
10.2. Erreurs fréquentes
Section titled “10.2. Erreurs fréquentes”| Code HTTP / Erreur | Signification | Action |
|---|---|---|
| 500 | Erreur Sanity API | Vérifier les logs et les credentials |
| 404 | Endpoint inexistant | Vérifier le pathname |
11. Bonnes pratiques
Section titled “11. Bonnes pratiques”11.1. Architecture
Section titled “11.1. Architecture”- ✅ Découpler le traitement de la validation
- ✅ Utiliser les bindings Cloudflare plutôt que des appels HTTP internes
- ✅ Traitement par batch pour optimiser les performances
11.2. Code
Section titled “11.2. Code”- ✅ Typer strictement les bindings et les réponses
- ✅ Gérer explicitement les erreurs (try/catch)
- ✅ Logger les erreurs et les succès
- ✅ Endpoint de test direct pour le développement local
11.3. Sécurité
Section titled “11.3. Sécurité”- ✅ Ne jamais exposer les secrets côté client
- ✅ Masquer les secrets dans les logs
- ✅ Utiliser
@inject_secret_to_cloudflare.shpour l’injection
11.4. Performance
Section titled “11.4. Performance”- ✅ Traitement par batch (max 10 messages)
- ✅ Retry exponentiel pour les erreurs
- ✅ Timeout de batch (30s)
12. Évolutions et roadmap
Section titled “12. Évolutions et roadmap”12.1. Améliorations planifiées
Section titled “12.1. Améliorations planifiées”- Configuration de Logpush pour les logs
- Ajout de métriques et alertes
- Ajout de structured logging (JSON)
- Amélioration de la gestion des erreurs (DLQ)
12.2. Limitations connues
Section titled “12.2. Limitations connues”- ❌ CORS permissif (
*) - à restreindre pour la production - ❌ Pas de Dead Letter Queue (DLQ)
- ❌ Pas de logs structurés
- ❌ Pas de monitoring configuré
- ⚠️ Endpoint
/test-directà désactiver en production
13. Ressources liées
Section titled “13. Ressources liées”- Documentation Cloudflare Workers : Getting Started
- Documentation Cloudflare Queues : Queues
- Documentation Sanity : Client API
- Workers associés :
- form-handler
- Fichiers concernés :
services/submission-processor/src/index.tsservices/submission-processor/wrangler.tomlservices/submission-processor/package.json@inject_secret_to_cloudflare.sh
14. Historique des modifications
Section titled “14. Historique des modifications”| Date | Version | Description | Auteur |
|---|---|---|---|
| 2026-09-15 | 1.0 | Création initiale | Devin |