+ 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