580 lines
23 KiB
JavaScript
580 lines
23 KiB
JavaScript
import { createClient } from '@supabase/supabase-js';
|
|
|
|
const supabaseUrl = process.env.REACT_APP_SUPABASE_URL || process.env.SUPABASE_URL;
|
|
const supabaseKey = process.env.REACT_APP_SUPABASE_ANON_KEY || process.env.SUPABASE_ANON_KEY;
|
|
const supabase = createClient(supabaseUrl, supabaseKey);
|
|
|
|
function checkIfHoraireRunsOnDate(horaire, date = new Date()) {
|
|
const yyyy = date.getFullYear();
|
|
const mm = String(date.getMonth() + 1).padStart(2, '0');
|
|
const dd = String(date.getDate()).padStart(2, '0');
|
|
const dateStr = `${yyyy}-${mm}-${dd}`;
|
|
|
|
// 1. Jours personnalisés (override)
|
|
let joursPerso = [];
|
|
try {
|
|
joursPerso = typeof horaire.jours_personnalises === 'string'
|
|
? JSON.parse(horaire.jours_personnalises)
|
|
: (horaire.jours_personnalises || []);
|
|
} catch(e) {}
|
|
const matchPerso = joursPerso.find(jp => jp.date === dateStr);
|
|
if (matchPerso) return matchPerso.circule;
|
|
|
|
// 2. Jours non circulation (override)
|
|
let joursNonCirc = [];
|
|
try {
|
|
joursNonCirc = typeof horaire.jours_non_circulation === 'string'
|
|
? JSON.parse(horaire.jours_non_circulation)
|
|
: (horaire.jours_non_circulation || []);
|
|
} catch(e) {}
|
|
if (joursNonCirc.includes(dateStr)) return false;
|
|
|
|
// 3. Jours de la semaine
|
|
const day = date.getDay(); // 0 = Dimanche, 1 = Lundi, ...
|
|
switch(day) {
|
|
case 0: return !!horaire.circule_dimanche;
|
|
case 1: return !!horaire.circule_lundi;
|
|
case 2: return !!horaire.circule_mardi;
|
|
case 3: return !!horaire.circule_mercredi;
|
|
case 4: return !!horaire.circule_jeudi;
|
|
case 5: return !!horaire.circule_vendredi;
|
|
case 6: return !!horaire.circule_samedi;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function getTrainDetailsForGare(horaire, gareId, type) {
|
|
// 1. Rechercher en priorité dans la liste gares_desservies
|
|
const stops = horaire.gares_desservies || [];
|
|
const stop = stops.find(s => s.gare_id === gareId);
|
|
if (stop) {
|
|
if (type === 'depart') {
|
|
return { time: stop.heure_depart || stop.heure_arrivee || '', quai: stop.quai_reel || stop.quai || '' };
|
|
} else {
|
|
return { time: stop.heure_arrivee || stop.heure_depart || '', quai: stop.quai_reel || stop.quai || '' };
|
|
}
|
|
}
|
|
|
|
// 2. Fallback direct sur l'objet horaire
|
|
if (type === 'depart' && horaire.gare_depart_id === gareId) {
|
|
return { time: horaire.heure_depart || '', quai: horaire.quai_reel || horaire.quai || '' };
|
|
}
|
|
if (type === 'arrivee' && horaire.gare_arrivee_id === gareId) {
|
|
return { time: horaire.heure_arrivee || '', quai: horaire.quai_reel || horaire.quai || '' };
|
|
}
|
|
|
|
return {
|
|
time: type === 'depart' ? horaire.heure_depart : horaire.heure_arrivee,
|
|
quai: horaire.quai_reel || horaire.quai || ''
|
|
};
|
|
}
|
|
|
|
function generateNomFichierAnnonce(typeAnnonce, ext = 'mp3') {
|
|
const date = new Date().toISOString().slice(0, 10);
|
|
const shortId = Math.random().toString(36).slice(2, 8);
|
|
return `${typeAnnonce}_${date}_${shortId}.${ext}`;
|
|
}
|
|
|
|
function concatenateAudio(buffers) {
|
|
if (buffers.length === 0) return { payload: null, format: 'mp3' };
|
|
if (buffers.length === 1) {
|
|
const isWav = buffers[0].subarray(0, 4).toString('ascii') === 'RIFF';
|
|
return { payload: buffers[0], format: isWav ? 'wav' : 'mp3' };
|
|
}
|
|
|
|
const isWav = buffers[0].subarray(0, 4).toString('ascii') === 'RIFF';
|
|
|
|
if (isWav) {
|
|
// Logic for WAV concatenation
|
|
let totalPcmLength = 0;
|
|
for (let i = 0; i < buffers.length; i++) {
|
|
const hasRiff = buffers[i].subarray(0, 4).toString('ascii') === 'RIFF';
|
|
totalPcmLength += hasRiff ? (buffers[i].length - 44) : buffers[i].length;
|
|
}
|
|
|
|
const header = Buffer.alloc(44);
|
|
buffers[0].copy(header, 0, 0, 44);
|
|
|
|
const totalFileSize = 44 + totalPcmLength;
|
|
header.writeUInt32LE(totalFileSize - 8, 4);
|
|
header.writeUInt32LE(totalPcmLength, 40);
|
|
|
|
const combinedBuffer = Buffer.alloc(totalFileSize);
|
|
header.copy(combinedBuffer, 0, 0, 44);
|
|
|
|
let offset = 44;
|
|
for (let i = 0; i < buffers.length; i++) {
|
|
const hasRiff = buffers[i].subarray(0, 4).toString('ascii') === 'RIFF';
|
|
const dataPayload = hasRiff ? buffers[i].subarray(44) : buffers[i];
|
|
dataPayload.copy(combinedBuffer, offset);
|
|
offset += dataPayload.length;
|
|
}
|
|
|
|
return { payload: combinedBuffer, format: 'wav' };
|
|
} else {
|
|
// Logic for MP3 concatenation (streamable, direct binary append)
|
|
return { payload: Buffer.concat(buffers), format: 'mp3' };
|
|
}
|
|
}
|
|
|
|
export default async function handler(req, res) {
|
|
// Authentification double : CRON_SECRET (Vercel Cron) ou Supabase session token (browser)
|
|
let isAuthorized = false;
|
|
const authHeader = req.headers.authorization;
|
|
|
|
if (process.env.CRON_SECRET && authHeader === `Bearer ${process.env.CRON_SECRET}`) {
|
|
isAuthorized = true;
|
|
} else if (authHeader && authHeader.startsWith('Bearer ')) {
|
|
const token = authHeader.replace('Bearer ', '');
|
|
try {
|
|
const { data: { user } } = await supabase.auth.getUser(token);
|
|
if (user) {
|
|
isAuthorized = true;
|
|
}
|
|
} catch (e) {
|
|
console.error('[Cron] User token auth failed:', e);
|
|
}
|
|
} else if (!process.env.CRON_SECRET) {
|
|
// En dev local, si CRON_SECRET n'est pas configuré
|
|
isAuthorized = true;
|
|
}
|
|
|
|
if (!isAuthorized) {
|
|
return res.status(401).end('Unauthorized');
|
|
}
|
|
|
|
try {
|
|
const now = new Date();
|
|
const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0, 0);
|
|
const todayEnd = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 23, 59, 59, 999);
|
|
|
|
const regenerate = req.query.regenerate === 'true';
|
|
console.log(`[CronAnnonces] Running at ${now.toISOString()} | Regenerate: ${regenerate}`);
|
|
|
|
// --- ETAPE 1 : RECUPERATION DES DONNEES ---
|
|
const [
|
|
{ data: gares },
|
|
{ data: horaires },
|
|
{ data: templates },
|
|
{ data: mots },
|
|
{ data: parametres },
|
|
{ data: todayAnnonces }
|
|
] = await Promise.all([
|
|
supabase.from('gares').select('*'),
|
|
supabase.from('horaires').select('*'),
|
|
supabase.from('annonces_templates').select('*').eq('actif', true),
|
|
supabase.from('banque_mots').select('*').eq('actif', true),
|
|
supabase.from('parametres').select('*'),
|
|
supabase.from('annonces_enregistrees')
|
|
.select('*')
|
|
.gte('date_diffusion', todayStart.toISOString())
|
|
.lte('date_diffusion', todayEnd.toISOString())
|
|
]);
|
|
|
|
// Si régénération demandée, supprimer de la BDD et du Storage toutes les annonces d'aujourd'hui
|
|
const regenerateParam = (parametres || []).find(p => p.cle === 'global_regenerate_requested');
|
|
const isGlobalRegenerate = regenerate || (regenerateParam && regenerateParam.valeur === 'true');
|
|
|
|
if (isGlobalRegenerate && todayAnnonces && todayAnnonces.length > 0) {
|
|
console.log(`[CronAnnonces] Deleting ${todayAnnonces.length} existing announcements for today...`);
|
|
const pathsToDelete = todayAnnonces.map(a => a.storage_path).filter(Boolean);
|
|
if (pathsToDelete.length > 0) {
|
|
await supabase.storage.from('banque-annonces').remove(pathsToDelete);
|
|
}
|
|
await supabase
|
|
.from('annonces_enregistrees')
|
|
.delete()
|
|
.in('id', todayAnnonces.map(a => a.id));
|
|
|
|
// Vider la liste pour forcer la régénération
|
|
todayAnnonces.length = 0;
|
|
}
|
|
|
|
// Réinitialiser le drapeau de régénération en base de données si nécessaire
|
|
if (regenerateParam && regenerateParam.valeur === 'true') {
|
|
await supabase
|
|
.from('parametres')
|
|
.update({ valeur: 'false', updated_at: now.toISOString() })
|
|
.eq('cle', 'global_regenerate_requested');
|
|
console.log('[CronAnnonces] Reset global_regenerate_requested parameter in DB.');
|
|
}
|
|
|
|
const settingsByGare = {};
|
|
const dijonGare = (gares || []).find(g => g.nom && g.nom.toLowerCase().trim() === 'dijon');
|
|
const dijonSettingsParam = dijonGare ? (parametres || []).find(p => p.cle === `diffusion_settings_${dijonGare.id}`) : null;
|
|
const dijonSettings = dijonSettingsParam ? dijonSettingsParam.valeur : null;
|
|
|
|
(parametres || []).forEach(p => {
|
|
if (p.cle.startsWith('diffusion_settings_')) {
|
|
const gareId = p.cle.replace('diffusion_settings_', '');
|
|
settingsByGare[gareId] = p.valeur;
|
|
}
|
|
});
|
|
|
|
const runningHorairesGlobal = (horaires || []).filter(h => checkIfHoraireRunsOnDate(h, now));
|
|
|
|
// --- ETAPE 2 : COMPTER LE NOMBRE TOTAL DE TACHES A GENERER ---
|
|
let totalTasksCount = 0;
|
|
const garesToProcess = (gares || []).filter(gare => {
|
|
let rule = settingsByGare[gare.id];
|
|
if (!rule && dijonSettings && dijonGare && dijonGare.id !== gare.id) {
|
|
rule = dijonSettings;
|
|
}
|
|
return !!rule;
|
|
});
|
|
|
|
const tasksToExecute = [];
|
|
|
|
for (const gare of garesToProcess) {
|
|
let rule = settingsByGare[gare.id];
|
|
if (!rule && dijonSettings && dijonGare && dijonGare.id !== gare.id) {
|
|
rule = dijonSettings;
|
|
}
|
|
|
|
// Filtrer et trier les horaires pour cette gare
|
|
const runningHorairesForGare = runningHorairesGlobal
|
|
.filter(h => {
|
|
if (h.gare_depart_id === gare.id || h.gare_arrivee_id === gare.id) return true;
|
|
const stops = h.gares_desservies || [];
|
|
return stops.some(s => s.gare_id === gare.id);
|
|
})
|
|
.sort((a, b) => {
|
|
const timeA = getTrainDetailsForGare(a, gare.id, 'depart').time || getTrainDetailsForGare(a, gare.id, 'arrivee').time || '00:00';
|
|
const timeB = getTrainDetailsForGare(b, gare.id, 'depart').time || getTrainDetailsForGare(b, gare.id, 'arrivee').time || '00:00';
|
|
return timeA.localeCompare(timeB);
|
|
});
|
|
|
|
for (const horaire of runningHorairesForGare) {
|
|
const isDepart = horaire.gare_depart_id === gare.id ||
|
|
(horaire.gares_desservies || []).some(s => s.gare_id === gare.id && s.heure_depart);
|
|
|
|
const isArrivee = horaire.gare_arrivee_id === gare.id ||
|
|
(horaire.gares_desservies || []).some(s => s.gare_id === gare.id && s.heure_arrivee);
|
|
|
|
if (isDepart) {
|
|
const typeRule = rule.depart;
|
|
if (typeRule && typeRule.templateId) {
|
|
const template = (templates || []).find(t => t.id === typeRule.templateId);
|
|
if (template) {
|
|
// Vérifier si cette annonce existe déjà
|
|
const exists = (todayAnnonces || []).some(a =>
|
|
a.gare_id === gare.id &&
|
|
a.type_annonce === 'depart' &&
|
|
a.variables_values?.numero_train === horaire.numero_train
|
|
);
|
|
if (!exists) {
|
|
tasksToExecute.push({ gare, typeAnnonce: 'depart', typeRule, template, horaire });
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (isArrivee) {
|
|
const typeRule = rule.arrivee;
|
|
if (typeRule && typeRule.templateId) {
|
|
const template = (templates || []).find(t => t.id === typeRule.templateId);
|
|
if (template) {
|
|
// Vérifier si cette annonce existe déjà
|
|
const exists = (todayAnnonces || []).some(a =>
|
|
a.gare_id === gare.id &&
|
|
a.type_annonce === 'arrivee' &&
|
|
a.variables_values?.numero_train === horaire.numero_train
|
|
);
|
|
if (!exists) {
|
|
tasksToExecute.push({ gare, typeAnnonce: 'arrivee', typeRule, template, horaire });
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
totalTasksCount = tasksToExecute.length;
|
|
console.log(`[CronAnnonces] Total tasks to generate: ${totalTasksCount}`);
|
|
|
|
// Publier l'état initial
|
|
await supabase.from('parametres').upsert({
|
|
cle: 'global_generation_progress',
|
|
valeur: JSON.stringify({
|
|
status: totalTasksCount > 0 ? 'running' : 'done',
|
|
current: 0,
|
|
total: totalTasksCount,
|
|
progress: totalTasksCount > 0 ? 0 : 100,
|
|
message: totalTasksCount > 0 ? `Préparation de la génération... (0/${totalTasksCount})` : 'Toutes les annonces sont déjà générées.'
|
|
}),
|
|
updated_at: new Date().toISOString()
|
|
}, { onConflict: 'cle' });
|
|
|
|
// Exécuter séquentiellement et publier la progression
|
|
let currentTaskIndex = 0;
|
|
for (const task of tasksToExecute) {
|
|
const { gare, typeAnnonce, typeRule, template, horaire } = task;
|
|
|
|
// Mettre à jour la progression avant chaque tâche
|
|
const pct = Math.round((currentTaskIndex / totalTasksCount) * 100);
|
|
await supabase.from('parametres').upsert({
|
|
cle: 'global_generation_progress',
|
|
valeur: JSON.stringify({
|
|
status: 'running',
|
|
current: currentTaskIndex,
|
|
total: totalTasksCount,
|
|
progress: pct,
|
|
message: `Génération : ${gare.nom} - Train ${horaire.numero_train || ''} (${typeAnnonce === 'depart' ? 'Départ' : 'Arrivée'}) (${currentTaskIndex + 1}/${totalTasksCount})`
|
|
}),
|
|
updated_at: new Date().toISOString()
|
|
}, { onConflict: 'cle' });
|
|
|
|
try {
|
|
await generateAnnonce(gare, typeAnnonce, typeRule, template, horaire);
|
|
} catch (err) {
|
|
console.error(`[Cron] Error generating task for ${gare.nom}:`, err);
|
|
}
|
|
|
|
currentTaskIndex++;
|
|
}
|
|
|
|
// Publier l'état final
|
|
await supabase.from('parametres').upsert({
|
|
cle: 'global_generation_progress',
|
|
valeur: JSON.stringify({
|
|
status: 'done',
|
|
current: totalTasksCount,
|
|
total: totalTasksCount,
|
|
progress: 100,
|
|
message: totalTasksCount > 0 ? 'Génération globale terminée avec succès !' : 'Toutes les annonces sont déjà générées.'
|
|
}),
|
|
updated_at: new Date().toISOString()
|
|
}, { onConflict: 'cle' });
|
|
|
|
// Fonction interne de génération séquentielle
|
|
async function generateAnnonce(gare, typeAnnonce, typeRule, template, horaire) {
|
|
const { time: trainTime, quai: trainQuai } = getTrainDetailsForGare(horaire, gare.id, typeAnnonce);
|
|
if (!trainTime) return;
|
|
|
|
const [hStr, mStr] = trainTime.split(':');
|
|
const trainHour = parseInt(hStr, 10);
|
|
const trainMin = parseInt(mStr, 10);
|
|
const scheduledDate = new Date(now.getFullYear(), now.getMonth(), now.getDate(), trainHour, trainMin);
|
|
scheduledDate.setMinutes(scheduledDate.getMinutes() - parseInt(typeRule.offset || 0, 10));
|
|
|
|
const isPast = scheduledDate <= now;
|
|
const direction = typeAnnonce === 'depart' ? `à destination de ${horaire.gare_arrivee_nom}` : `en provenance de ${horaire.gare_depart_nom}`;
|
|
const name = `Auto: ${horaire.numero_train} (${typeAnnonce}) ${direction} - ${trainTime}`;
|
|
|
|
const stopsList = (horaire.snapshot_gares_desservies || horaire.gares_desservies || []);
|
|
const intermediateStops = stopsList
|
|
.filter(g => !g.is_depart && !g.is_arrivee)
|
|
.map(g => g.gare_nom)
|
|
.join(', ');
|
|
|
|
const resolvedVars = {
|
|
numero_train: horaire.numero_train || '',
|
|
destination: horaire.gare_arrivee_nom || '',
|
|
provenance: horaire.gare_depart_nom || '',
|
|
heure_depart: horaire.heure_depart || '',
|
|
heure_arrivee: horaire.heure_arrivee || '',
|
|
heure: trainTime,
|
|
quai: trainQuai,
|
|
voie: trainQuai,
|
|
arrets_intermediaires: intermediateStops,
|
|
afficheurs: typeRule.afficheurs || [],
|
|
type_train: horaire.type_train || ''
|
|
};
|
|
|
|
// Construire le texte TTS
|
|
let ttsText = template.tts_texte || '';
|
|
if (!ttsText) {
|
|
ttsText = (template.sequence || [])
|
|
.map(s => {
|
|
if (s.type === 'mot') return s.label;
|
|
if (s.type === 'variable') return resolvedVars[s.variable] || '';
|
|
return '';
|
|
})
|
|
.filter(Boolean)
|
|
.join(' ');
|
|
} else {
|
|
Object.entries(resolvedVars).forEach(([key, val]) => {
|
|
ttsText = ttsText.replaceAll(`{${key}}`, Array.isArray(val) ? val.join(', ') : val);
|
|
});
|
|
}
|
|
|
|
resolvedVars.tts_texte = ttsText;
|
|
|
|
const mode = typeRule.mode || ((template.nom?.toLowerCase().includes('sive') || template.description?.toLowerCase().includes('sive')) ? 'concatenation' : 'tts');
|
|
let audioPayload = Buffer.from('TTS_ONLY');
|
|
let format = 'mp3';
|
|
|
|
if (mode === 'concatenation') {
|
|
const targetGareSousType = template.nom?.toLowerCase().includes('sive') ? 'sive' : 'quai';
|
|
const buffers = [];
|
|
|
|
for (let idxInSeq = 0; idxInSeq < (template.sequence || []).length; idxInSeq++) {
|
|
const item = template.sequence[idxInSeq];
|
|
let motToPlay = null;
|
|
|
|
if (item.type === 'mot') {
|
|
motToPlay = mots.find(m => m.id === item.mot_id);
|
|
} else if (item.type === 'variable') {
|
|
const val = resolvedVars[item.variable];
|
|
if (val) {
|
|
const normValue = String(val).toLowerCase().trim();
|
|
if (item.variable === 'destination' || item.variable === 'provenance') {
|
|
const gMots = mots.filter(m => m.categorie === 'gare');
|
|
for (const m of gMots) {
|
|
try {
|
|
const parsed = JSON.parse(m.description);
|
|
if (parsed.gare_nom?.toLowerCase().trim() === normValue || m.label?.toLowerCase().trim() === normValue) {
|
|
if (parsed.sous_type === targetGareSousType) { motToPlay = m; break; }
|
|
if (!motToPlay) motToPlay = m;
|
|
}
|
|
} catch (_) {}
|
|
}
|
|
} else if (item.variable === 'quai' || item.variable === 'voie') {
|
|
const isLast = idxInSeq === template.sequence.length - 1;
|
|
const targetQuaiSousType = isLast ? 'descendant' : 'montant';
|
|
const qMots = mots.filter(m => m.categorie === 'quai');
|
|
for (const m of qMots) {
|
|
try {
|
|
const parsed = JSON.parse(m.description);
|
|
if (parsed.quai_nom?.toLowerCase().trim() === normValue || m.label?.toLowerCase().trim() === normValue) {
|
|
if (parsed.sous_type === targetQuaiSousType) { motToPlay = m; break; }
|
|
if (!motToPlay) motToPlay = m;
|
|
}
|
|
} catch (_) {}
|
|
}
|
|
} else if (item.variable === 'type_train') {
|
|
const ttMots = mots.filter(m => m.categorie === 'type_train');
|
|
for (const m of ttMots) {
|
|
try {
|
|
const parsed = JSON.parse(m.description);
|
|
if (parsed.type_train_nom?.toLowerCase().trim() === normValue || m.label?.toLowerCase().trim() === normValue) {
|
|
motToPlay = m;
|
|
break;
|
|
}
|
|
} catch (_) {}
|
|
}
|
|
}
|
|
if (!motToPlay) {
|
|
motToPlay = mots.find(m => m.label?.toLowerCase().trim() === normValue);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (motToPlay) {
|
|
try {
|
|
const { data: fileData, error: dlError } = await supabase.storage.from('banque-annonces').download(motToPlay.storage_path);
|
|
if (!dlError && fileData) {
|
|
const arrayBuffer = await fileData.arrayBuffer();
|
|
buffers.push(Buffer.from(arrayBuffer));
|
|
}
|
|
} catch (e) {
|
|
console.warn(`[Cron] download failed for ${motToPlay.storage_path}`, e);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (buffers.length > 0) {
|
|
const resAudio = concatenateAudio(buffers);
|
|
if (resAudio.payload) {
|
|
audioPayload = resAudio.payload;
|
|
format = resAudio.format;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Upload to storage
|
|
const ext = format;
|
|
const nomFichier = generateNomFichierAnnonce(typeAnnonce, ext);
|
|
const storagePath = `annonces/${nomFichier}`;
|
|
|
|
const { error: uploadError } = await supabase.storage
|
|
.from('banque-annonces')
|
|
.upload(storagePath, audioPayload, {
|
|
cacheControl: '3600',
|
|
upsert: false,
|
|
contentType: ext === 'wav' ? 'audio/wav' : 'audio/mpeg'
|
|
});
|
|
|
|
if (uploadError) {
|
|
console.error('[Cron] Upload error:', uploadError);
|
|
return;
|
|
}
|
|
|
|
// Save in BDD
|
|
const { error: dbError } = await supabase
|
|
.from('annonces_enregistrees')
|
|
.insert({
|
|
nom: name,
|
|
type_annonce: typeAnnonce,
|
|
template_id: template.id,
|
|
storage_path: storagePath,
|
|
nom_fichier: nomFichier,
|
|
variables_values: resolvedVars,
|
|
mode_generation: mode,
|
|
duree_ms: null,
|
|
taille_bytes: audioPayload.length,
|
|
gare_id: gare.id,
|
|
notes: `Programmed Auto Cron: ${typeAnnonce} | Afficheurs: ${(resolvedVars.afficheurs || []).join(', ')}`,
|
|
date_diffusion: scheduledDate.toISOString(),
|
|
diffuse: isPast
|
|
});
|
|
|
|
if (dbError) {
|
|
console.error('[Cron] DB error inserting annonce:', dbError);
|
|
await supabase.storage.from('banque-annonces').remove([storagePath]);
|
|
} else {
|
|
console.log(`[Cron] Generated and saved annonce for train ${horaire.numero_train} at ${gare.nom}`);
|
|
}
|
|
}
|
|
|
|
// --- ETAPE 3 : DIFFUSION DES ANNONCES DUES ---
|
|
const { data: dueAnnonces, error: dueError } = await supabase
|
|
.from('annonces_enregistrees')
|
|
.select('*')
|
|
.eq('diffuse', false)
|
|
.lte('date_diffusion', now.toISOString());
|
|
|
|
if (dueError) {
|
|
console.error('[Cron] dueAnnonces fetch error:', dueError);
|
|
} else if (dueAnnonces && dueAnnonces.length > 0) {
|
|
console.log(`[Cron] Found ${dueAnnonces.length} due announcements to broadcast.`);
|
|
|
|
const broadcastPromises = dueAnnonces.map(async (annonce) => {
|
|
try {
|
|
// 1. Diffuser sur la table BDD en direct pour les afficheurs
|
|
const { error: diffError } = await supabase
|
|
.from('annonces_diffusions')
|
|
.insert([
|
|
{
|
|
annonce_id: annonce.id,
|
|
gare_id: annonce.gare_id,
|
|
audio_path: annonce.mode_generation === 'tts' ? null : annonce.storage_path,
|
|
tts_texte: annonce.mode_generation === 'tts' ? (annonce.variables_values?.tts_texte || 'Attention, train en approche.') : null
|
|
}
|
|
]);
|
|
|
|
if (diffError) {
|
|
console.error(`[Cron] Error broadcasting annonce ${annonce.id}:`, diffError);
|
|
} else {
|
|
// 2. Mettre à jour l'annonce dans la base comme diffusée
|
|
await supabase
|
|
.from('annonces_enregistrees')
|
|
.update({ diffuse: true, updated_at: now.toISOString() })
|
|
.eq('id', annonce.id);
|
|
console.log(`[Cron] Broadcasted and updated annonce ${annonce.id} (nom: ${annonce.nom})`);
|
|
}
|
|
} catch (broadcastErr) {
|
|
console.error(`[Cron] Broadcast error for annonce ${annonce.id}:`, broadcastErr);
|
|
}
|
|
});
|
|
|
|
await Promise.all(broadcastPromises);
|
|
}
|
|
|
|
return res.status(200).json({ success: true, message: "Génération et diffusion crons exécutées." });
|
|
} catch (err) {
|
|
console.error('[Cron] Global exception:', err);
|
|
return res.status(500).json({ error: err.message });
|
|
}
|
|
}
|