Aller au contenu principal

Communication entre Services

Vue d'ensemble

Les services communiquent via plusieurs protocoles :

SourceDestinationProtocoleUsage
FrontendSupabaseREST/WebSocketCRUD, Auth, Realtime
FrontendGeneratorRESTGénération vidéo
GeneratorSupabaseRESTAccès Storage
SupabaseFrontendWebhookNotifications

Frontend ↔ Supabase

Client SDK

import { createClient } from '@supabase/supabase-js';

const supabase = createClient(
'https://xxx.supabase.co',
'eyJ...' // Anon key
);

Authentification

// Inscription
const { data, error } = await supabase.auth.signUp({
email: 'user@example.com',
password: 'password'
});

// Connexion
const { data, error } = await supabase.auth.signInWithPassword({
email: 'user@example.com',
password: 'password'
});

// Session
const { data: { session } } = await supabase.auth.getSession();

Requêtes Base de Données

// SELECT
const { data } = await supabase
.from('product')
.select('*, product_items(*)')
.eq('id', productId);

// INSERT
const { data } = await supabase
.from('product')
.insert({ name: 'Mon événement' })
.select();

// UPDATE
const { data } = await supabase
.from('product')
.update({ close: true })
.eq('id', productId);

// DELETE
await supabase
.from('product_items')
.delete()
.eq('id', itemId);

Storage

// Upload
const { data, error } = await supabase.storage
.from('video_dedicace')
.upload(path, file, {
cacheControl: '30',
contentType: 'video/webm'
});

// Download (URL signée)
const { data } = await supabase.storage
.from('video_dedicace')
.createSignedUrl(path, 3600);

// URL publique
const { data } = supabase.storage
.from('video_dedicace')
.getPublicUrl(path);

Edge Functions

// Appel d'une Edge Function
const { data, error } = await supabase.functions.invoke('invite-participate', {
body: {
productId: 123,
emails: ['user@example.com']
}
});

Realtime (optionnel)

// Subscription aux changements
const channel = supabase
.channel('product-items')
.on(
'postgres_changes',
{
event: 'INSERT',
schema: 'public',
table: 'product_items',
filter: `product_id=eq.${productId}`
},
(payload) => {
console.log('Nouveau message:', payload.new);
}
)
.subscribe();

// Désabonnement
channel.unsubscribe();

Frontend ↔ Video Generator

Création de Présentation

// Service Angular
async createPresentation(productId: number): Promise<string> {
const product = await this.getProductWithItems(productId);

const pages = product.items.map(item => ({
id: item.id,
type: item.message_type.toLowerCase(),
content: item.message_type === 'TEXT'
? item.message_media
: await this.getSignedVideoUrl(item.message_media),
author: item.participant_name
}));

const response = await fetch('https://api.momentscollectifs.fr/create_presentation', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
title: product.name,
description: product.description,
pages,
webhook_url: `${environment.apiUrl}/webhook/presentation`
})
});

const { presentationId } = await response.json();
return presentationId;
}

Téléchargement

// Téléchargement direct
downloadPresentation(presentationId: string, format: 'desktop' | 'mobile') {
const filename = `${format}.mp4`;
const url = `https://api.momentscollectifs.fr/download/${presentationId}/${filename}`;

// Téléchargement via lien
const link = document.createElement('a');
link.href = url;
link.download = `presentation_${format}.mp4`;
link.click();
}

Video Generator ↔ Supabase

Accès aux Vidéos

Le générateur utilise une service key pour accéder aux fichiers privés.

// main.js
const { createClient } = require('@supabase/supabase-js');

const supabase = createClient(
process.env.SUPABASE_URL,
process.env.SUPABASE_SERVICE_KEY // Clé service (admin)
);

async function downloadVideo(path) {
// Créer URL signée
const { data, error } = await supabase.storage
.from('video_dedicace')
.createSignedUrl(path, 3600);

if (error) throw error;

// Télécharger
const response = await axios.get(data.signedUrl, {
responseType: 'arraybuffer'
});

return response.data;
}

Webhooks

Stripe → Supabase

Stripe envoie des webhooks lors des paiements.

// Edge Function: stripe-webhook
Deno.serve(async (req) => {
const signature = req.headers.get('stripe-signature');
const body = await req.text();

// Vérifier la signature
const event = stripe.webhooks.constructEvent(
body,
signature,
STRIPE_WEBHOOK_SECRET
);

if (event.type === 'checkout.session.completed') {
const session = event.data.object;

// Mettre à jour le produit
await supabaseAdmin
.from('product')
.update({
plan: 'basic',
session_id: session.id
})
.eq('id', session.metadata.product_id);
}

return new Response('OK');
});

Generator → Frontend

Le générateur notifie via webhook quand la vidéo est prête.

// Generator: notification
async function notifyCompletion(webhookUrl, presentationId, status) {
await axios.post(webhookUrl, {
event: 'presentation.completed',
data: {
presentationId,
status,
downloadUrls: {
desktop: `/download/${presentationId}/desktop.mp4`,
mobile: `/download/${presentationId}/mobile.mp4`
}
}
});
}

CORS Configuration

Video Generator

// main.js
const cors = require('cors');

app.use(cors({
origin: [
'https://app.momentscollectifs.fr',
'http://localhost:4200',
'http://localhost:3000'
],
methods: ['GET', 'POST'],
allowedHeaders: ['Content-Type', 'Authorization']
}));

Supabase Edge Functions

// Edge Function
const corsHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
};

Deno.serve(async (req) => {
// Handle CORS preflight
if (req.method === 'OPTIONS') {
return new Response('ok', { headers: corsHeaders });
}

// ... logique
return new Response(JSON.stringify(data), {
headers: { ...corsHeaders, 'Content-Type': 'application/json' }
});
});

Gestion des Erreurs

Frontend

// Intercepteur HTTP Angular
@Injectable()
export class ErrorInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(req).pipe(
catchError((error: HttpErrorResponse) => {
if (error.status === 401) {
// Déconnexion automatique
this.authService.logout();
}

if (error.status >= 500) {
// Notification erreur serveur
this.toast.error('Erreur serveur, veuillez réessayer');
}

return throwError(() => error);
})
);
}
}

Retry avec Backoff

// Service avec retry
async fetchWithRetry(url: string, options: RequestInit, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
const response = await fetch(url, options);
if (response.ok) return response;

if (response.status >= 500) {
// Attendre avant retry (exponential backoff)
await this.delay(Math.pow(2, i) * 1000);
continue;
}

throw new Error(`HTTP ${response.status}`);
} catch (error) {
if (i === maxRetries - 1) throw error;
await this.delay(Math.pow(2, i) * 1000);
}
}
}

Timeouts

ServiceOpérationTimeout
FrontendAPI calls30s
GeneratorVideo download120s
GeneratorFFmpeg process600s
SupabaseEdge Functions60s
SupabaseStorage upload120s