From 206b5dbfa5ad209dd1c70eee7ed271fac5b6199a Mon Sep 17 00:00:00 2001 From: albert Date: Mon, 28 Jul 2025 23:07:39 +0200 Subject: [PATCH] + adds server.ts + implements FeedController + feedRoutes.ts + utils/logger add server msgs + add port to src/config --- src/config/config.ts | 5 +- src/controllers/FeedController.ts | 110 +++++++++++++++++++++++++++++- src/routes/feedRoutes.ts | 23 +++++++ src/server.ts | 52 +++++++++++++- src/utils/logger.ts | 11 +++ 5 files changed, 194 insertions(+), 7 deletions(-) create mode 100644 src/routes/feedRoutes.ts diff --git a/src/config/config.ts b/src/config/config.ts index 0e5069c..dec4b91 100644 --- a/src/config/config.ts +++ b/src/config/config.ts @@ -1,6 +1,5 @@ - - export interface IConfig { + port: number; mongodbUri: string; nodeEnv: string; } @@ -8,11 +7,13 @@ export interface IConfig { class Config implements IConfig { private static instance: Config; + public readonly port: number; public readonly mongodbUri: string; public readonly nodeEnv: string; private constructor() { + this.port = parseInt(process.env.PORT || '4000', 10); this.mongodbUri = process.env.MONGODB_URI || 'mongodb://localhost:27017/dailytrends'; this.nodeEnv = process.env.NODE_ENV || 'development'; diff --git a/src/controllers/FeedController.ts b/src/controllers/FeedController.ts index 0fe775e..983a0e3 100644 --- a/src/controllers/FeedController.ts +++ b/src/controllers/FeedController.ts @@ -1,6 +1,112 @@ -// Aquí exportamos una classe que gestiona las peticiones y comunica con los servicios/repositorioes -// thinking out loud: creo que voy a usar hono.dev, usaba express hace tiempo, y veo que es una especie de express moderno compatible con express +import { Context } from 'hono'; +import { IFeedService } from '../services/FeedService.js'; +import { NewsSource } from '../types/Feed.js'; export class FeedController { + private feedService: IFeedService; + + constructor(feedService: IFeedService) { + this.feedService = feedService; + } + + public getAllFeeds = async (c: Context) => { + const query = c.req.query(); + + const feedQuery = { + source: query.source as NewsSource, + category: query.category, + startDate: query.startDate ? new Date(query.startDate) : undefined, + endDate: query.endDate ? new Date(query.endDate) : undefined, + limit: query.limit ? parseInt(query.limit) : 20, + offset: query.offset ? parseInt(query.offset) : 0 + }; + + const result = await this.feedService.getAllFeeds(feedQuery); + + return c.json({ + success: true, + data: result.data, + pagination: result.pagination + }); + }; + + public getTodaysNews = async (c: Context) => { + const feeds = await this.feedService.getTodaysFrontPageNews(); + + return c.json({ + success: true, + data: feeds, + count: feeds.length + }); + }; + + public getFeedById = async (c: Context) => { + const id = c.req.param('id'); + const feed = await this.feedService.getFeedById(id); + + return c.json({ + success: true, + data: feed + }); + }; + + public createFeed = async (c: Context) => { + const body = await c.req.json(); + + const feedData = { + ...body, + source: NewsSource.MANUAL, + publishedAt: body.publishedAt ? new Date(body.publishedAt) : new Date() + }; + + const feed = await this.feedService.createFeed(feedData); + + return c.json({ + success: true, + data: feed, + message: 'Feed creado exitosamente' + }, 201); + }; + + public updateFeed = async (c: Context) => { + const id = c.req.param('id'); + const body = await c.req.json(); + + const updateData = { + ...body, + publishedAt: body.publishedAt ? new Date(body.publishedAt) : undefined + }; + + const feed = await this.feedService.updateFeed(id, updateData); + + return c.json({ + success: true, + data: feed, + message: 'Feed actualizado exitosamente' + }); + }; + + public deleteFeed = async (c: Context) => { + const id = c.req.param('id'); + const deleted = await this.feedService.deleteFeed(id); + + return c.json({ + success: true, + message: 'Feed eliminado exitosamente' + }); + }; + + public getFeedsBySource = async (c: Context) => { + const source = c.req.param('source') as NewsSource; + const limit = parseInt(c.req.query('limit') || '10'); + + const feeds = await this.feedService.getFeedsBySource(source, limit); + + return c.json({ + success: true, + data: feeds, + count: feeds.length + }); + }; } \ No newline at end of file diff --git a/src/routes/feedRoutes.ts b/src/routes/feedRoutes.ts new file mode 100644 index 0000000..9bb4a67 --- /dev/null +++ b/src/routes/feedRoutes.ts @@ -0,0 +1,23 @@ +import { Hono } from 'hono'; +import { FeedController } from '../controllers/FeedController'; +import { FeedService } from '../services/FeedService'; +import FeedRepository from '../repositories/FeedRepository'; + +// Dependency injection setup +const feedRepository = new FeedRepository(); +const feedService = new FeedService(feedRepository); +const feedController = new FeedController(feedService); + +// Create Hono router +const feedRoutes = new Hono(); + +// Feed CRUD routes +feedRoutes.get('/', feedController.getAllFeeds); +feedRoutes.get('/today', feedController.getTodaysNews); +feedRoutes.get('/source/:source', feedController.getFeedsBySource); +feedRoutes.get('/:id', feedController.getFeedById); +feedRoutes.post('/', feedController.createFeed); +feedRoutes.put('/:id', feedController.updateFeed); +feedRoutes.delete('/:id', feedController.deleteFeed); + +export { feedRoutes }; \ No newline at end of file diff --git a/src/server.ts b/src/server.ts index 0a6ca08..7f830ef 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1,9 +1,55 @@ -// server.ts import { Hono } from 'hono'; import { serve } from '@hono/node-server'; +import { cors } from 'hono/cors'; +import { logger } from 'hono/logger'; +import { prettyJSON } from 'hono/pretty-json'; +import { feedRoutes } from './routes/feedRoutes'; +import { DatabaseConnection } from './config/database'; +import { config } from './config/config'; +import { Logger } from './utils/logger'; const app = new Hono(); -app.get('/', (c) => c.text('Hello world')); +// Middleware +app.use('*', cors({ origin: ['http://localhost:3000', 'http://localhost:5173'] })); +app.use('*', logger(), prettyJSON()); -serve({ fetch: app.fetch, port: 3000 }); +app.get('/health', (c) => c.json({ status: 'OK', timestamp: new Date().toISOString() })); + +app.route('/api/v1/feeds', feedRoutes); +app.notFound((c) => c.json({ error: 'Not found' }, 404)); +app.onError((err, c) => { + Logger.error('Error', { error: err }); + return c.json({ error: err.message }, 500); +}); + +async function initializeApp() { + try { + await DatabaseConnection.getInstance().connect(); + Logger.database.connected(); + + serve({ fetch: app.fetch, port: config.port }); + Logger.server.running(config.port); + } catch (error) { + Logger.error('Failed to start server', { error }); + process.exit(1); + } +} + +const shutdown = async () => { + try { + await DatabaseConnection.getInstance().disconnect(); + Logger.database.disconnected(); + process.exit(0); + } catch (error) { + Logger.error('Error during shutdown', { error }); + process.exit(1); + } +}; + +process.on('SIGINT', shutdown); +process.on('SIGTERM', shutdown); + +initializeApp().catch(error => Logger.error('Failed to initialize app', { error })); + +export default app; \ No newline at end of file diff --git a/src/utils/logger.ts b/src/utils/logger.ts index ad98dd7..17c3809 100644 --- a/src/utils/logger.ts +++ b/src/utils/logger.ts @@ -46,6 +46,17 @@ export const Logger = { alreadyConnected: () => logger.info('🔄 Database is already connected'), notConnected: () => logger.warn('⚠️ Database is not connected') }, + + server: { + starting: (port: number, env: string) => { + logger.info(undefined, `🚀 Starting DailyTrends API server on port ${port}`); + logger.info(undefined, `📱 Environment: ${env}`); + logger.info(undefined, `🔗 Health check: http://localhost:${port}/health`); + logger.info(undefined, `📰 API Base URL: http://localhost:${port}/api/v1`); + }, + running: (port: number) => logger.info(undefined, `✅ Server is running on http://localhost:${port}`), + shutdown: (signal: string) => logger.info(undefined, `\n🛑 Received ${signal}, shutting down gracefully...`) + }, }; export default Logger; \ No newline at end of file