feat: fix clouds functions and heygen

This commit is contained in:
2026-09-04 12:22:29 +02:00
parent 7bf913e014
commit efbb7994f2
12 changed files with 276 additions and 75 deletions
+16
View File
@@ -17,6 +17,7 @@ const projectsRef = db.collection('projects')
const REGION = process.env.FIREBASE_REGION || 'europe-west1'
const HEYGEN_BASE_URL = 'https://api.heygen.com/v3'
const HEYGEN_REQUEST_TIMEOUT_MS = 45000
const PLAYBACK_STATUS = {
GENERATING: 'GENERATING',
DRAFT_READY: 'DRAFT_READY',
@@ -120,6 +121,7 @@ const findProjectForWebhook = async ({ callbackId, videoId }) => {
const fetchCanonicalVideo = async (videoId) => {
const response = await axios.get(`${HEYGEN_BASE_URL}/videos/${videoId}`, {
headers: getHeygenHeaders(),
timeout: HEYGEN_REQUEST_TIMEOUT_MS,
})
return response?.data?.data || null
@@ -176,6 +178,13 @@ exports.startPlaybackGeneration = onCall(
const callbackUrl = buildCallbackUrl()
const title = trimString(project?.title) || `Playback ${projectId}`
logger.info('[HeyGen] startPlaybackGeneration request', {
projectId,
uid,
hasSongUrl: Boolean(songUrl),
hasPhotoUrl: Boolean(photoUrl),
})
await projectRef.set(
{
playbackProvider: 'heygen',
@@ -211,6 +220,7 @@ exports.startPlaybackGeneration = onCall(
},
{
headers: getHeygenHeaders(),
timeout: HEYGEN_REQUEST_TIMEOUT_MS,
}
)
@@ -227,6 +237,12 @@ exports.startPlaybackGeneration = onCall(
{ merge: true }
)
logger.info('[HeyGen] generation accepted', {
projectId,
uid,
videoId,
})
return {
jobId: videoId,
callbackId,
+3 -3
View File
@@ -204,9 +204,9 @@ exports.generateLyrics = onCall(LYRICS_FUNCTION_OPTIONS, async ({ auth = {}, dat
}
} catch (moderationError) {
if (moderationError instanceof HttpsError) throw moderationError
console.error('⚠️ Moderation check error (fail open)', moderationError)
// On continue si l'appel modération fail (fail open) ou on throw (fail closed) selon ta politique.
// Ici je fail closed par sécurité pour une app publique.
console.error('⚠️ Moderation check error (fail closed)', moderationError)
// La modération essaie plusieurs modèles stables avant d'arriver ici.
// Si tous sont indisponibles, on bloque par sécurité pour une app publique.
throw new HttpsError('internal', 'Vérification de sécurité indisponible.')
}
+19 -11
View File
@@ -2,7 +2,7 @@ const { onCall, onRequest, HttpsError } = require('firebase-functions/v2/https')
const axios = require('axios')
const admin = require('firebase-admin')
const { FieldValue } = require('firebase-admin/firestore')
const { logger } = require('firebase-functions/logger')
const logger = require('firebase-functions/logger')
const { pipeline } = require('stream/promises')
const { randomUUID } = require('crypto')
const { ALERT_TYPE, refList } = require('../index')
@@ -22,6 +22,12 @@ const MUSIC_REFUND_SOURCE = 'music_generation_refund'
const SUNO_LYRICS_PROMPT_MAX_LENGTH = 5000
const SUNO_TITLE_MAX_LENGTH = 100
const getSunoAuthorizationHeader = () => {
const apiKey = String(SUNO_API_KEY.value() || '').trim()
if (!apiKey) throw new Error('SUNO_API_KEY_EMPTY')
return `Bearer ${apiKey}`
}
// ==========================================
// 1. DICTIONNAIRES DE TRADUCTION (FRONT -> SUNO)
// ==========================================
@@ -409,14 +415,16 @@ async function markProjectMusicFailure(projectId, error) {
reason: sunoMessage,
})
}
await docRef.set(
{
musicStatus: 'FAILED',
musicError: errorPayload,
updatedAt: FieldValue.serverTimestamp(),
},
{ merge: true }
)
const failureUpdate = {
musicStatus: 'FAILED',
musicError: errorPayload,
updatedAt: FieldValue.serverTimestamp(),
}
if (refundResult?.orderId) {
failureUpdate.musicCreditsRefunded = true
failureUpdate.musicCreditsRefundOrderId = refundResult.orderId
}
await docRef.set(failureUpdate, { merge: true })
// (Notification logic here...)
} catch (err) {
console.error('Error marking failure', err)
@@ -592,7 +600,7 @@ exports.generateMusic = onCall({ secrets: [SUNO_API_KEY] }, async ({ data = {} }
const response = await axios.post(`${SUNO_API_BASE}${SUNO_API_PATH}`, payload, {
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${SUNO_API_KEY.value()}`,
Authorization: getSunoAuthorizationHeader(),
},
})
@@ -626,7 +634,7 @@ exports.getSunoStatus = onCall({ secrets: [SUNO_API_KEY] }, async ({ data = {} }
if (!taskId) throw new Error('TaskId manquant')
try {
const response = await axios.get(`${SUNO_API_BASE}${SUNO_STATUS_PATH}?taskId=${taskId}`, {
headers: { Authorization: `Bearer ${SUNO_API_KEY.value()}` },
headers: { Authorization: getSunoAuthorizationHeader() },
timeout: 30000,
})
return {