functions

This commit is contained in:
Thomas Demirdjian
2025-11-03 13:47:07 +01:00
parent 460c91f1e6
commit 7b3a47090d
16 changed files with 23868 additions and 2399 deletions
+95 -22
View File
@@ -1,7 +1,13 @@
const { onCall, onRequest, HttpsError } = require("firebase-functions/v2/https");
const {
onCall,
onRequest,
HttpsError,
} = require("firebase-functions/v2/https");
const axios = require("axios");
const admin = require("firebase-admin");
const { logger } = require("firebase-functions/logger");
const { ALERT_TYPE, refList } = require("../index");
const { sendNotification } = require("./notifications");
const { SUNO_API_KEY } = require("../config/keys");
const {
SUNO_MODEL,
@@ -11,9 +17,6 @@ const {
SUNO_STATUS_PATH,
} = require("../config/suno");
// Initialiser Firestorey
const db = admin.firestore();
/**
* Marque un projet comme échoué suite à une erreur Suno
* @param {string} projectId - Identifiant du projet
@@ -22,7 +25,9 @@ const db = admin.firestore();
async function markProjectMusicFailure(projectId, error) {
if (!projectId) return;
try {
const docRef = db.collection("projects").doc(projectId);
const docRef = refList.projects.doc(projectId);
const projectSnap = await docRef.get();
const projectData = projectSnap?.data() || {};
const status = error?.response?.status || error?.status || null;
const sunoMessage =
error?.response?.data?.msg ||
@@ -44,12 +49,44 @@ async function markProjectMusicFailure(projectId, error) {
musicError: errorPayload,
updatedAt: admin.firestore.FieldValue.serverTimestamp(),
},
{ merge: true }
{ merge: true },
);
const receiverId =
typeof projectData?.userId === "string" && projectData.userId.trim()
? projectData.userId.trim()
: null;
if (receiverId) {
const projectTitle =
typeof projectData?.title === "string" && projectData.title.trim()
? projectData.title.trim()
: "ton projet";
const message = `La génération de musique pour "${projectTitle}" a échoué.`;
try {
await sendNotification({
sender: "SYSTEM",
receiver: receiverId,
receiverCollection: "users",
title: "Génération de musique échouée",
message,
data: {
type: ALERT_TYPE?.MUSIC_GENERATION_FAILED,
projectId,
projectTitle,
error: errorPayload,
},
});
} catch (notifyError) {
console.error(
"[markProjectMusicFailure] Failed to send notification:",
notifyError,
);
}
}
} catch (err) {
console.error(
"❌ [generateMusic] Impossible de marquer le projet en erreur:",
err
err,
);
}
}
@@ -80,7 +117,7 @@ function extractVoiceStrings(voice) {
}
if (typeof voice === "object") {
return Object.values(voice).filter(
(v) => typeof v === "string" && v.trim()
(v) => typeof v === "string" && v.trim(),
);
}
return [];
@@ -134,7 +171,7 @@ function detectVocalGender(voiceInput = "") {
if (Array.isArray(voiceInput)) {
const baseItem = voiceInput.find(
(v) =>
v && (v.category === "base" || v?.category?.toLowerCase() === "base")
v && (v.category === "base" || v?.category?.toLowerCase() === "base"),
);
const text = baseItem ? baseItem.value || baseItem.text || baseItem : null;
if (text) return detectVocalGender(text);
@@ -346,7 +383,7 @@ exports.generateMusic = onCall(async ({ data = {} }) => {
"Content-Type": "application/json",
Authorization: `Bearer ${SUNO_API_KEY}`,
},
}
},
);
const parsed = response.data;
@@ -370,7 +407,7 @@ exports.generateMusic = onCall(async ({ data = {} }) => {
} catch (markErr) {
console.error(
"❌ Erreur lors de la mise à jour du statut de projet:",
markErr
markErr,
);
}
@@ -412,7 +449,7 @@ exports.getSunoStatus = onCall(async ({ data = {} }) => {
Authorization: `Bearer ${SUNO_API_KEY}`,
},
timeout: 30000, // 30 secondes de timeout
}
},
);
parsed = response.data;
@@ -547,16 +584,24 @@ exports.sunoCallback = onRequest({ methods: ["POST"] }, async (req, res) => {
// 1) Récupérer le projectId associé au taskId
let projectId = null;
const projSnap = await db
.collection("projects")
const projSnap = await refList.projects
.where("sunoTaskId", "==", taskId)
.limit(1)
.get();
if (projSnap.empty) {
throw new Error("Aucun projet trouvé pour ce taskId");
}
projectId = projSnap.docs[0].id;
const { userId } = projSnap.docs[0].data() || {};
const projectDoc = projSnap.docs[0];
const projectData = projectDoc?.data() || {};
projectId = projectDoc.id;
const userId =
typeof projectData.userId === "string" && projectData.userId.trim()
? projectData.userId.trim()
: null;
const projectTitle =
typeof projectData.title === "string" && projectData.title.trim()
? projectData.title.trim()
: "ton projet";
if (!projectId) {
console.warn("⚠️ [SunoCallback] Aucun projet trouvé pour", { taskId });
@@ -567,7 +612,7 @@ exports.sunoCallback = onRequest({ methods: ["POST"] }, async (req, res) => {
const audioUrls = tracks
.map(
(t) =>
t.audio_url || t.audioUrl || t.stream_audio_url || t.streamAudioUrl
t.audio_url || t.audioUrl || t.stream_audio_url || t.streamAudioUrl,
)
.filter(Boolean)
.slice(0, 2);
@@ -582,7 +627,7 @@ exports.sunoCallback = onRequest({ methods: ["POST"] }, async (req, res) => {
has_audio_url: !!t.audio_url,
has_stream_audio_url: !!t.stream_audio_url,
})),
}
},
);
}
@@ -607,14 +652,14 @@ exports.sunoCallback = onRequest({ methods: ["POST"] }, async (req, res) => {
},
});
const downloadUrl = `https://firebasestorage.googleapis.com/v0/b/${bucket.name}/o/${encodeURIComponent(
path
path,
)}?alt=media&token=${token}`;
console.log("✅ [SunoCallback] Sauvegardé:", path, "URL:", downloadUrl);
return { path, url: downloadUrl };
} catch (e) {
console.error(
`❌ [SunoCallback] Échec save piste ${index + 1}:`,
e.message
e.message,
);
return null;
}
@@ -628,19 +673,47 @@ exports.sunoCallback = onRequest({ methods: ["POST"] }, async (req, res) => {
// 4) Mettre à jour le statut du projet
try {
const musicUrls = [p1?.url, p2?.url].filter(Boolean);
await db.collection("projects").doc(projectId).set(
await refList.projects.doc(projectId).set(
{
musicStatus: "GENERATED",
musicUrls,
musicError: admin.firestore.FieldValue.delete(),
updatedAt: admin.firestore.FieldValue.serverTimestamp(),
},
{ merge: true }
{ merge: true },
);
console.log("🏷️ [SunoCallback] Projet marqué GENERATED", {
projectId,
musicUrlsCount: musicUrls.length,
});
if (userId) {
const successMessage =
musicUrls.length > 0
? `Ta musique pour "${projectTitle}" est prête.`
: `La génération de musique pour "${projectTitle}" est terminée.`;
try {
await sendNotification({
sender: "SYSTEM",
receiver: userId,
receiverCollection: "users",
title: "Musique prête",
message: successMessage,
data: {
type: ALERT_TYPE?.MUSIC_GENERATION_SUCCESS,
projectId,
projectTitle,
musicUrls,
taskId,
},
});
} catch (notifError) {
console.error(
"[sunoCallback] Failed to send success notification:",
notifError,
);
}
}
} catch (e) {
console.error("❌ [SunoCallback] Erreur maj projet:", e.message);
}