Fix workflow production avec XML Digital Ocean et format Google Sheets
Corrections majeures: - Digital Ocean: Récupération réelle XML depuis /wp-content/XML/ (86k chars au lieu de mock 1k) - Nettoyage tags: Suppression <strong> dans extractElements() pour éviter parsing errors - Doublons résilients: Tolérance doublons XML avec validation tags uniques - Hiérarchie complète: StepExecutor génère 36 éléments depuis hierarchy.originalElement.name - Format Google Sheets: Adaptation colonnes selon useVersionedSheet (17 legacy vs 21 versioned) - Range Google Sheets: Force A1 avec INSERT_ROWS pour éviter décalage U:AO - xmlTemplate optimisé: Exclusion du JSON metadata pour limite 50k chars Résultat: 2151 mots, 36 éléments, sauvegarde correcte A-Q 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
f51c4095f6
commit
b2fe9e0b7b
@ -264,47 +264,81 @@ async function saveGeneratedArticleOrganic(articleData, csvData, config = {}) {
|
|||||||
versionHistory: config.versionHistory || null
|
versionHistory: config.versionHistory || null
|
||||||
};
|
};
|
||||||
|
|
||||||
// Préparer la ligne de données avec versioning
|
// Préparer la ligne de données selon le format de la sheet
|
||||||
const row = [
|
let row;
|
||||||
metadata.timestamp,
|
|
||||||
metadata.slug,
|
if (config.useVersionedSheet) {
|
||||||
metadata.mc0,
|
// Format VERSIONED (21 colonnes) : avec version, stage, stageDescription, parentArticleId
|
||||||
metadata.t0,
|
row = [
|
||||||
metadata.personality,
|
metadata.timestamp,
|
||||||
metadata.antiDetectionLevel,
|
metadata.slug,
|
||||||
compiledText, // ← TEXTE ORGANIQUE
|
metadata.mc0,
|
||||||
metadata.textLength,
|
metadata.t0,
|
||||||
metadata.wordCount,
|
metadata.personality,
|
||||||
metadata.elementsCount,
|
metadata.antiDetectionLevel,
|
||||||
metadata.llmUsed,
|
compiledText,
|
||||||
metadata.validationStatus,
|
metadata.textLength,
|
||||||
// 🆕 Colonnes de versioning
|
metadata.wordCount,
|
||||||
metadata.version,
|
metadata.elementsCount,
|
||||||
metadata.stage,
|
metadata.llmUsed,
|
||||||
metadata.stageDescription,
|
metadata.validationStatus,
|
||||||
metadata.parentArticleId || '',
|
metadata.version, // Colonne M
|
||||||
'', '', '', '', // Colonnes de scores détecteurs (réservées)
|
metadata.stage, // Colonne N
|
||||||
JSON.stringify({
|
metadata.stageDescription, // Colonne O
|
||||||
csvData: csvData,
|
metadata.parentArticleId || '', // Colonne P
|
||||||
config: config,
|
'', '', '', '', // Colonnes Q,R,S,T : scores détecteurs (réservées)
|
||||||
stats: metadata,
|
JSON.stringify({ // Colonne U
|
||||||
versionHistory: metadata.versionHistory // Inclure l'historique
|
csvData: { ...csvData, xmlTemplate: undefined, xmlFileName: csvData.xmlFileName },
|
||||||
})
|
config: config,
|
||||||
];
|
stats: metadata,
|
||||||
|
versionHistory: metadata.versionHistory
|
||||||
|
})
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
// Format LEGACY (17 colonnes) : sans version/stage, scores détecteurs à la place
|
||||||
|
row = [
|
||||||
|
metadata.timestamp,
|
||||||
|
metadata.slug,
|
||||||
|
metadata.mc0,
|
||||||
|
metadata.t0,
|
||||||
|
metadata.personality,
|
||||||
|
metadata.antiDetectionLevel,
|
||||||
|
compiledText,
|
||||||
|
metadata.textLength,
|
||||||
|
metadata.wordCount,
|
||||||
|
metadata.elementsCount,
|
||||||
|
metadata.llmUsed,
|
||||||
|
metadata.validationStatus,
|
||||||
|
'', '', '', '', // Colonnes M,N,O,P : scores détecteurs (GPTZero, Originality, CopyLeaks, HumanQuality)
|
||||||
|
JSON.stringify({ // Colonne Q
|
||||||
|
csvData: { ...csvData, xmlTemplate: undefined, xmlFileName: csvData.xmlFileName },
|
||||||
|
config: config,
|
||||||
|
stats: metadata
|
||||||
|
})
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
// DEBUG: Vérifier le slug généré
|
// DEBUG: Vérifier le slug généré
|
||||||
logSh(`💾 Sauvegarde avec slug: "${metadata.slug}" (colonne B)`, 'DEBUG');
|
logSh(`💾 Sauvegarde avec slug: "${metadata.slug}" (colonne B)`, 'DEBUG');
|
||||||
|
|
||||||
// Ajouter la ligne aux données dans la bonne sheet
|
// Ajouter la ligne aux données dans la bonne sheet
|
||||||
const targetRange = config.useVersionedSheet ? 'Generated_Articles_Versioned!A:U' : 'Generated_Articles!A:U';
|
// Forcer le range à A1 pour éviter le décalage horizontal
|
||||||
await sheets.spreadsheets.values.append({
|
const targetRange = config.useVersionedSheet ? 'Generated_Articles_Versioned!A1' : 'Generated_Articles!A1';
|
||||||
|
|
||||||
|
logSh(`🔍 DEBUG APPEND: sheetId=${SHEET_CONFIG.sheetId}, range=${targetRange}, rowLength=${row.length}`, 'INFO');
|
||||||
|
logSh(`🔍 DEBUG ROW PREVIEW: [${row.slice(0, 5).map(c => typeof c === 'string' ? c.substring(0, 50) : c).join(', ')}...]`, 'INFO');
|
||||||
|
|
||||||
|
const appendResult = await sheets.spreadsheets.values.append({
|
||||||
spreadsheetId: SHEET_CONFIG.sheetId,
|
spreadsheetId: SHEET_CONFIG.sheetId,
|
||||||
range: targetRange,
|
range: targetRange,
|
||||||
valueInputOption: 'USER_ENTERED',
|
valueInputOption: 'USER_ENTERED',
|
||||||
|
insertDataOption: 'INSERT_ROWS', // Force l'insertion d'une nouvelle ligne
|
||||||
resource: {
|
resource: {
|
||||||
values: [row]
|
values: [row]
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
logSh(`✅ APPEND SUCCESS: ${appendResult.status} - Updated ${appendResult.data.updates?.updatedCells || 0} cells`, 'INFO');
|
||||||
|
|
||||||
// Récupérer le numéro de ligne pour l'ID article
|
// Récupérer le numéro de ligne pour l'ID article
|
||||||
const targetRangeForId = config.useVersionedSheet ? 'Generated_Articles_Versioned!A:A' : 'Generated_Articles!A:A';
|
const targetRangeForId = config.useVersionedSheet ? 'Generated_Articles_Versioned!A:A' : 'Generated_Articles!A:A';
|
||||||
|
|||||||
@ -10,6 +10,7 @@ const path = require('path');
|
|||||||
|
|
||||||
// Import de la fonction logSh (assumant qu'elle existe dans votre projet Node.js)
|
// Import de la fonction logSh (assumant qu'elle existe dans votre projet Node.js)
|
||||||
const { logSh } = require('./ErrorReporting');
|
const { logSh } = require('./ErrorReporting');
|
||||||
|
const { DigitalOceanTemplates } = require('./batch/DigitalOceanTemplates');
|
||||||
|
|
||||||
// Configuration
|
// Configuration
|
||||||
const CONFIG = {
|
const CONFIG = {
|
||||||
@ -117,12 +118,25 @@ async function readInstructionsData(rowNumber = 2) {
|
|||||||
const xmlTemplateValue = row[8] || '';
|
const xmlTemplateValue = row[8] || '';
|
||||||
let xmlTemplate = xmlTemplateValue;
|
let xmlTemplate = xmlTemplateValue;
|
||||||
let xmlFileName = null;
|
let xmlFileName = null;
|
||||||
|
|
||||||
// Si c'est un nom de fichier, garder le nom ET utiliser un template par défaut
|
// Si c'est un nom de fichier, le récupérer depuis Digital Ocean
|
||||||
if (xmlTemplateValue && xmlTemplateValue.endsWith('.xml') && xmlTemplateValue.length < 100) {
|
if (xmlTemplateValue && xmlTemplateValue.endsWith('.xml') && xmlTemplateValue.length < 100) {
|
||||||
logSh(`🔧 XML filename detected (${xmlTemplateValue}), keeping filename for Digital Ocean`, 'INFO');
|
logSh(`🔧 XML filename detected (${xmlTemplateValue}), fetching from Digital Ocean`, 'INFO');
|
||||||
xmlFileName = xmlTemplateValue; // Garder le nom du fichier pour Digital Ocean
|
xmlFileName = xmlTemplateValue;
|
||||||
xmlTemplate = createDefaultXMLTemplate(); // Template par défaut pour le processing
|
|
||||||
|
// Récupérer le template depuis Digital Ocean
|
||||||
|
try {
|
||||||
|
const doTemplates = new DigitalOceanTemplates();
|
||||||
|
xmlTemplate = await doTemplates.getTemplate(xmlFileName);
|
||||||
|
logSh(`✅ Template ${xmlFileName} récupéré depuis Digital Ocean (${xmlTemplate?.length || 0} chars)`, 'INFO');
|
||||||
|
|
||||||
|
if (!xmlTemplate) {
|
||||||
|
throw new Error('Template vide récupéré');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logSh(`⚠️ Erreur récupération ${xmlFileName} depuis DO: ${error.message}. Fallback template par défaut.`, 'WARNING');
|
||||||
|
xmlTemplate = createDefaultXMLTemplate();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -26,9 +26,12 @@ async function extractElements(xmlTemplate, csvData) {
|
|||||||
// FIX REGEX INSTRUCTIONS - Enlever d'abord les {{variables}} puis chercher {instructions}
|
// FIX REGEX INSTRUCTIONS - Enlever d'abord les {{variables}} puis chercher {instructions}
|
||||||
const withoutVariables = fullMatch.replace(/\{\{[^}]+\}\}/g, '');
|
const withoutVariables = fullMatch.replace(/\{\{[^}]+\}\}/g, '');
|
||||||
const instructionsMatch = withoutVariables.match(/\{([^}]+)\}/);
|
const instructionsMatch = withoutVariables.match(/\{([^}]+)\}/);
|
||||||
|
|
||||||
const tagName = nameMatch ? nameMatch[1].trim() : fullMatch.split('{')[0];
|
let tagName = nameMatch ? nameMatch[1].trim() : fullMatch.split('{')[0];
|
||||||
|
|
||||||
|
// NETTOYAGE: Enlever <strong>, </strong> du nom du tag
|
||||||
|
tagName = tagName.replace(/<\/?strong>/g, '');
|
||||||
|
|
||||||
// TAG PUR (sans variables)
|
// TAG PUR (sans variables)
|
||||||
const pureTag = `|${tagName}|`;
|
const pureTag = `|${tagName}|`;
|
||||||
|
|
||||||
|
|||||||
@ -331,7 +331,9 @@ async function handleModularWorkflow(config = {}) {
|
|||||||
// 🆕 SAUVEGARDE ÉTAPE 1: Génération initiale
|
// 🆕 SAUVEGARDE ÉTAPE 1: Génération initiale
|
||||||
let parentArticleId = null;
|
let parentArticleId = null;
|
||||||
let versionHistory = [];
|
let versionHistory = [];
|
||||||
|
|
||||||
|
logSh(`🔍 DEBUG: saveIntermediateSteps = ${saveIntermediateSteps}`, 'INFO');
|
||||||
|
|
||||||
if (saveIntermediateSteps) {
|
if (saveIntermediateSteps) {
|
||||||
logSh(`💾 SAUVEGARDE v1.0: Génération initiale`, 'INFO');
|
logSh(`💾 SAUVEGARDE v1.0: Génération initiale`, 'INFO');
|
||||||
|
|
||||||
|
|||||||
@ -201,13 +201,29 @@ function parseMissingKeywordsResponse(response, missingElements) {
|
|||||||
logSh(`✓ Mot-clé généré [${elementName}]: "${generatedKeyword}"`, 'DEBUG');
|
logSh(`✓ Mot-clé généré [${elementName}]: "${generatedKeyword}"`, 'DEBUG');
|
||||||
}
|
}
|
||||||
|
|
||||||
// FATAL si parsing partiel
|
// VALIDATION: Vérifier qu'on a au moins récupéré des résultats (tolérer doublons)
|
||||||
if (Object.keys(results).length < missingElements.length) {
|
const uniqueNames = [...new Set(missingElements.map(e => e.name))];
|
||||||
const manquants = missingElements.length - Object.keys(results).length;
|
const parsedCount = Object.keys(results).length;
|
||||||
logSh(`❌ FATAL: Parsing mots-clés partiel - ${manquants}/${missingElements.length} manquants`, 'ERROR');
|
|
||||||
throw new Error(`FATAL: Parsing mots-clés incomplet (${manquants}/${missingElements.length} manquants) - arrêt du workflow`);
|
if (parsedCount === 0) {
|
||||||
|
logSh(`❌ FATAL: Aucun mot-clé parsé`, 'ERROR');
|
||||||
|
throw new Error(`FATAL: Parsing mots-clés échoué complètement - arrêt du workflow`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Warning si doublons détectés (mais on continue)
|
||||||
|
if (missingElements.length > uniqueNames.length) {
|
||||||
|
const doublonsCount = missingElements.length - uniqueNames.length;
|
||||||
|
logSh(`⚠️ ${doublonsCount} doublons détectés dans les tags XML (${uniqueNames.length} tags uniques)`, 'WARNING');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vérifier qu'on a au moins autant de résultats que de tags uniques
|
||||||
|
if (parsedCount < uniqueNames.length) {
|
||||||
|
const manquants = uniqueNames.length - parsedCount;
|
||||||
|
logSh(`❌ FATAL: Parsing incomplet - ${manquants}/${uniqueNames.length} tags uniques non parsés`, 'ERROR');
|
||||||
|
throw new Error(`FATAL: Parsing mots-clés incomplet (${manquants}/${uniqueNames.length} manquants) - arrêt du workflow`);
|
||||||
|
}
|
||||||
|
|
||||||
|
logSh(`✅ ${parsedCount} mots-clés parsés pour ${uniqueNames.length} tags uniques (${missingElements.length} éléments total)`, 'INFO');
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -91,7 +91,58 @@ class StepExecutor {
|
|||||||
// ========================================
|
// ========================================
|
||||||
// EXÉCUTEURS SPÉCIFIQUES
|
// EXÉCUTEURS SPÉCIFIQUES
|
||||||
// ========================================
|
// ========================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construire la structure de contenu depuis la hiérarchie réelle
|
||||||
|
*/
|
||||||
|
buildContentStructureFromHierarchy(inputData, hierarchy) {
|
||||||
|
const contentStructure = {};
|
||||||
|
|
||||||
|
// Si hiérarchie disponible, l'utiliser
|
||||||
|
if (hierarchy && Object.keys(hierarchy).length > 0) {
|
||||||
|
logSh(`🔍 Hiérarchie debug: ${Object.keys(hierarchy).length} sections`, 'DEBUG');
|
||||||
|
logSh(`🔍 Première section sample: ${JSON.stringify(Object.values(hierarchy)[0]).substring(0, 200)}`, 'DEBUG');
|
||||||
|
|
||||||
|
Object.entries(hierarchy).forEach(([path, section]) => {
|
||||||
|
// Générer pour le titre si présent
|
||||||
|
if (section.title && section.title.originalElement) {
|
||||||
|
const tag = section.title.originalElement.name;
|
||||||
|
const instruction = section.title.instructions || section.title.originalElement.instructions || `Rédige un titre pour ${inputData.mc0}`;
|
||||||
|
contentStructure[tag] = instruction;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Générer pour le texte si présent
|
||||||
|
if (section.text && section.text.originalElement) {
|
||||||
|
const tag = section.text.originalElement.name;
|
||||||
|
const instruction = section.text.instructions || section.text.originalElement.instructions || `Rédige du contenu sur ${inputData.mc0}`;
|
||||||
|
contentStructure[tag] = instruction;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Générer pour les questions FAQ si présentes
|
||||||
|
if (section.questions && section.questions.length > 0) {
|
||||||
|
section.questions.forEach(q => {
|
||||||
|
if (q.originalElement) {
|
||||||
|
const tag = q.originalElement.name;
|
||||||
|
const instruction = q.instructions || q.originalElement.instructions || `Rédige une question/réponse FAQ sur ${inputData.mc0}`;
|
||||||
|
contentStructure[tag] = instruction;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
logSh(`🏗️ Structure depuis hiérarchie: ${Object.keys(contentStructure).length} éléments`, 'DEBUG');
|
||||||
|
} else {
|
||||||
|
// Fallback: structure générique si pas de hiérarchie
|
||||||
|
logSh(`⚠️ Pas de hiérarchie, utilisation structure générique`, 'WARNING');
|
||||||
|
contentStructure['Titre_H1'] = `Rédige un titre H1 accrocheur et optimisé SEO sur ${inputData.mc0}`;
|
||||||
|
contentStructure['Introduction'] = `Rédige une introduction engageante qui présente ${inputData.mc0}`;
|
||||||
|
contentStructure['Contenu_Principal'] = `Développe le contenu principal détaillé sur ${inputData.mc0} avec des informations utiles et techniques`;
|
||||||
|
contentStructure['Conclusion'] = `Rédige une conclusion percutante qui encourage à l'action pour ${inputData.mc0}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return contentStructure;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute Initial Generation
|
* Execute Initial Generation
|
||||||
*/
|
*/
|
||||||
@ -100,19 +151,18 @@ class StepExecutor {
|
|||||||
const { InitialGenerationLayer } = require('./generation/InitialGeneration');
|
const { InitialGenerationLayer } = require('./generation/InitialGeneration');
|
||||||
|
|
||||||
logSh('🎯 Démarrage Génération Initiale', 'DEBUG');
|
logSh('🎯 Démarrage Génération Initiale', 'DEBUG');
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
temperature: options.temperature || 0.7,
|
temperature: options.temperature || 0.7,
|
||||||
maxTokens: options.maxTokens || 4000
|
maxTokens: options.maxTokens || 4000
|
||||||
};
|
};
|
||||||
|
|
||||||
// Créer la structure de contenu à générer
|
// Créer la structure de contenu à générer depuis la hiérarchie réelle
|
||||||
const contentStructure = {
|
// La hiérarchie peut être dans inputData.hierarchy OU options.hierarchy
|
||||||
'Titre_H1': `Rédige un titre H1 accrocheur et optimisé SEO sur ${inputData.mc0}`,
|
const hierarchy = options.hierarchy || inputData.hierarchy;
|
||||||
'Introduction': `Rédige une introduction engageante qui présente ${inputData.mc0}`,
|
const contentStructure = this.buildContentStructureFromHierarchy(inputData, hierarchy);
|
||||||
'Contenu_Principal': `Développe le contenu principal détaillé sur ${inputData.mc0} avec des informations utiles et techniques`,
|
|
||||||
'Conclusion': `Rédige une conclusion percutante qui encourage à l'action pour ${inputData.mc0}`
|
logSh(`📊 Structure construite: ${Object.keys(contentStructure).length} éléments depuis hiérarchie`, 'DEBUG');
|
||||||
};
|
|
||||||
|
|
||||||
const initialGenerator = new InitialGenerationLayer();
|
const initialGenerator = new InitialGenerationLayer();
|
||||||
const result = await initialGenerator.apply(contentStructure, {
|
const result = await initialGenerator.apply(contentStructure, {
|
||||||
|
|||||||
@ -3,11 +3,13 @@
|
|||||||
// Responsabilité: Récupération et cache des templates XML depuis DigitalOcean Spaces
|
// Responsabilité: Récupération et cache des templates XML depuis DigitalOcean Spaces
|
||||||
// ========================================
|
// ========================================
|
||||||
|
|
||||||
|
require('dotenv').config();
|
||||||
const { logSh } = require('../ErrorReporting');
|
const { logSh } = require('../ErrorReporting');
|
||||||
const { tracer } = require('../trace');
|
const { tracer } = require('../trace');
|
||||||
const fs = require('fs').promises;
|
const fs = require('fs').promises;
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const axios = require('axios');
|
const axios = require('axios');
|
||||||
|
const AWS = require('aws-sdk');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DIGITAL OCEAN TEMPLATES MANAGER
|
* DIGITAL OCEAN TEMPLATES MANAGER
|
||||||
@ -17,12 +19,22 @@ class DigitalOceanTemplates {
|
|||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.cacheDir = path.join(__dirname, '../../cache/templates');
|
this.cacheDir = path.join(__dirname, '../../cache/templates');
|
||||||
|
|
||||||
|
// Extraire bucket du endpoint si présent (ex: https://autocollant.fra1.digitaloceanspaces.com)
|
||||||
|
let endpoint = process.env.DO_ENDPOINT || process.env.DO_SPACES_ENDPOINT || 'https://fra1.digitaloceanspaces.com';
|
||||||
|
let bucket = process.env.DO_BUCKET_NAME || process.env.DO_SPACES_BUCKET || 'autocollant';
|
||||||
|
|
||||||
|
// Si endpoint contient le bucket, le retirer
|
||||||
|
if (endpoint.includes(`${bucket}.`)) {
|
||||||
|
endpoint = endpoint.replace(`${bucket}.`, '');
|
||||||
|
}
|
||||||
|
|
||||||
this.config = {
|
this.config = {
|
||||||
endpoint: process.env.DO_SPACES_ENDPOINT || 'https://fra1.digitaloceanspaces.com',
|
endpoint: endpoint,
|
||||||
bucket: process.env.DO_SPACES_BUCKET || 'autocollant',
|
bucket: bucket,
|
||||||
region: process.env.DO_SPACES_REGION || 'fra1',
|
region: process.env.DO_REGION || process.env.DO_SPACES_REGION || 'fra1',
|
||||||
accessKey: process.env.DO_SPACES_KEY,
|
accessKey: process.env.DO_ACCESS_KEY_ID || process.env.DO_SPACES_KEY,
|
||||||
secretKey: process.env.DO_SPACES_SECRET,
|
secretKey: process.env.DO_SECRET_ACCESS_KEY || process.env.DO_SPACES_SECRET,
|
||||||
timeout: 10000 // 10 secondes
|
timeout: 10000 // 10 secondes
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -81,7 +93,7 @@ class DigitalOceanTemplates {
|
|||||||
* Récupère un template XML (avec cache et fallback)
|
* Récupère un template XML (avec cache et fallback)
|
||||||
*/
|
*/
|
||||||
async getTemplate(filename) {
|
async getTemplate(filename) {
|
||||||
return tracer.run('DigitalOceanTemplates.getTemplate', { filename }, async () => {
|
return tracer.run('DigitalOceanTemplates.getTemplate', async () => {
|
||||||
if (!filename) {
|
if (!filename) {
|
||||||
throw new Error('Nom de fichier template requis');
|
throw new Error('Nom de fichier template requis');
|
||||||
}
|
}
|
||||||
@ -141,35 +153,37 @@ class DigitalOceanTemplates {
|
|||||||
* Récupère depuis Digital Ocean Spaces
|
* Récupère depuis Digital Ocean Spaces
|
||||||
*/
|
*/
|
||||||
async fetchFromDigitalOcean(filename) {
|
async fetchFromDigitalOcean(filename) {
|
||||||
return tracer.run('DigitalOceanTemplates.fetchFromDigitalOcean', { filename }, async () => {
|
return tracer.run('DigitalOceanTemplates.fetchFromDigitalOcean', async () => {
|
||||||
const url = `${this.config.endpoint}/${this.config.bucket}/templates/${filename}`;
|
const fileKey = `wp-content/XML/${filename}`;
|
||||||
|
|
||||||
logSh(`🌊 Récupération DO: ${url}`, 'DEBUG');
|
logSh(`🌊 Récupération DO avec authentification S3: ${fileKey}`, 'DEBUG');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Utiliser une requête simple sans authentification S3 complexe
|
// Configuration S3 pour Digital Ocean Spaces
|
||||||
// Digital Ocean Spaces peut être configuré pour accès public aux templates
|
const s3 = new AWS.S3({
|
||||||
const response = await axios.get(url, {
|
endpoint: this.config.endpoint,
|
||||||
timeout: this.config.timeout,
|
accessKeyId: this.config.accessKey,
|
||||||
responseType: 'text',
|
secretAccessKey: this.config.secretKey,
|
||||||
headers: {
|
region: this.config.region,
|
||||||
'Accept': 'application/xml, text/xml, text/plain'
|
s3ForcePathStyle: false,
|
||||||
}
|
signatureVersion: 'v4'
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.status === 200 && response.data) {
|
const params = {
|
||||||
logSh(`✅ Template ${filename} récupéré (${response.data.length} chars)`, 'DEBUG');
|
Bucket: this.config.bucket,
|
||||||
return response.data;
|
Key: fileKey
|
||||||
}
|
};
|
||||||
|
|
||||||
throw new Error(`Réponse invalide: ${response.status}`);
|
logSh(`🔑 S3 getObject: bucket=${this.config.bucket}, key=${fileKey}`, 'DEBUG');
|
||||||
|
|
||||||
|
const data = await s3.getObject(params).promise();
|
||||||
|
const template = data.Body.toString('utf-8');
|
||||||
|
|
||||||
|
logSh(`✅ Template ${filename} récupéré depuis DO (${template.length} chars)`, 'INFO');
|
||||||
|
return template;
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.response) {
|
logSh(`❌ Digital Ocean S3 error: ${error.message} (code: ${error.code})`, 'WARNING');
|
||||||
logSh(`❌ Digital Ocean error ${error.response.status}: ${error.response.statusText}`, 'WARNING');
|
|
||||||
} else {
|
|
||||||
logSh(`❌ Digital Ocean network error: ${error.message}`, 'WARNING');
|
|
||||||
}
|
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user