MIGRATION VERS GITEA
This commit is contained in:
commit
5f9d991c15
333 files changed
+108514
No files matched your search
@@ -0,0 +1,579 @@
|
||||
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 });
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user