V1
This commit is contained in:
255
src/app.js
Normal file
255
src/app.js
Normal file
@@ -0,0 +1,255 @@
|
||||
import express from 'express';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import yaml from 'js-yaml';
|
||||
import Emusks from './emusks-local/index.js';
|
||||
|
||||
// --- GESTION DES CHEMINS (ES Modules) ---
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
// --- LECTURE DE LA CONFIGURATION (YAML ou JSON) ---
|
||||
let config = {};
|
||||
const configPaths = [
|
||||
path.join(__dirname, 'config.yml'),
|
||||
path.join(__dirname, 'config.yaml'),
|
||||
path.join(__dirname, '../config.yml'),
|
||||
path.join(process.cwd(), 'config.yml')
|
||||
];
|
||||
|
||||
for (const configPath of configPaths) {
|
||||
try {
|
||||
if (fs.existsSync(configPath)) {
|
||||
const fileContents = fs.readFileSync(configPath, 'utf8');
|
||||
config = yaml.load(fileContents);
|
||||
console.log(`✅ Configuration chargée depuis : ${configPath}`);
|
||||
break;
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn(`⚠️ Erreur lecture config ${configPath}: ${e.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(config).length === 0) {
|
||||
console.warn("⚠️ Aucun fichier de configuration trouvé, utilisation des variables d'environnement.");
|
||||
}
|
||||
|
||||
// --- CONFIGURATION ---
|
||||
const API_SECRET = config.api_secret || process.env.API_SECRET || 'secret_par_defaut';
|
||||
const PORT = config.port || process.env.PORT || 3000;
|
||||
const NODE_ENV = process.env.NODE_ENV || 'development';
|
||||
|
||||
console.log(`🔧 Environment: ${NODE_ENV}`);
|
||||
console.log(`🔐 API Secret configured: ${API_SECRET ? 'Yes' : 'No'}`);
|
||||
console.log(`🚀 Port: ${PORT}`);
|
||||
|
||||
// --- INITIALISATION EXPRESS ---
|
||||
const app = express();
|
||||
|
||||
// Middleware pour parser le JSON (avec limite augmentée si besoin)
|
||||
app.use(express.json({ limit: '10mb' }));
|
||||
|
||||
// Logging des requêtes (seulement en dev)
|
||||
if (NODE_ENV === 'development') {
|
||||
app.use((req, res, next) => {
|
||||
console.log(`[${new Date().toISOString()}] ${req.method} ${req.path}`);
|
||||
next();
|
||||
});
|
||||
}
|
||||
|
||||
// --- ENDPOINT DE SANTÉ (Health Check) ---
|
||||
app.get('/health', (req, res) => {
|
||||
res.json({
|
||||
status: 'ok',
|
||||
service: 'twitter-worker',
|
||||
timestamp: new Date().toISOString(),
|
||||
environment: NODE_ENV
|
||||
});
|
||||
});
|
||||
|
||||
// --- ENDPOINT RACINE ---
|
||||
app.get('/', (req, res) => {
|
||||
res.json({
|
||||
service: 'Twitter Worker API',
|
||||
version: '1.0.0',
|
||||
endpoints: {
|
||||
health: 'GET /health',
|
||||
call: 'POST /api/twitter/call'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// --- MIDDLEWARE DE SÉCURITÉ ---
|
||||
const authMiddleware = (req, res, next) => {
|
||||
const authHeader = req.headers.authorization;
|
||||
|
||||
if (!authHeader) {
|
||||
return res.status(401).json({
|
||||
error: 'Non autorisé. Header Authorization manquant.',
|
||||
required: 'Bearer <API_SECRET>'
|
||||
});
|
||||
}
|
||||
|
||||
const [scheme, token] = authHeader.split(' ');
|
||||
|
||||
if (scheme !== 'Bearer' || token !== API_SECRET) {
|
||||
console.warn(`⚠️ Tentative d'accès non autorisé depuis ${req.ip}`);
|
||||
return res.status(401).json({
|
||||
error: 'Non autorisé. Clé API invalide.'
|
||||
});
|
||||
}
|
||||
|
||||
next();
|
||||
};
|
||||
|
||||
// Appliquer l'authentification uniquement sur les routes API
|
||||
app.use('/api', authMiddleware);
|
||||
|
||||
// --- ENDPOINT PROXY DYNAMIQUE ---
|
||||
app.post('/api/twitter/call', async (req, res) => {
|
||||
const { auth_token, proxy, domain, method, args } = req.body;
|
||||
|
||||
// Validation des paramètres requis
|
||||
if (!auth_token) {
|
||||
return res.status(400).json({
|
||||
error: "auth_token requis.",
|
||||
hint: "Le token d'authentification Twitter est obligatoire."
|
||||
});
|
||||
}
|
||||
|
||||
if (!domain || !method) {
|
||||
return res.status(400).json({
|
||||
error: "domain et method requis.",
|
||||
hint: "Exemple: domain='twitter', method='search'"
|
||||
});
|
||||
}
|
||||
|
||||
console.log(`📡 Requête reçue: ${domain}.${method}()`);
|
||||
|
||||
let client = null;
|
||||
|
||||
try {
|
||||
// Initialisation du client Emusks
|
||||
client = new Emusks();
|
||||
|
||||
// Connexion avec les paramètres fournis
|
||||
await client.login({
|
||||
auth_token,
|
||||
proxy,
|
||||
client: "web"
|
||||
});
|
||||
|
||||
// Vérification que la méthode existe
|
||||
if (!client[domain] || typeof client[domain][method] !== 'function') {
|
||||
return res.status(400).json({
|
||||
error: `La méthode client.${domain}.${method}() n'existe pas.`,
|
||||
available_domains: Object.keys(client).filter(k => typeof client[k] === 'object')
|
||||
});
|
||||
}
|
||||
|
||||
// Exécution de la méthode
|
||||
const startTime = Date.now();
|
||||
const result = await client[domain][method](...(args || []));
|
||||
const duration = Date.now() - startTime;
|
||||
|
||||
console.log(`✅ Succès: ${domain}.${method}() en ${duration}ms`);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: result,
|
||||
meta: {
|
||||
duration_ms: duration,
|
||||
timestamp: new Date().toISOString()
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
const duration = Date.now() - (startTime || Date.now());
|
||||
|
||||
console.error(`❌ [ERREUR] Action: ${domain}.${method}`);
|
||||
console.error(` Message: ${error.message}`);
|
||||
console.error(` Duration: ${duration}ms`);
|
||||
|
||||
if (NODE_ENV === 'development') {
|
||||
console.error(error.stack);
|
||||
}
|
||||
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error.message || "Erreur interne du serveur",
|
||||
details: error.response?.data || error.response?.body || null,
|
||||
meta: {
|
||||
duration_ms: duration,
|
||||
timestamp: new Date().toISOString()
|
||||
}
|
||||
});
|
||||
} finally {
|
||||
// Nettoyage si nécessaire
|
||||
if (client && typeof client.logout === 'function') {
|
||||
// optionnel: await client.logout();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// --- GESTION DES ERREURS 404 ---
|
||||
app.use((req, res) => {
|
||||
res.status(404).json({
|
||||
error: 'Endpoint non trouvé',
|
||||
path: req.path,
|
||||
method: req.method,
|
||||
available_endpoints: ['GET /', 'GET /health', 'POST /api/twitter/call']
|
||||
});
|
||||
});
|
||||
|
||||
// --- GESTION DES ERREURS GLOBALES ---
|
||||
app.use((err, req, res, next) => {
|
||||
console.error('🔥 Erreur globale:', err.message);
|
||||
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Erreur interne du serveur',
|
||||
message: NODE_ENV === 'development' ? err.message : undefined
|
||||
});
|
||||
});
|
||||
|
||||
// --- DÉMARRAGE DU SERVEUR ---
|
||||
const server = app.listen(PORT, () => {
|
||||
console.log('');
|
||||
console.log('╔════════════════════════════════════════════╗');
|
||||
console.log('║ 🐦 Twitter Worker API Démarrée ║');
|
||||
console.log('╠════════════════════════════════════════════╣');
|
||||
console.log(`║ 🌍 URL: http://localhost:${PORT} ║`);
|
||||
console.log(`║ 🔐 Auth: Bearer ${API_SECRET.substring(0, 8)}... ║`);
|
||||
console.log(`║ 🚀 Env: ${NODE_ENV.padEnd(28)}║`);
|
||||
console.log('╚════════════════════════════════════════════╝');
|
||||
console.log('');
|
||||
});
|
||||
|
||||
// --- GESTION DES SIGNAUX POUR ARRÊT PROPRE ---
|
||||
process.on('SIGTERM', () => {
|
||||
console.log('📶 Signal SIGTERM reçu, fermeture en cours...');
|
||||
server.close(() => {
|
||||
console.log('✅ Serveur fermé proprement');
|
||||
process.exit(0);
|
||||
});
|
||||
});
|
||||
|
||||
process.on('SIGINT', () => {
|
||||
console.log('📶 Signal SIGINT reçu (Ctrl+C), fermeture en cours...');
|
||||
server.close(() => {
|
||||
console.log('✅ Serveur fermé proprement');
|
||||
process.exit(0);
|
||||
});
|
||||
});
|
||||
|
||||
// Gestion des erreurs non capturées
|
||||
process.on('uncaughtException', (err) => {
|
||||
console.error('💥 Exception non capturée:', err);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
process.on('unhandledRejection', (reason, promise) => {
|
||||
console.error('💥 Rejection non gérée:', reason);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user