- APIController.js: Full RESTful API with articles, projects, templates endpoints - Real HTTP integration tests with live server validation - Unit tests with proper mocking and error handling - API documentation with examples and usage patterns - Enhanced audit tool supporting HTML, npm scripts, dynamic imports - Cleaned 28 dead files identified by enhanced audit analysis - Google Sheets integration fully validated in test environment
435 lines
11 KiB
JavaScript
435 lines
11 KiB
JavaScript
/**
|
|
* Contrôleur API RESTful pour SEO Generator
|
|
* Centralise toute la logique API métier
|
|
*/
|
|
|
|
const { logSh } = require('./ErrorReporting');
|
|
const { handleFullWorkflow } = require('./Main');
|
|
const { getPersonalities, readInstructionsData } = require('./BrainConfig');
|
|
const { getStoredArticle, getRecentArticles } = require('./ArticleStorage');
|
|
|
|
class APIController {
|
|
constructor() {
|
|
this.articles = new Map(); // Cache articles en mémoire
|
|
this.projects = new Map(); // Cache projets
|
|
this.templates = new Map(); // Cache templates
|
|
}
|
|
|
|
// ========================================
|
|
// GESTION ARTICLES
|
|
// ========================================
|
|
|
|
/**
|
|
* GET /api/articles - Liste tous les articles
|
|
*/
|
|
async getArticles(req, res) {
|
|
try {
|
|
const { limit = 50, offset = 0, project, status } = req.query;
|
|
|
|
logSh(`📋 Récupération articles: limit=${limit}, offset=${offset}`, 'DEBUG');
|
|
|
|
// Récupération depuis Google Sheets
|
|
const articles = await getRecentArticles(parseInt(limit));
|
|
|
|
// Filtrage optionnel
|
|
let filteredArticles = articles;
|
|
if (project) {
|
|
filteredArticles = articles.filter(a => a.project === project);
|
|
}
|
|
if (status) {
|
|
filteredArticles = filteredArticles.filter(a => a.status === status);
|
|
}
|
|
|
|
res.json({
|
|
success: true,
|
|
data: {
|
|
articles: filteredArticles.slice(offset, offset + limit),
|
|
total: filteredArticles.length,
|
|
limit: parseInt(limit),
|
|
offset: parseInt(offset)
|
|
},
|
|
timestamp: new Date().toISOString()
|
|
});
|
|
|
|
} catch (error) {
|
|
logSh(`❌ Erreur récupération articles: ${error.message}`, 'ERROR');
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Erreur lors de la récupération des articles',
|
|
message: error.message
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* GET /api/articles/:id - Récupère un article spécifique
|
|
*/
|
|
async getArticle(req, res) {
|
|
try {
|
|
const { id } = req.params;
|
|
const { format = 'json' } = req.query || {};
|
|
|
|
logSh(`📄 Récupération article ID: ${id}`, 'DEBUG');
|
|
|
|
const article = await getStoredArticle(id);
|
|
|
|
if (!article) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
error: 'Article non trouvé',
|
|
id
|
|
});
|
|
}
|
|
|
|
// Format de réponse
|
|
if (format === 'html') {
|
|
res.setHeader('Content-Type', 'text/html; charset=utf-8');
|
|
res.send(article.htmlContent || article.content);
|
|
} else if (format === 'text') {
|
|
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
|
|
res.send(article.textContent || article.content);
|
|
} else {
|
|
res.json({
|
|
success: true,
|
|
data: article,
|
|
timestamp: new Date().toISOString()
|
|
});
|
|
}
|
|
|
|
} catch (error) {
|
|
logSh(`❌ Erreur récupération article ${req.params.id}: ${error.message}`, 'ERROR');
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Erreur lors de la récupération de l\'article',
|
|
message: error.message
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* POST /api/articles - Créer un nouvel article
|
|
*/
|
|
async createArticle(req, res) {
|
|
try {
|
|
const {
|
|
keyword,
|
|
rowNumber,
|
|
project = 'api',
|
|
config = {},
|
|
template,
|
|
personalityPreference
|
|
} = req.body;
|
|
|
|
// Validation
|
|
if (!keyword && !rowNumber) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
error: 'Mot-clé ou numéro de ligne requis'
|
|
});
|
|
}
|
|
|
|
logSh(`✨ Création article: ${keyword || `ligne ${rowNumber}`}`, 'INFO');
|
|
|
|
// Configuration par défaut
|
|
const workflowConfig = {
|
|
rowNumber: rowNumber || 2,
|
|
source: 'api',
|
|
project,
|
|
selectiveStack: config.selectiveStack || 'standardEnhancement',
|
|
adversarialMode: config.adversarialMode || 'light',
|
|
humanSimulationMode: config.humanSimulationMode || 'none',
|
|
patternBreakingMode: config.patternBreakingMode || 'none',
|
|
personalityPreference,
|
|
template,
|
|
...config
|
|
};
|
|
|
|
// Si mot-clé fourni, créer données temporaires
|
|
if (keyword && !rowNumber) {
|
|
workflowConfig.csvData = {
|
|
mc0: keyword,
|
|
t0: `Guide complet ${keyword}`,
|
|
personality: personalityPreference || { nom: 'Marc', style: 'professionnel' }
|
|
};
|
|
}
|
|
|
|
// Exécution du workflow
|
|
const result = await handleFullWorkflow(workflowConfig);
|
|
|
|
res.status(201).json({
|
|
success: true,
|
|
data: {
|
|
id: result.id || result.slug,
|
|
article: result,
|
|
config: workflowConfig
|
|
},
|
|
message: 'Article créé avec succès',
|
|
timestamp: new Date().toISOString()
|
|
});
|
|
|
|
} catch (error) {
|
|
logSh(`❌ Erreur création article: ${error.message}`, 'ERROR');
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Erreur lors de la création de l\'article',
|
|
message: error.message
|
|
});
|
|
}
|
|
}
|
|
|
|
// ========================================
|
|
// GESTION PROJETS
|
|
// ========================================
|
|
|
|
/**
|
|
* GET /api/projects - Liste tous les projets
|
|
*/
|
|
async getProjects(req, res) {
|
|
try {
|
|
const projects = Array.from(this.projects.values());
|
|
|
|
res.json({
|
|
success: true,
|
|
data: {
|
|
projects,
|
|
total: projects.length
|
|
},
|
|
timestamp: new Date().toISOString()
|
|
});
|
|
|
|
} catch (error) {
|
|
logSh(`❌ Erreur récupération projets: ${error.message}`, 'ERROR');
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Erreur lors de la récupération des projets',
|
|
message: error.message
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* POST /api/projects - Créer un nouveau projet
|
|
*/
|
|
async createProject(req, res) {
|
|
try {
|
|
// Validation body null/undefined
|
|
if (!req.body) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
error: 'Corps de requête requis'
|
|
});
|
|
}
|
|
|
|
const { name, description, config = {} } = req.body;
|
|
|
|
if (!name) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
error: 'Nom du projet requis'
|
|
});
|
|
}
|
|
|
|
const project = {
|
|
id: `project_${Date.now()}`,
|
|
name,
|
|
description,
|
|
config,
|
|
createdAt: new Date().toISOString(),
|
|
articlesCount: 0
|
|
};
|
|
|
|
this.projects.set(project.id, project);
|
|
|
|
logSh(`📁 Projet créé: ${name}`, 'INFO');
|
|
|
|
res.status(201).json({
|
|
success: true,
|
|
data: project,
|
|
message: 'Projet créé avec succès'
|
|
});
|
|
|
|
} catch (error) {
|
|
logSh(`❌ Erreur création projet: ${error.message}`, 'ERROR');
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Erreur lors de la création du projet',
|
|
message: error.message
|
|
});
|
|
}
|
|
}
|
|
|
|
// ========================================
|
|
// GESTION TEMPLATES
|
|
// ========================================
|
|
|
|
/**
|
|
* GET /api/templates - Liste tous les templates
|
|
*/
|
|
async getTemplates(req, res) {
|
|
try {
|
|
const templates = Array.from(this.templates.values());
|
|
|
|
res.json({
|
|
success: true,
|
|
data: {
|
|
templates,
|
|
total: templates.length
|
|
},
|
|
timestamp: new Date().toISOString()
|
|
});
|
|
|
|
} catch (error) {
|
|
logSh(`❌ Erreur récupération templates: ${error.message}`, 'ERROR');
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Erreur lors de la récupération des templates',
|
|
message: error.message
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* POST /api/templates - Créer un nouveau template
|
|
*/
|
|
async createTemplate(req, res) {
|
|
try {
|
|
const { name, content, description, category = 'custom' } = req.body;
|
|
|
|
if (!name || !content) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
error: 'Nom et contenu du template requis'
|
|
});
|
|
}
|
|
|
|
const template = {
|
|
id: `template_${Date.now()}`,
|
|
name,
|
|
content,
|
|
description,
|
|
category,
|
|
createdAt: new Date().toISOString()
|
|
};
|
|
|
|
this.templates.set(template.id, template);
|
|
|
|
logSh(`📋 Template créé: ${name}`, 'INFO');
|
|
|
|
res.status(201).json({
|
|
success: true,
|
|
data: template,
|
|
message: 'Template créé avec succès'
|
|
});
|
|
|
|
} catch (error) {
|
|
logSh(`❌ Erreur création template: ${error.message}`, 'ERROR');
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Erreur lors de la création du template',
|
|
message: error.message
|
|
});
|
|
}
|
|
}
|
|
|
|
// ========================================
|
|
// CONFIGURATION & MONITORING
|
|
// ========================================
|
|
|
|
/**
|
|
* GET /api/config/personalities - Configuration personnalités
|
|
*/
|
|
async getPersonalitiesConfig(req, res) {
|
|
try {
|
|
const personalities = await getPersonalities();
|
|
|
|
res.json({
|
|
success: true,
|
|
data: {
|
|
personalities,
|
|
total: personalities.length
|
|
},
|
|
timestamp: new Date().toISOString()
|
|
});
|
|
|
|
} catch (error) {
|
|
logSh(`❌ Erreur config personnalités: ${error.message}`, 'ERROR');
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Erreur lors de la récupération des personnalités',
|
|
message: error.message
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* GET /api/health - Health check
|
|
*/
|
|
async getHealth(req, res) {
|
|
try {
|
|
const health = {
|
|
status: 'healthy',
|
|
timestamp: new Date().toISOString(),
|
|
version: '1.0.0',
|
|
uptime: process.uptime(),
|
|
memory: process.memoryUsage(),
|
|
environment: process.env.NODE_ENV || 'development'
|
|
};
|
|
|
|
res.json({
|
|
success: true,
|
|
data: health
|
|
});
|
|
|
|
} catch (error) {
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Health check failed',
|
|
message: error.message
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* GET /api/metrics - Métriques système
|
|
*/
|
|
async getMetrics(req, res) {
|
|
try {
|
|
const metrics = {
|
|
articles: {
|
|
total: this.articles.size,
|
|
recent: Array.from(this.articles.values()).filter(
|
|
a => new Date(a.createdAt) > new Date(Date.now() - 24 * 60 * 60 * 1000)
|
|
).length
|
|
},
|
|
projects: {
|
|
total: this.projects.size
|
|
},
|
|
templates: {
|
|
total: this.templates.size
|
|
},
|
|
system: {
|
|
uptime: process.uptime(),
|
|
memory: process.memoryUsage(),
|
|
platform: process.platform,
|
|
nodeVersion: process.version
|
|
}
|
|
};
|
|
|
|
res.json({
|
|
success: true,
|
|
data: metrics,
|
|
timestamp: new Date().toISOString()
|
|
});
|
|
|
|
} catch (error) {
|
|
logSh(`❌ Erreur métriques: ${error.message}`, 'ERROR');
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Erreur lors de la récupération des métriques',
|
|
message: error.message
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
module.exports = { APIController }; |