feat: fix clouds functions and heygen
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user