seo-generator-server/lib/APIController.js
StillHammer 5f9ff4941d Complete API system implementation with comprehensive testing
- 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
2025-09-16 11:10:46 +08:00

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 };