import express from 'express'; import cors from 'cors'; import { getMangaInfo, getMangaChapters, getChapterImages, getPopularMangas } from './scraper.js'; const app = express(); const PORT = process.env.PORT || 3000; // Middleware app.use(cors()); app.use(express.json()); // Cache simple (en memoria, se puede mejorar con Redis) const cache = new Map(); const CACHE_TTL = 5 * 60 * 1000; // 5 minutos function getCached(key) { const cached = cache.get(key); if (cached && Date.now() - cached.timestamp < CACHE_TTL) { return cached.data; } return null; } function setCache(key, data) { cache.set(key, { data, timestamp: Date.now() }); } // Routes /** * @route GET /api/health * @desc Health check endpoint */ app.get('/api/health', (req, res) => { res.json({ status: 'ok', message: 'MangaReader API is running' }); }); /** * @route GET /api/mangas/popular * @desc Obtener lista de mangas populares * @query ?force=true - Ignorar cache */ app.get('/api/mangas/popular', async (req, res) => { try { const { force } = req.query; if (!force) { const cached = getCached('popular'); if (cached) { return res.json(cached); } } const mangas = await getPopularMangas(); setCache('popular', mangas); res.json(mangas); } catch (error) { console.error('Error getting popular mangas:', error); res.status(500).json({ error: 'Error obteniendo mangas populares', message: error.message }); } }); /** * @route GET /api/manga/:slug * @desc Obtener información de un manga específico * @param slug - Slug del manga (ej: one-piece_1695365223767) */ app.get('/api/manga/:slug', async (req, res) => { try { const { slug } = req.params; const cacheKey = `manga_${slug}`; const cached = getCached(cacheKey); if (cached) { return res.json(cached); } const manga = await getMangaInfo(slug); setCache(cacheKey, manga); res.json(manga); } catch (error) { console.error('Error getting manga info:', error); res.status(500).json({ error: 'Error obteniendo información del manga', message: error.message }); } }); /** * @route GET /api/manga/:slug/chapters * @desc Obtener lista de capítulos de un manga * @param slug - Slug del manga */ app.get('/api/manga/:slug/chapters', async (req, res) => { try { const { slug } = req.params; const cacheKey = `chapters_${slug}`; const cached = getCached(cacheKey); if (cached) { return res.json(cached); } const chapters = await getMangaChapters(slug); setCache(cacheKey, chapters); res.json(chapters); } catch (error) { console.error('Error getting manga chapters:', error); res.status(500).json({ error: 'Error obteniendo capítulos del manga', message: error.message }); } }); /** * @route GET /api/chapter/:slug/images * @desc Obtener imágenes de un capítulo * @param slug - Slug del capítulo (ej: one-piece_1695365223767-1172) * @query ?force=true - Ignorar cache */ app.get('/api/chapter/:slug/images', async (req, res) => { try { const { slug } = req.params; const { force } = req.query; const cacheKey = `images_${slug}`; if (!force) { const cached = getCached(cacheKey); if (cached) { return res.json(cached); } } const images = await getChapterImages(slug); setCache(cacheKey, images); res.json(images); } catch (error) { console.error('Error getting chapter images:', error); res.status(500).json({ error: 'Error obteniendo imágenes del capítulo', message: error.message }); } }); /** * @route GET /api/manga/:slug/full * @desc Obtener info completa del manga con capítulos * @param slug - Slug del manga */ app.get('/api/manga/:slug/full', async (req, res) => { try { const { slug } = req.params; const cacheKey = `full_${slug}`; const cached = getCached(cacheKey); if (cached) { return res.json(cached); } // Obtener info y capítulos en paralelo const [manga, chapters] = await Promise.all([ getMangaInfo(slug), getMangaChapters(slug) ]); const fullData = { ...manga, chapters: chapters }; setCache(cacheKey, fullData); res.json(fullData); } catch (error) { console.error('Error getting full manga data:', error); res.status(500).json({ error: 'Error obteniendo datos completos del manga', message: error.message }); } }); // Error handler app.use((err, req, res, next) => { console.error('Unhandled error:', err); res.status(500).json({ error: 'Error interno del servidor', message: err.message }); }); // 404 handler app.use((req, res) => { res.status(404).json({ error: 'Endpoint no encontrado', path: req.path }); }); // Start server app.listen(PORT, () => { console.log(`🚀 MangaReader API corriendo en puerto ${PORT}`); console.log(`📚 API disponible en: http://localhost:${PORT}/api`); console.log(`\nEndpoints disponibles:`); console.log(` GET /api/health`); console.log(` GET /api/mangas/popular`); console.log(` GET /api/manga/:slug`); console.log(` GET /api/manga/:slug/chapters`); console.log(` GET /api/chapter/:slug/images`); console.log(` GET /api/manga/:slug/full`); });