merge pull request #5 from aabril/feat/add_endpoints
Define endpoints (implement server.ts)
This commit is contained in:
		
							
								
								
									
										32
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										32
									
								
								README.md
									
									
									
									
									
								
							@@ -1,19 +1,38 @@
 | 
				
			|||||||
# dailytrends
 | 
					# dailytrends
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Tareas a realizar
 | 
				
			||||||
 | 
					- [*]  Crea un proyecto TypeScript con una arquitectura de ficheros que consideres apropiada.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- [*] | Crea un modelo Feed y define sus atributos. 
 | 
				
			||||||
 | 
					      | El origen de datos tiene que ser MongoDB, por lo que puedes usar algún ODM.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- [*] | Define los diferentes endpoints para gestionar los servicios CRUD del modelo Feed. 
 | 
				
			||||||
 | 
					      | Intenta desacoplar las capas del API lo máximo posible.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- [ ] | Crea un “servicio de lectura de feeds” que extraiga por web scraping (no lectura de fuentes RSS) 
 | 
				
			||||||
 | 
					      | en cada uno de los periódicos sus noticias de portada y que las guarde como Feeds. 
 | 
				
			||||||
 | 
					      | Esta es la parte donde más conceptos de orientación a objetos puedes usar y la más “compleja”, ponle especial atención.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					> Otros detalles
 | 
				
			||||||
 | 
					- Representa en un dibujo la arquitectura y las capas de la aplicación.
 | 
				
			||||||
 | 
					- Usa todas las buenas prácticas que conozcas.
 | 
				
			||||||
 | 
					- Demuestra conocimientos en programación orientada a objetos: 
 | 
				
			||||||
 | 
					  - abstracción, encapsulamiento, herencia y polimorfismo.
 | 
				
			||||||
 | 
					  - Haz los tests que consideres necesarios.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Changelog
 | 
					## Changelog
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
- Inicializamos proyecto:
 | 
					- Inicializamos proyecto:
 | 
				
			||||||
    - Usando npm init, con la node@latest (v24.4.1)
 | 
					    - Usando npm init, con la node@latest (v24.4.1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- First part: [#1 PR : feat/project_structure ](https://github.com/aabril/dailytrends/pull/1)
 | 
					- First part: [#1 PR : feat/project_structure ](https://github.com/aabril/dailytrends/pull/1)
 | 
				
			||||||
    - Añadimos dependencias que voy a usar 
 | 
					    - Crea un proyecto TypeScript con una arquitectura de ficheros que consideres apropiada.
 | 
				
			||||||
    - Creo una estructura de directorio inicial
 | 
					      - Añadimos dependencias que voy a usar 
 | 
				
			||||||
    - Añado un primer test (database.test.ts) para jest
 | 
					      - Creo una estructura de directorio inicial
 | 
				
			||||||
 | 
					      - Añado un primer test (database.test.ts) para jest
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Second part: [#2 PR : feat/database_and_feed_model ](https://github.com/aabril/dailytrends/pull/2) && [#4 PR: feat/database_and_feed_model 2nd part](https://github.com/aabril/dailytrends/pull/4)
 | 
					- Second part: [#2 PR : feat/database_and_feed_model ](https://github.com/aabril/dailytrends/pull/2) && [#4 PR: feat/database_and_feed_model 2nd part](https://github.com/aabril/dailytrends/pull/4)
 | 
				
			||||||
 | 
					  - Crea un modelo Feed y define sus atributos. El origen de datos tiene que ser MongoDB, por lo que puedes usar algún ODM.
 | 
				
			||||||
    - Añadimos `moongose` a las dependencias
 | 
					    - Añadimos `moongose` a las dependencias
 | 
				
			||||||
    - Añado un docker-compose con mongo local (luego lo ampliaré para esta propia app)
 | 
					    - Añado un docker-compose con mongo local (luego lo ampliaré para esta propia app)
 | 
				
			||||||
    - Modificar el docker para tenerlo multistage y reducir el tamaño de las imagenes de contenedores
 | 
					    - Modificar el docker para tenerlo multistage y reducir el tamaño de las imagenes de contenedores
 | 
				
			||||||
@@ -24,6 +43,11 @@
 | 
				
			|||||||
    - añadir tests para FeedService & Feed.model 
 | 
					    - añadir tests para FeedService & Feed.model 
 | 
				
			||||||
    - añadir funcionamiento de feed en las diferentes capas
 | 
					    - añadir funcionamiento de feed en las diferentes capas
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Third part: [#5 PR : feat/add_endpoints ](https://github.com/aabril/dailytrends/pull/5)
 | 
				
			||||||
 | 
					  - Define los diferentes endpoints para gestionar los servicios CRUD del modelo Feed. Intenta desacoplar las capas del API lo máximo posible.
 | 
				
			||||||
 | 
					    - reemplazar index por server.ts
 | 
				
			||||||
 | 
					    - implement a basic server.ts in server.ts
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Feed layer abstractions
 | 
					## Feed layer abstractions
 | 
				
			||||||
 | 
					
 | 
				
			||||||
From higher to lower:
 | 
					From higher to lower:
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,12 +3,14 @@ import type { Config } from 'jest';
 | 
				
			|||||||
const config: Config = {
 | 
					const config: Config = {
 | 
				
			||||||
  preset: 'ts-jest',
 | 
					  preset: 'ts-jest',
 | 
				
			||||||
  testEnvironment: 'node',
 | 
					  testEnvironment: 'node',
 | 
				
			||||||
 | 
					  roots: ['<rootDir>/src'],
 | 
				
			||||||
 | 
					  testMatch: ['**/__tests__/**/*.test.ts'],
 | 
				
			||||||
  extensionsToTreatAsEsm: ['.ts'],
 | 
					  extensionsToTreatAsEsm: ['.ts'],
 | 
				
			||||||
  transform: {
 | 
					  transform: {
 | 
				
			||||||
    '^.+\\.ts$': ['ts-jest', { useESM: true }],
 | 
					    '^.+\.ts$': ['ts-jest', { useESM: true }],
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  moduleNameMapper: {
 | 
					  moduleNameMapper: {
 | 
				
			||||||
    '^(\\.{1,2}/.*)\\.js$': '$1',
 | 
					    '^(\.{1,2}/.*)\.js$': '$1',
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										23
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										23
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							@@ -9,6 +9,8 @@
 | 
				
			|||||||
      "version": "0.0.1",
 | 
					      "version": "0.0.1",
 | 
				
			||||||
      "license": "AGPL-3.0-or-later",
 | 
					      "license": "AGPL-3.0-or-later",
 | 
				
			||||||
      "dependencies": {
 | 
					      "dependencies": {
 | 
				
			||||||
 | 
					        "@hono/node-server": "^1.17.1",
 | 
				
			||||||
 | 
					        "hono": "^4.8.9",
 | 
				
			||||||
        "mongoose": "^8.16.5",
 | 
					        "mongoose": "^8.16.5",
 | 
				
			||||||
        "pino": "^9.7.0"
 | 
					        "pino": "^9.7.0"
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
@@ -1254,6 +1256,18 @@
 | 
				
			|||||||
        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
 | 
					        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/@hono/node-server": {
 | 
				
			||||||
 | 
					      "version": "1.17.1",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.17.1.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-SY79W/C+2b1MyAzmIcV32Q47vO1b5XwLRwj8S9N6Jr5n1QCkIfAIH6umOSgqWZ4/v67hg6qq8Ha5vZonVidGsg==",
 | 
				
			||||||
 | 
					      "license": "MIT",
 | 
				
			||||||
 | 
					      "engines": {
 | 
				
			||||||
 | 
					        "node": ">=18.14.1"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "peerDependencies": {
 | 
				
			||||||
 | 
					        "hono": "^4"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "node_modules/@humanfs/core": {
 | 
					    "node_modules/@humanfs/core": {
 | 
				
			||||||
      "version": "0.19.1",
 | 
					      "version": "0.19.1",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
 | 
				
			||||||
@@ -4153,6 +4167,15 @@
 | 
				
			|||||||
      "dev": true,
 | 
					      "dev": true,
 | 
				
			||||||
      "license": "MIT"
 | 
					      "license": "MIT"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/hono": {
 | 
				
			||||||
 | 
					      "version": "4.8.9",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/hono/-/hono-4.8.9.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-ERIxkXMRhUxGV7nS/Af52+j2KL60B1eg+k6cPtgzrGughS+espS9KQ7QO0SMnevtmRlBfAcN0mf1jKtO6j/doA==",
 | 
				
			||||||
 | 
					      "license": "MIT",
 | 
				
			||||||
 | 
					      "engines": {
 | 
				
			||||||
 | 
					        "node": ">=16.9.0"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "node_modules/html-escaper": {
 | 
					    "node_modules/html-escaper": {
 | 
				
			||||||
      "version": "2.0.2",
 | 
					      "version": "2.0.2",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,8 +16,8 @@
 | 
				
			|||||||
  "main": "index.js",
 | 
					  "main": "index.js",
 | 
				
			||||||
  "scripts": {
 | 
					  "scripts": {
 | 
				
			||||||
    "build": "tsc",
 | 
					    "build": "tsc",
 | 
				
			||||||
    "start": "node dist/index.js",
 | 
					    "start": "node dist/server.js",
 | 
				
			||||||
    "dev": "tsx watch src/index.ts",
 | 
					    "dev": "tsx watch src/server.ts",
 | 
				
			||||||
    "test": "jest",
 | 
					    "test": "jest",
 | 
				
			||||||
    "test:watch": "jest --watch",
 | 
					    "test:watch": "jest --watch",
 | 
				
			||||||
    "lint": "eslint src/**/*.ts",
 | 
					    "lint": "eslint src/**/*.ts",
 | 
				
			||||||
@@ -36,6 +36,8 @@
 | 
				
			|||||||
    "typescript": "^5.8.3"
 | 
					    "typescript": "^5.8.3"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "dependencies": {
 | 
					  "dependencies": {
 | 
				
			||||||
 | 
					    "@hono/node-server": "^1.17.1",
 | 
				
			||||||
 | 
					    "hono": "^4.8.9",
 | 
				
			||||||
    "mongoose": "^8.16.5",
 | 
					    "mongoose": "^8.16.5",
 | 
				
			||||||
    "pino": "^9.7.0"
 | 
					    "pino": "^9.7.0"
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,5 @@
 | 
				
			|||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export interface IConfig {
 | 
					export interface IConfig {
 | 
				
			||||||
 | 
					  port: number;
 | 
				
			||||||
  mongodbUri: string;
 | 
					  mongodbUri: string;
 | 
				
			||||||
  nodeEnv: string;
 | 
					  nodeEnv: string;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -8,11 +7,13 @@ export interface IConfig {
 | 
				
			|||||||
class Config implements IConfig {
 | 
					class Config implements IConfig {
 | 
				
			||||||
  private static instance: Config;
 | 
					  private static instance: Config;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public readonly port: number;
 | 
				
			||||||
  public readonly mongodbUri: string;
 | 
					  public readonly mongodbUri: string;
 | 
				
			||||||
  public readonly nodeEnv: string;
 | 
					  public readonly nodeEnv: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private constructor() {
 | 
					  private constructor() {
 | 
				
			||||||
 | 
					    this.port = parseInt(process.env.PORT || '4000', 10);
 | 
				
			||||||
    this.mongodbUri = process.env.MONGODB_URI || 'mongodb://localhost:27017/dailytrends';
 | 
					    this.mongodbUri = process.env.MONGODB_URI || 'mongodb://localhost:27017/dailytrends';
 | 
				
			||||||
    this.nodeEnv = process.env.NODE_ENV || 'development';
 | 
					    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
 | 
					import { Context } from 'hono';
 | 
				
			||||||
// 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 { IFeedService } from '../services/FeedService.js';
 | 
				
			||||||
 | 
					import { NewsSource } from '../types/Feed.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class FeedController {
 | 
					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
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -1,5 +0,0 @@
 | 
				
			|||||||
"use strict";
 | 
					 | 
				
			||||||
const helloFn = (name) => {
 | 
					 | 
				
			||||||
    return `Hola ${name}`;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
console.log(helloFn("Mundo"));
 | 
					 | 
				
			||||||
@@ -1,7 +0,0 @@
 | 
				
			|||||||
const helloFn = (name: string): string => { 
 | 
					 | 
				
			||||||
  return `Hola ${name}`
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
console.log(helloFn("Mundo"));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
							
								
								
									
										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 };
 | 
				
			||||||
							
								
								
									
										55
									
								
								src/server.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								src/server.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,55 @@
 | 
				
			|||||||
 | 
					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();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Middleware
 | 
				
			||||||
 | 
					app.use('*', cors({ origin: ['http://localhost:3000', 'http://localhost:5173'] }));
 | 
				
			||||||
 | 
					app.use('*', logger(), prettyJSON());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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'),
 | 
					    alreadyConnected: () => logger.info('🔄 Database is already connected'),
 | 
				
			||||||
    notConnected: () => logger.warn('⚠️ Database is not 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;
 | 
					export default Logger;
 | 
				
			||||||
@@ -1,12 +1,17 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
  "compilerOptions": {
 | 
					  "compilerOptions": {
 | 
				
			||||||
 | 
					    "target": "ES2020",
 | 
				
			||||||
    "module": "ESNext",
 | 
					    "module": "ESNext",
 | 
				
			||||||
    "target": "es2020",
 | 
					 | 
				
			||||||
    "moduleResolution": "Node",
 | 
					    "moduleResolution": "Node",
 | 
				
			||||||
    "esModuleInterop": true,
 | 
					 | 
				
			||||||
    "strict": true,
 | 
					 | 
				
			||||||
    "outDir": "dist",
 | 
					    "outDir": "dist",
 | 
				
			||||||
    "rootDir": "src"
 | 
					    "esModuleInterop": true,
 | 
				
			||||||
 | 
					    "allowSyntheticDefaultImports": true,
 | 
				
			||||||
 | 
					    "sourceMap": true,
 | 
				
			||||||
 | 
					    "resolveJsonModule": true,
 | 
				
			||||||
 | 
					    "isolatedModules": true,
 | 
				
			||||||
 | 
					    "skipLibCheck": true,
 | 
				
			||||||
 | 
					    "strict": true
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "include": ["src"]
 | 
					  "include": ["src/**/*.ts"],
 | 
				
			||||||
 | 
					  "exclude": ["dist", "node_modules"]
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user