manage errors
This commit is contained in:
+71
-13
@@ -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,9 +365,26 @@ 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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user