manage errors

This commit is contained in:
2025-10-20 11:37:06 +02:00
parent aefbffd480
commit 3a57e4c437
3 changed files with 158 additions and 26 deletions
+70 -12
View File
@@ -1,4 +1,4 @@
const { onCall, onRequest } = 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");
@@ -14,6 +14,46 @@ const {
// Initialiser Firestorey
const db = admin.firestore();
/**
* Marque un projet comme échoué suite à une erreur Suno
* @param {string} projectId - Identifiant du projet
* @param {Object} error - Erreur capturée (axios)
*/
async function markProjectMusicFailure(projectId, error) {
if (!projectId) return;
try {
const docRef = db.collection("projects").doc(projectId);
const status = error?.response?.status || error?.status || null;
const sunoMessage =
error?.response?.data?.msg ||
error?.response?.data?.message ||
error?.message ||
"Erreur lors de la génération de musique";
const errorPayload = {
source: "SUNO_API",
message: sunoMessage,
};
if (status) errorPayload.status = status;
if (error?.code) errorPayload.code = error.code;
await docRef.set(
{
musicStatus: "FAILED",
sunoTaskId: admin.firestore.FieldValue.delete(),
generationStartAt: admin.firestore.FieldValue.delete(),
musicError: errorPayload,
updatedAt: admin.firestore.FieldValue.serverTimestamp(),
},
{ merge: true }
);
} catch (err) {
console.error(
"❌ [generateMusic] Impossible de marquer le projet en erreur:",
err
);
}
}
/**
* Limite la longueur d'une chaîne
* @param {string} str - La chaîne à limiter
@@ -40,7 +80,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 [];
@@ -94,7 +134,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);
@@ -306,7 +346,7 @@ exports.generateMusic = onCall(async ({ data = {} }) => {
"Content-Type": "application/json",
Authorization: `Bearer ${SUNO_API_KEY}`,
},
},
}
);
const parsed = response.data;
@@ -325,10 +365,27 @@ exports.generateMusic = onCall(async ({ data = {} }) => {
};
} catch (error) {
console.error("❌ Erreur lors de la génération de musique:", error);
throw new Error(
`Erreur lors de la génération de musique: ${error.message}`,
try {
await markProjectMusicFailure(data?.projectId, error);
} catch (markErr) {
console.error(
"❌ Erreur lors de la mise à jour du statut de projet:",
markErr
);
}
const status = error?.response?.status || "INTERNAL_ERROR";
const message =
error?.response?.data?.msg ||
error?.response?.data?.message ||
error?.message ||
"Erreur lors de la génération de musique avec Suno";
throw new HttpsError("internal", message, {
status,
source: "SUNO_API",
});
}
});
/**
@@ -355,7 +412,7 @@ exports.getSunoStatus = onCall(async ({ data = {} }) => {
Authorization: `Bearer ${SUNO_API_KEY}`,
},
timeout: 30000, // 30 secondes de timeout
},
}
);
parsed = response.data;
@@ -510,7 +567,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);
@@ -525,7 +582,7 @@ exports.sunoCallback = onRequest({ methods: ["POST"] }, async (req, res) => {
has_audio_url: !!t.audio_url,
has_stream_audio_url: !!t.stream_audio_url,
})),
},
}
);
}
@@ -550,14 +607,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;
}
@@ -575,9 +632,10 @@ exports.sunoCallback = onRequest({ methods: ["POST"] }, async (req, res) => {
{
musicStatus: "GENERATED",
musicUrls,
musicError: admin.firestore.FieldValue.delete(),
updatedAt: admin.firestore.FieldValue.serverTimestamp(),
},
{ merge: true },
{ merge: true }
);
console.log("🏷️ [SunoCallback] Projet marqué GENERATED", {
projectId,
+47 -12
View File
@@ -1,18 +1,19 @@
import { View, Text, Image, Pressable, Platform } from "react-native";
import React, { useEffect, useState } from "react";
import { ai, icons } from "../../assets";
import { BlurView } from "expo-blur";
import Style, { size } from "../../styles/Style";
import { Palette } from "../../styles";
import { FONT_FAMILY } from "../../styles/Fonts";
import moment from "moment";
import React, { useEffect, useState } from "react";
import { Image, Platform, Pressable, Text, View } from "react-native";
import useMinuit from "react-native-minuit/src/hooks/useMinuit.js";
import { ai, icons } from "../../assets";
import AppAlert from "../../components/Alert";
import GradientButton from "../../components/GradientButton";
import ProgressBar from "../../components/ProgressBar";
import firebase, { projectsRef } from "../../config/firebase";
import { Routes } from "../../navigation";
import { goBack, navigate } from "../../navigation/NavigationService";
import { useUser } from "../../providers/UserDataProvider";
import { Routes } from "../../navigation";
import GradientButton from "../../components/GradientButton";
import firebase, { projectsRef } from "../../config/firebase";
import useMinuit from "react-native-minuit/src/hooks/useMinuit.js";
import moment from "moment";
import { Palette } from "../../styles";
import { FONT_FAMILY } from "../../styles/Fonts";
import Style, { size } from "../../styles/Style";
const CreatingSong = ({ active, config }) => {
const [progress, setProgress] = useState(0);
@@ -118,6 +119,7 @@ const CreatingSong = ({ active, config }) => {
{
sunoTaskId: taskId,
musicStatus: "GENERATING",
musicError: firebase.firestore.FieldValue.delete(),
musicConfig: {
title: config?.title || "",
lyrics: config?.lyrics || [],
@@ -134,7 +136,40 @@ const CreatingSong = ({ active, config }) => {
);
}
} catch (e) {
console.log(e);
console.log("CreatingSong generation error", e?.message);
const rawMessage = e?.message || "";
const sanitized =
typeof rawMessage === "string"
? rawMessage.replace(/^INTERNAL:/i, "").trim()
: "";
const fallbackMessage =
e?.details?.status ||
"Le service est actuellement indisponible. Merci de réessayer.";
AppAlert(
"Impossible de générer la musique veuillez réessayer plus tard."
);
if (config?.projectId) {
try {
await projectsRef.doc(config.projectId).set(
{
musicStatus: "FAILED",
sunoTaskId: firebase.firestore.FieldValue.delete(),
generationStartAt: firebase.firestore.FieldValue.delete(),
updatedAt: firebase.firestore.FieldValue.serverTimestamp(),
musicError: {
source: "SUNO_API",
message: sanitized || fallbackMessage,
},
},
{ merge: true }
);
} catch (firestoreError) {
console.log(
"CreatingSong Firestore update error",
firestoreError?.message
);
}
}
} finally {
await setIsLoading(false);
}
+40 -1
View File
@@ -123,6 +123,7 @@ const GeneratingSong = () => {
voice: cfg?.voice,
instruments: cfg?.instruments,
tempo: cfg?.tempo,
projectId: selectedProjectId || selectedProject?.id || null,
});
const taskId =
data?.response?.data?.taskId || data?.response?.data?.task_id;
@@ -130,6 +131,7 @@ const GeneratingSong = () => {
const baseUpdate = {
sunoTaskId: taskId,
musicStatus: "GENERATING",
musicError: firebase.firestore.FieldValue.delete(),
generationStartAt: firebase.firestore.FieldValue.serverTimestamp(),
updatedAt: firebase.firestore.FieldValue.serverTimestamp(),
};
@@ -152,6 +154,42 @@ const GeneratingSong = () => {
}
} catch (e) {
console.log("GeneratingSong error", e?.message);
const rawMessage = e?.message || "";
const sanitized =
typeof rawMessage === "string"
? rawMessage.replace(/^INTERNAL:/i, "").trim()
: "";
const fallbackMessage =
e?.details?.message ||
e?.details?.status ||
"Le service Suno est indisponible. Veuillez réessayer plus tard.";
AppAlert(
"Impossible de générer la musique",
sanitized || fallbackMessage
);
if (selectedProjectId) {
try {
await projectsRef.doc(selectedProjectId).set(
{
musicStatus: "FAILED",
sunoTaskId: firebase.firestore.FieldValue.delete(),
generationStartAt: firebase.firestore.FieldValue.delete(),
updatedAt: firebase.firestore.FieldValue.serverTimestamp(),
musicError: {
source: "SUNO_API",
message: sanitized || fallbackMessage,
},
},
{ merge: true }
);
} catch (firestoreError) {
console.log(
"GeneratingSong Firestore update error",
firestoreError?.message
);
}
}
askedRef.current = false;
} finally {
await setIsLoading(false);
}
@@ -181,7 +219,8 @@ const GeneratingSong = () => {
status == null ||
status === "" ||
status === "PENDING" ||
status === "READY";
status === "READY" ||
status === "FAILED";
if (titleOk && isIdle && !askedRef.current) {
askedRef.current = true;