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 axios = require("axios");
|
||||||
const admin = require("firebase-admin");
|
const admin = require("firebase-admin");
|
||||||
const { logger } = require("firebase-functions/logger");
|
const { logger } = require("firebase-functions/logger");
|
||||||
@@ -14,6 +14,46 @@ const {
|
|||||||
// Initialiser Firestorey
|
// Initialiser Firestorey
|
||||||
const db = admin.firestore();
|
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
|
* Limite la longueur d'une chaîne
|
||||||
* @param {string} str - La chaîne à limiter
|
* @param {string} str - La chaîne à limiter
|
||||||
@@ -40,7 +80,7 @@ function extractVoiceStrings(voice) {
|
|||||||
}
|
}
|
||||||
if (typeof voice === "object") {
|
if (typeof voice === "object") {
|
||||||
return Object.values(voice).filter(
|
return Object.values(voice).filter(
|
||||||
(v) => typeof v === "string" && v.trim(),
|
(v) => typeof v === "string" && v.trim()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
@@ -94,7 +134,7 @@ function detectVocalGender(voiceInput = "") {
|
|||||||
if (Array.isArray(voiceInput)) {
|
if (Array.isArray(voiceInput)) {
|
||||||
const baseItem = voiceInput.find(
|
const baseItem = voiceInput.find(
|
||||||
(v) =>
|
(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;
|
const text = baseItem ? baseItem.value || baseItem.text || baseItem : null;
|
||||||
if (text) return detectVocalGender(text);
|
if (text) return detectVocalGender(text);
|
||||||
@@ -306,7 +346,7 @@ exports.generateMusic = onCall(async ({ data = {} }) => {
|
|||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
Authorization: `Bearer ${SUNO_API_KEY}`,
|
Authorization: `Bearer ${SUNO_API_KEY}`,
|
||||||
},
|
},
|
||||||
},
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const parsed = response.data;
|
const parsed = response.data;
|
||||||
@@ -325,9 +365,26 @@ exports.generateMusic = onCall(async ({ data = {} }) => {
|
|||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("❌ Erreur lors de la génération de musique:", error);
|
console.error("❌ Erreur lors de la génération de musique:", error);
|
||||||
throw new Error(
|
try {
|
||||||
`Erreur lors de la génération de musique: ${error.message}`,
|
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}`,
|
Authorization: `Bearer ${SUNO_API_KEY}`,
|
||||||
},
|
},
|
||||||
timeout: 30000, // 30 secondes de timeout
|
timeout: 30000, // 30 secondes de timeout
|
||||||
},
|
}
|
||||||
);
|
);
|
||||||
parsed = response.data;
|
parsed = response.data;
|
||||||
|
|
||||||
@@ -510,7 +567,7 @@ exports.sunoCallback = onRequest({ methods: ["POST"] }, async (req, res) => {
|
|||||||
const audioUrls = tracks
|
const audioUrls = tracks
|
||||||
.map(
|
.map(
|
||||||
(t) =>
|
(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)
|
.filter(Boolean)
|
||||||
.slice(0, 2);
|
.slice(0, 2);
|
||||||
@@ -525,7 +582,7 @@ exports.sunoCallback = onRequest({ methods: ["POST"] }, async (req, res) => {
|
|||||||
has_audio_url: !!t.audio_url,
|
has_audio_url: !!t.audio_url,
|
||||||
has_stream_audio_url: !!t.stream_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(
|
const downloadUrl = `https://firebasestorage.googleapis.com/v0/b/${bucket.name}/o/${encodeURIComponent(
|
||||||
path,
|
path
|
||||||
)}?alt=media&token=${token}`;
|
)}?alt=media&token=${token}`;
|
||||||
console.log("✅ [SunoCallback] Sauvegardé:", path, "URL:", downloadUrl);
|
console.log("✅ [SunoCallback] Sauvegardé:", path, "URL:", downloadUrl);
|
||||||
return { path, url: downloadUrl };
|
return { path, url: downloadUrl };
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(
|
console.error(
|
||||||
`❌ [SunoCallback] Échec save piste ${index + 1}:`,
|
`❌ [SunoCallback] Échec save piste ${index + 1}:`,
|
||||||
e.message,
|
e.message
|
||||||
);
|
);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -575,9 +632,10 @@ exports.sunoCallback = onRequest({ methods: ["POST"] }, async (req, res) => {
|
|||||||
{
|
{
|
||||||
musicStatus: "GENERATED",
|
musicStatus: "GENERATED",
|
||||||
musicUrls,
|
musicUrls,
|
||||||
|
musicError: admin.firestore.FieldValue.delete(),
|
||||||
updatedAt: admin.firestore.FieldValue.serverTimestamp(),
|
updatedAt: admin.firestore.FieldValue.serverTimestamp(),
|
||||||
},
|
},
|
||||||
{ merge: true },
|
{ merge: true }
|
||||||
);
|
);
|
||||||
console.log("🏷️ [SunoCallback] Projet marqué GENERATED", {
|
console.log("🏷️ [SunoCallback] Projet marqué GENERATED", {
|
||||||
projectId,
|
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 { BlurView } from "expo-blur";
|
||||||
import Style, { size } from "../../styles/Style";
|
import moment from "moment";
|
||||||
import { Palette } from "../../styles";
|
import React, { useEffect, useState } from "react";
|
||||||
import { FONT_FAMILY } from "../../styles/Fonts";
|
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 ProgressBar from "../../components/ProgressBar";
|
||||||
|
import firebase, { projectsRef } from "../../config/firebase";
|
||||||
|
import { Routes } from "../../navigation";
|
||||||
import { goBack, navigate } from "../../navigation/NavigationService";
|
import { goBack, navigate } from "../../navigation/NavigationService";
|
||||||
import { useUser } from "../../providers/UserDataProvider";
|
import { useUser } from "../../providers/UserDataProvider";
|
||||||
import { Routes } from "../../navigation";
|
import { Palette } from "../../styles";
|
||||||
import GradientButton from "../../components/GradientButton";
|
import { FONT_FAMILY } from "../../styles/Fonts";
|
||||||
import firebase, { projectsRef } from "../../config/firebase";
|
import Style, { size } from "../../styles/Style";
|
||||||
import useMinuit from "react-native-minuit/src/hooks/useMinuit.js";
|
|
||||||
import moment from "moment";
|
|
||||||
|
|
||||||
const CreatingSong = ({ active, config }) => {
|
const CreatingSong = ({ active, config }) => {
|
||||||
const [progress, setProgress] = useState(0);
|
const [progress, setProgress] = useState(0);
|
||||||
@@ -118,6 +119,7 @@ const CreatingSong = ({ active, config }) => {
|
|||||||
{
|
{
|
||||||
sunoTaskId: taskId,
|
sunoTaskId: taskId,
|
||||||
musicStatus: "GENERATING",
|
musicStatus: "GENERATING",
|
||||||
|
musicError: firebase.firestore.FieldValue.delete(),
|
||||||
musicConfig: {
|
musicConfig: {
|
||||||
title: config?.title || "",
|
title: config?.title || "",
|
||||||
lyrics: config?.lyrics || [],
|
lyrics: config?.lyrics || [],
|
||||||
@@ -134,7 +136,40 @@ const CreatingSong = ({ active, config }) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} 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 {
|
} finally {
|
||||||
await setIsLoading(false);
|
await setIsLoading(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -123,6 +123,7 @@ const GeneratingSong = () => {
|
|||||||
voice: cfg?.voice,
|
voice: cfg?.voice,
|
||||||
instruments: cfg?.instruments,
|
instruments: cfg?.instruments,
|
||||||
tempo: cfg?.tempo,
|
tempo: cfg?.tempo,
|
||||||
|
projectId: selectedProjectId || selectedProject?.id || null,
|
||||||
});
|
});
|
||||||
const taskId =
|
const taskId =
|
||||||
data?.response?.data?.taskId || data?.response?.data?.task_id;
|
data?.response?.data?.taskId || data?.response?.data?.task_id;
|
||||||
@@ -130,6 +131,7 @@ const GeneratingSong = () => {
|
|||||||
const baseUpdate = {
|
const baseUpdate = {
|
||||||
sunoTaskId: taskId,
|
sunoTaskId: taskId,
|
||||||
musicStatus: "GENERATING",
|
musicStatus: "GENERATING",
|
||||||
|
musicError: firebase.firestore.FieldValue.delete(),
|
||||||
generationStartAt: firebase.firestore.FieldValue.serverTimestamp(),
|
generationStartAt: firebase.firestore.FieldValue.serverTimestamp(),
|
||||||
updatedAt: firebase.firestore.FieldValue.serverTimestamp(),
|
updatedAt: firebase.firestore.FieldValue.serverTimestamp(),
|
||||||
};
|
};
|
||||||
@@ -152,6 +154,42 @@ const GeneratingSong = () => {
|
|||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log("GeneratingSong error", e?.message);
|
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 {
|
} finally {
|
||||||
await setIsLoading(false);
|
await setIsLoading(false);
|
||||||
}
|
}
|
||||||
@@ -181,7 +219,8 @@ const GeneratingSong = () => {
|
|||||||
status == null ||
|
status == null ||
|
||||||
status === "" ||
|
status === "" ||
|
||||||
status === "PENDING" ||
|
status === "PENDING" ||
|
||||||
status === "READY";
|
status === "READY" ||
|
||||||
|
status === "FAILED";
|
||||||
|
|
||||||
if (titleOk && isIdle && !askedRef.current) {
|
if (titleOk && isIdle && !askedRef.current) {
|
||||||
askedRef.current = true;
|
askedRef.current = true;
|
||||||
|
|||||||
Reference in New Issue
Block a user