filter on lyrics for bloking unwanted content, tickets fix

This commit is contained in:
Thomas Demirdjian
2025-09-08 15:48:29 +02:00
parent 987388c501
commit a3dc4f89b7
22 changed files with 1386 additions and 126 deletions
+74 -32
View File
@@ -1,5 +1,5 @@
import { View, ScrollView, Alert } from "react-native";
import React, { useMemo, useState, useCallback, useEffect, useRef } from "react";
import React, { useMemo, useState, useCallback, useEffect } from "react";
import Page from "../../layouts/Page";
import MusicLandHeader from "../../components/MusicLandHeader";
import { navigate } from "../../navigation/NavigationService";
@@ -61,37 +61,6 @@ const Lyrics = ({ navigation }) => {
}, [projectTitle, projectLyrics, projectConfig]);
const [titleValue, setTitleValue] = useState(initial.title || "");
const titleSaveTimer = useRef(null);
// Auto-save du titre lorsqu'il est modifié (si non vide)
useEffect(() => {
const newTitle = (titleValue || "").trim();
// Annuler tout timer précédent
if (titleSaveTimer.current) clearTimeout(titleSaveTimer.current);
// Ne rien faire si inchangé vs projet courant
const currentTitle = (selectedProject?.title || "").trim();
if (!newTitle || newTitle === currentTitle) return;
titleSaveTimer.current = setTimeout(async () => {
try {
await projectsRef.doc(selectedProjectId).set(
{
title: newTitle,
titleLower: newTitle.toLowerCase(),
updatedAt: firebase.firestore.FieldValue.serverTimestamp(),
},
{ merge: true },
);
} catch (e) {
// silencieux: l'utilisateur pourra tjs valider plus tard
console.log("Auto-save titre échoué", e?.message);
}
}, 500);
return () => {
if (titleSaveTimer.current) clearTimeout(titleSaveTimer.current);
};
}, [titleValue, selectedProjectId, selectedProject]);
const [sections, setSections] = useState(initial.sections || []);
const setSectionAt = (index, value) => {
setSections((prev) => {
@@ -158,6 +127,79 @@ const Lyrics = ({ navigation }) => {
s.lyrics === normalizedNewLyrics[i]?.lyrics,
);
// 1) Appel de la Cloud Function de modération avant tout enregistrement
try {
const callable = firebase
.functions()
.httpsCallable("lyrics-analyseLyricsToxicity");
const { data } = await callable({
title: titleTrimmed,
lyrics: normalizedNewLyrics,
});
if (!data?.success) {
// Blocage dur: afficher le message et ne pas sauvegarder
if (data?.errorCode === "TOXIC_CONTENT_BLOCKED") {
const quotes = Array.isArray(data?.result?.excerpts)
? data.result.excerpts
.slice(0, 3)
.map((e) => `${e.quote}`)
.join("\n")
: null;
Alert.alert(
"Contenu interdit",
[data?.message, quotes].filter(Boolean).join("\n\n"),
);
return; // stop here
}
// Erreur d'analyse: informer et arrêter
if (data?.errorCode === "ANALYSE_FAILED") {
Alert.alert(
"Analyse indisponible",
"Impossible de vérifier la toxicité pour le moment. Réessayez plus tard.",
);
return;
}
}
// Cas signalé mais non bloquant: demander confirmation
if (data?.errorCode === "TOXIC_CONTENT_FLAGGED") {
const quotes = Array.isArray(data?.result?.excerpts)
? data.result.excerpts
.slice(0, 3)
.map((e) => `${e.quote}`)
.join("\n")
: null;
const msg = [data?.message, quotes].filter(Boolean).join("\n\n");
const proceed = await new Promise((resolve) => {
Alert.alert("Contenu potentiellement sensible", msg, [
{
text: "Annuler",
style: "cancel",
onPress: () => resolve(false),
},
{
text: "Continuer",
style: "destructive",
onPress: () => resolve(true),
},
]);
});
if (!proceed) return;
}
} catch (moderationError) {
console.log(
"Moderation call failed",
moderationError?.message || moderationError,
);
Alert.alert(
"Analyse indisponible",
"Impossible de vérifier la toxicité pour le moment. Réessayez plus tard.",
);
return;
}
const baseData = {
title: titleTrimmed,
titleLower: titleTrimmed.toLowerCase(),