+ adds server.ts
+ implements FeedController + feedRoutes.ts + utils/logger add server msgs + add port to src/config
This commit is contained in:
		@@ -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';
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
    });
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										23
									
								
								src/routes/feedRoutes.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src/routes/feedRoutes.ts
									
									
									
									
									
										Normal file
									
								
							@@ -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 };
 | 
			
		||||
@@ -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;
 | 
			
		||||
@@ -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;
 | 
			
		||||
		Reference in New Issue
	
	Block a user