Merge pull request #2 from aabril/feat/database_and_feed_model
02 - adds mongodb database connection and odm and form the feed model
This commit is contained in:
		
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -1,2 +1,3 @@
 | 
			
		||||
node_modules
 | 
			
		||||
dist
 | 
			
		||||
*.bk
 | 
			
		||||
							
								
								
									
										15
									
								
								Dockerfile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								Dockerfile
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,15 @@
 | 
			
		||||
FROM node:24-slim AS builder
 | 
			
		||||
WORKDIR /app
 | 
			
		||||
COPY package*.json ./
 | 
			
		||||
RUN npm install
 | 
			
		||||
COPY . .
 | 
			
		||||
RUN npm run build
 | 
			
		||||
 | 
			
		||||
FROM node:24-slim AS production
 | 
			
		||||
WORKDIR /app
 | 
			
		||||
COPY package*.json ./
 | 
			
		||||
RUN npm install --production
 | 
			
		||||
COPY --from=builder /app/dist ./dist
 | 
			
		||||
EXPOSE 3000
 | 
			
		||||
CMD ["node", "dist/index.js"]
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										93
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										93
									
								
								README.md
									
									
									
									
									
								
							@@ -2,17 +2,88 @@
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## Changelog
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
- Inicializamos el proyecto, usando npm init, con la node@latest (v24.4.1)
 | 
			
		||||
- Añadimos dependencias que voy a usar 
 | 
			
		||||
- Creo una estructura de directorio inicial
 | 
			
		||||
- Añado un primer test (database.test.ts) para jest
 | 
			
		||||
- Inicializamos proyecto:
 | 
			
		||||
    - Usando npm init, con la node@latest (v24.4.1)
 | 
			
		||||
 | 
			
		||||
- [#1 PR : feat/project_structure ](https://github.com/aabril/dailytrends/pull/1)
 | 
			
		||||
    - Añadimos dependencias que voy a usar 
 | 
			
		||||
    - Creo una estructura de directorio inicial
 | 
			
		||||
    - Añado un primer test (database.test.ts) para jest
 | 
			
		||||
 | 
			
		||||
- [#2 PR : feat/database_and_feed_model ](https://github.com/aabril/dailytrends/pull/2)
 | 
			
		||||
    - Añadimos `moongose` a las dependencias
 | 
			
		||||
    - 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
 | 
			
		||||
    - añadir documentación de Docker, docker-compose
 | 
			
		||||
    - añadir documentación de las capas de abstracción de "Feed"
 | 
			
		||||
    - añadir tests para la conexión con la base de datos con Mocks (el mocking aqui a veces se complica)
 | 
			
		||||
    - añadir primeras definiciones de Feed, empezaremos de lowerst a higher abstraction: tipo -> modelo -> repo -> servicio -> controller
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## Feed layer abstractions
 | 
			
		||||
 | 
			
		||||
From higher to lower:
 | 
			
		||||
 | 
			
		||||
- Layer 1: HTTP/Controller (Highest Abstraction)  
 | 
			
		||||
 - `FeedController.ts` : Handles HTTP requests/responses, API endpoints
 | 
			
		||||
 | 
			
		||||
- Layer 2: Business Logic/Service
 | 
			
		||||
 - `FeedService.ts`    : Implements business rules, validation, orchestration
 | 
			
		||||
 | 
			
		||||
- Layer 3: Data Access/Repository 
 | 
			
		||||
 - `FeedRepository.ts` : Abstracts database operations, CRUD methods
 | 
			
		||||
 | 
			
		||||
- Layer 4: Data Model/Schema
 | 
			
		||||
 - `Feed.ts`           : Mongoose schema, database validations, indexes
 | 
			
		||||
 | 
			
		||||
- Layer 5: Type Definitions (Lowest Abstraction) 
 | 
			
		||||
 - `Feed.ts`           : TypeScript interfaces, enums, DTOs
 | 
			
		||||
 - `config.ts`         : Configuration settings
 | 
			
		||||
 | 
			
		||||
## Dockerfile simple to multistage
 | 
			
		||||
 | 
			
		||||
I rebuild the Dockerfile to be multistage, since the image was heavy because all the node_modules dependencies.
 | 
			
		||||
The size of the image has been reduced from 717Mb to 376.
 | 
			
		||||
 | 
			
		||||
dailytrends-app-legacy    latest    96a2dfe15361   3 minutes ago   717MB
 | 
			
		||||
dailytrends-app-light     latest    7436142e1301   3 seconds ago   376MB
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
###### legacy 
 | 
			
		||||
 | 
			
		||||
```Dockerfile
 | 
			
		||||
FROM node:24-slim
 | 
			
		||||
WORKDIR /app
 | 
			
		||||
COPY package*.json ./
 | 
			
		||||
RUN npm install
 | 
			
		||||
COPY . .
 | 
			
		||||
RUN npm run build
 | 
			
		||||
EXPOSE 3000
 | 
			
		||||
CMD ["node", "dist/index.js"]
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
###### light
 | 
			
		||||
 | 
			
		||||
```Dockerfile
 | 
			
		||||
FROM node:24-slim AS builder
 | 
			
		||||
WORKDIR /app
 | 
			
		||||
COPY package*.json ./
 | 
			
		||||
RUN npm install
 | 
			
		||||
COPY . .
 | 
			
		||||
RUN npm run build
 | 
			
		||||
 | 
			
		||||
FROM node:24-slim AS production
 | 
			
		||||
WORKDIR /app
 | 
			
		||||
COPY package*.json ./
 | 
			
		||||
RUN npm install --production
 | 
			
		||||
COPY --from=builder /app/dist ./dist
 | 
			
		||||
EXPOSE 3000
 | 
			
		||||
CMD ["node", "dist/index.js"]
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										26
									
								
								docker-compose.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								docker-compose.yml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,26 @@
 | 
			
		||||
services:
 | 
			
		||||
  mongo:
 | 
			
		||||
    image: mongo:6
 | 
			
		||||
    container_name: dailytrends_mongo
 | 
			
		||||
    restart: always
 | 
			
		||||
    ports:
 | 
			
		||||
      - "27017:27017"
 | 
			
		||||
    volumes:
 | 
			
		||||
      - mongo_data:/data/db
 | 
			
		||||
  app:
 | 
			
		||||
    build: .
 | 
			
		||||
    container_name: dailytrends_app
 | 
			
		||||
    ports:
 | 
			
		||||
      - "3000:3000"
 | 
			
		||||
    environment:
 | 
			
		||||
      - MONGO_URI=mongodb://mongo:27017/dailytrends
 | 
			
		||||
    depends_on:
 | 
			
		||||
      - mongo
 | 
			
		||||
    volumes:
 | 
			
		||||
      - .:/app
 | 
			
		||||
      - /app/node_modules
 | 
			
		||||
    command: npm run dev  # for development
 | 
			
		||||
    # change to "npm start" for production
 | 
			
		||||
 | 
			
		||||
volumes:
 | 
			
		||||
  mongo_data:
 | 
			
		||||
							
								
								
									
										15
									
								
								jest.config.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								jest.config.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,15 @@
 | 
			
		||||
import type { Config } from 'jest';
 | 
			
		||||
 | 
			
		||||
const config: Config = {
 | 
			
		||||
  preset: 'ts-jest',
 | 
			
		||||
  testEnvironment: 'node',
 | 
			
		||||
  extensionsToTreatAsEsm: ['.ts'],
 | 
			
		||||
  transform: {
 | 
			
		||||
    '^.+\\.ts$': ['ts-jest', { useESM: true }],
 | 
			
		||||
  },
 | 
			
		||||
  moduleNameMapper: {
 | 
			
		||||
    '^(\\.{1,2}/.*)\\.js$': '$1',
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default config;
 | 
			
		||||
							
								
								
									
										446
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										446
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							@@ -8,6 +8,10 @@
 | 
			
		||||
      "name": "dailytrends",
 | 
			
		||||
      "version": "0.0.1",
 | 
			
		||||
      "license": "AGPL-3.0-or-later",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "mongoose": "^8.16.5",
 | 
			
		||||
        "pino": "^9.7.0"
 | 
			
		||||
      },
 | 
			
		||||
      "devDependencies": {
 | 
			
		||||
        "@types/jest": "^30.0.0",
 | 
			
		||||
        "@types/node": "^24.1.0",
 | 
			
		||||
@@ -15,6 +19,7 @@
 | 
			
		||||
        "@typescript-eslint/parser": "^8.38.0",
 | 
			
		||||
        "eslint": "^9.32.0",
 | 
			
		||||
        "jest": "^30.0.5",
 | 
			
		||||
        "pino-pretty": "^13.0.0",
 | 
			
		||||
        "ts-jest": "^29.4.0",
 | 
			
		||||
        "tsx": "^4.20.3",
 | 
			
		||||
        "typescript": "^5.8.3"
 | 
			
		||||
@@ -1877,6 +1882,15 @@
 | 
			
		||||
        "@jridgewell/sourcemap-codec": "^1.4.10"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@mongodb-js/saslprep": {
 | 
			
		||||
      "version": "1.3.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.3.0.tgz",
 | 
			
		||||
      "integrity": "sha512-zlayKCsIjYb7/IdfqxorK5+xUMyi4vOKcFy10wKJYc63NSdKI8mNME+uJqfatkPmOSMMUiojrL58IePKBm3gvQ==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "sparse-bitfield": "^3.0.3"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@napi-rs/wasm-runtime": {
 | 
			
		||||
      "version": "0.2.12",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz",
 | 
			
		||||
@@ -2140,6 +2154,21 @@
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "license": "MIT"
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@types/webidl-conversions": {
 | 
			
		||||
      "version": "7.0.3",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz",
 | 
			
		||||
      "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==",
 | 
			
		||||
      "license": "MIT"
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@types/whatwg-url": {
 | 
			
		||||
      "version": "11.0.5",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz",
 | 
			
		||||
      "integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@types/webidl-conversions": "*"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@types/yargs": {
 | 
			
		||||
      "version": "17.0.33",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz",
 | 
			
		||||
@@ -2805,6 +2834,15 @@
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "license": "MIT"
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/atomic-sleep": {
 | 
			
		||||
      "version": "1.0.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz",
 | 
			
		||||
      "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=8.0.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/babel-jest": {
 | 
			
		||||
      "version": "30.0.5",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.0.5.tgz",
 | 
			
		||||
@@ -2989,6 +3027,15 @@
 | 
			
		||||
        "node-int64": "^0.4.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/bson": {
 | 
			
		||||
      "version": "6.10.4",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/bson/-/bson-6.10.4.tgz",
 | 
			
		||||
      "integrity": "sha512-WIsKqkSC0ABoBJuT1LEX+2HEvNmNKKgnTAyd0fL8qzK4SH2i9NXg+t08YtdZp/V9IZ33cxe3iV4yM0qg8lMQng==",
 | 
			
		||||
      "license": "Apache-2.0",
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=16.20.1"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/buffer-from": {
 | 
			
		||||
      "version": "1.1.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
 | 
			
		||||
@@ -3203,6 +3250,13 @@
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "license": "MIT"
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/colorette": {
 | 
			
		||||
      "version": "2.0.20",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz",
 | 
			
		||||
      "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "license": "MIT"
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/concat-map": {
 | 
			
		||||
      "version": "0.0.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
 | 
			
		||||
@@ -3241,11 +3295,20 @@
 | 
			
		||||
        "node": ">= 8"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/dateformat": {
 | 
			
		||||
      "version": "4.6.3",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz",
 | 
			
		||||
      "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": "*"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/debug": {
 | 
			
		||||
      "version": "4.4.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
 | 
			
		||||
      "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "ms": "^2.1.3"
 | 
			
		||||
@@ -3363,6 +3426,16 @@
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "license": "MIT"
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/end-of-stream": {
 | 
			
		||||
      "version": "1.4.5",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
 | 
			
		||||
      "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "once": "^1.4.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/error-ex": {
 | 
			
		||||
      "version": "1.3.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
 | 
			
		||||
@@ -3726,6 +3799,13 @@
 | 
			
		||||
        "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/fast-copy": {
 | 
			
		||||
      "version": "3.0.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-3.0.2.tgz",
 | 
			
		||||
      "integrity": "sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "license": "MIT"
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/fast-deep-equal": {
 | 
			
		||||
      "version": "3.1.3",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
 | 
			
		||||
@@ -3777,6 +3857,22 @@
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "license": "MIT"
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/fast-redact": {
 | 
			
		||||
      "version": "3.5.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.5.0.tgz",
 | 
			
		||||
      "integrity": "sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=6"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/fast-safe-stringify": {
 | 
			
		||||
      "version": "2.1.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz",
 | 
			
		||||
      "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "license": "MIT"
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/fastq": {
 | 
			
		||||
      "version": "1.19.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz",
 | 
			
		||||
@@ -4050,6 +4146,13 @@
 | 
			
		||||
        "node": ">=8"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/help-me": {
 | 
			
		||||
      "version": "5.0.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz",
 | 
			
		||||
      "integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "license": "MIT"
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/html-escaper": {
 | 
			
		||||
      "version": "2.0.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
 | 
			
		||||
@@ -4957,6 +5060,16 @@
 | 
			
		||||
        "url": "https://github.com/chalk/supports-color?sponsor=1"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/joycon": {
 | 
			
		||||
      "version": "3.1.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz",
 | 
			
		||||
      "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=10"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/js-tokens": {
 | 
			
		||||
      "version": "4.0.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
 | 
			
		||||
@@ -5031,6 +5144,15 @@
 | 
			
		||||
        "node": ">=6"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/kareem": {
 | 
			
		||||
      "version": "2.6.3",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.6.3.tgz",
 | 
			
		||||
      "integrity": "sha512-C3iHfuGUXK2u8/ipq9LfjFfXFxAZMQJJq7vLS45r3D9Y2xQ/m4S8zaR4zMLFWh9AsNPXmcFfUDhTEO8UIC/V6Q==",
 | 
			
		||||
      "license": "Apache-2.0",
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=12.0.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/keyv": {
 | 
			
		||||
      "version": "4.5.4",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
 | 
			
		||||
@@ -5145,6 +5267,12 @@
 | 
			
		||||
        "tmpl": "1.0.5"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/memory-pager": {
 | 
			
		||||
      "version": "1.5.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz",
 | 
			
		||||
      "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==",
 | 
			
		||||
      "license": "MIT"
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/merge-stream": {
 | 
			
		||||
      "version": "2.0.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
 | 
			
		||||
@@ -5202,6 +5330,16 @@
 | 
			
		||||
        "url": "https://github.com/sponsors/isaacs"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/minimist": {
 | 
			
		||||
      "version": "1.2.8",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
 | 
			
		||||
      "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "funding": {
 | 
			
		||||
        "url": "https://github.com/sponsors/ljharb"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/minipass": {
 | 
			
		||||
      "version": "7.1.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
 | 
			
		||||
@@ -5212,11 +5350,109 @@
 | 
			
		||||
        "node": ">=16 || 14 >=14.17"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/mongodb": {
 | 
			
		||||
      "version": "6.17.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.17.0.tgz",
 | 
			
		||||
      "integrity": "sha512-neerUzg/8U26cgruLysKEjJvoNSXhyID3RvzvdcpsIi2COYM3FS3o9nlH7fxFtefTb942dX3W9i37oPfCVj4wA==",
 | 
			
		||||
      "license": "Apache-2.0",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@mongodb-js/saslprep": "^1.1.9",
 | 
			
		||||
        "bson": "^6.10.4",
 | 
			
		||||
        "mongodb-connection-string-url": "^3.0.0"
 | 
			
		||||
      },
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=16.20.1"
 | 
			
		||||
      },
 | 
			
		||||
      "peerDependencies": {
 | 
			
		||||
        "@aws-sdk/credential-providers": "^3.188.0",
 | 
			
		||||
        "@mongodb-js/zstd": "^1.1.0 || ^2.0.0",
 | 
			
		||||
        "gcp-metadata": "^5.2.0",
 | 
			
		||||
        "kerberos": "^2.0.1",
 | 
			
		||||
        "mongodb-client-encryption": ">=6.0.0 <7",
 | 
			
		||||
        "snappy": "^7.2.2",
 | 
			
		||||
        "socks": "^2.7.1"
 | 
			
		||||
      },
 | 
			
		||||
      "peerDependenciesMeta": {
 | 
			
		||||
        "@aws-sdk/credential-providers": {
 | 
			
		||||
          "optional": true
 | 
			
		||||
        },
 | 
			
		||||
        "@mongodb-js/zstd": {
 | 
			
		||||
          "optional": true
 | 
			
		||||
        },
 | 
			
		||||
        "gcp-metadata": {
 | 
			
		||||
          "optional": true
 | 
			
		||||
        },
 | 
			
		||||
        "kerberos": {
 | 
			
		||||
          "optional": true
 | 
			
		||||
        },
 | 
			
		||||
        "mongodb-client-encryption": {
 | 
			
		||||
          "optional": true
 | 
			
		||||
        },
 | 
			
		||||
        "snappy": {
 | 
			
		||||
          "optional": true
 | 
			
		||||
        },
 | 
			
		||||
        "socks": {
 | 
			
		||||
          "optional": true
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/mongodb-connection-string-url": {
 | 
			
		||||
      "version": "3.0.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.2.tgz",
 | 
			
		||||
      "integrity": "sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==",
 | 
			
		||||
      "license": "Apache-2.0",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@types/whatwg-url": "^11.0.2",
 | 
			
		||||
        "whatwg-url": "^14.1.0 || ^13.0.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/mongoose": {
 | 
			
		||||
      "version": "8.16.5",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.16.5.tgz",
 | 
			
		||||
      "integrity": "sha512-Ey84Jf1c426FEMj16+j/VqFKupFN2Ey5uAy6TTAN9HlFP4OcunL7j9O7vTuwAipzlZdjctxP0OK9MRJ4Aa/jNg==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "bson": "^6.10.4",
 | 
			
		||||
        "kareem": "2.6.3",
 | 
			
		||||
        "mongodb": "~6.17.0",
 | 
			
		||||
        "mpath": "0.9.0",
 | 
			
		||||
        "mquery": "5.0.0",
 | 
			
		||||
        "ms": "2.1.3",
 | 
			
		||||
        "sift": "17.1.3"
 | 
			
		||||
      },
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=16.20.1"
 | 
			
		||||
      },
 | 
			
		||||
      "funding": {
 | 
			
		||||
        "type": "opencollective",
 | 
			
		||||
        "url": "https://opencollective.com/mongoose"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/mpath": {
 | 
			
		||||
      "version": "0.9.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz",
 | 
			
		||||
      "integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=4.0.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/mquery": {
 | 
			
		||||
      "version": "5.0.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/mquery/-/mquery-5.0.0.tgz",
 | 
			
		||||
      "integrity": "sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "debug": "4.x"
 | 
			
		||||
      },
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=14.0.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/ms": {
 | 
			
		||||
      "version": "2.1.3",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
 | 
			
		||||
      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "license": "MIT"
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/napi-postinstall": {
 | 
			
		||||
@@ -5279,6 +5515,15 @@
 | 
			
		||||
        "node": ">=8"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/on-exit-leak-free": {
 | 
			
		||||
      "version": "2.1.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz",
 | 
			
		||||
      "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=14.0.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/once": {
 | 
			
		||||
      "version": "1.4.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
 | 
			
		||||
@@ -5478,6 +5723,68 @@
 | 
			
		||||
        "url": "https://github.com/sponsors/jonschlinkert"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/pino": {
 | 
			
		||||
      "version": "9.7.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/pino/-/pino-9.7.0.tgz",
 | 
			
		||||
      "integrity": "sha512-vnMCM6xZTb1WDmLvtG2lE/2p+t9hDEIvTWJsu6FejkE62vB7gDhvzrpFR4Cw2to+9JNQxVnkAKVPA1KPB98vWg==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "atomic-sleep": "^1.0.0",
 | 
			
		||||
        "fast-redact": "^3.1.1",
 | 
			
		||||
        "on-exit-leak-free": "^2.1.0",
 | 
			
		||||
        "pino-abstract-transport": "^2.0.0",
 | 
			
		||||
        "pino-std-serializers": "^7.0.0",
 | 
			
		||||
        "process-warning": "^5.0.0",
 | 
			
		||||
        "quick-format-unescaped": "^4.0.3",
 | 
			
		||||
        "real-require": "^0.2.0",
 | 
			
		||||
        "safe-stable-stringify": "^2.3.1",
 | 
			
		||||
        "sonic-boom": "^4.0.1",
 | 
			
		||||
        "thread-stream": "^3.0.0"
 | 
			
		||||
      },
 | 
			
		||||
      "bin": {
 | 
			
		||||
        "pino": "bin.js"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/pino-abstract-transport": {
 | 
			
		||||
      "version": "2.0.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz",
 | 
			
		||||
      "integrity": "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "split2": "^4.0.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/pino-pretty": {
 | 
			
		||||
      "version": "13.0.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-13.0.0.tgz",
 | 
			
		||||
      "integrity": "sha512-cQBBIVG3YajgoUjo1FdKVRX6t9XPxwB9lcNJVD5GCnNM4Y6T12YYx8c6zEejxQsU0wrg9TwmDulcE9LR7qcJqA==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "colorette": "^2.0.7",
 | 
			
		||||
        "dateformat": "^4.6.3",
 | 
			
		||||
        "fast-copy": "^3.0.2",
 | 
			
		||||
        "fast-safe-stringify": "^2.1.1",
 | 
			
		||||
        "help-me": "^5.0.0",
 | 
			
		||||
        "joycon": "^3.1.1",
 | 
			
		||||
        "minimist": "^1.2.6",
 | 
			
		||||
        "on-exit-leak-free": "^2.1.0",
 | 
			
		||||
        "pino-abstract-transport": "^2.0.0",
 | 
			
		||||
        "pump": "^3.0.0",
 | 
			
		||||
        "secure-json-parse": "^2.4.0",
 | 
			
		||||
        "sonic-boom": "^4.0.1",
 | 
			
		||||
        "strip-json-comments": "^3.1.1"
 | 
			
		||||
      },
 | 
			
		||||
      "bin": {
 | 
			
		||||
        "pino-pretty": "bin.js"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/pino-std-serializers": {
 | 
			
		||||
      "version": "7.0.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.0.0.tgz",
 | 
			
		||||
      "integrity": "sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==",
 | 
			
		||||
      "license": "MIT"
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/pirates": {
 | 
			
		||||
      "version": "4.0.7",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz",
 | 
			
		||||
@@ -5595,11 +5902,37 @@
 | 
			
		||||
        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/process-warning": {
 | 
			
		||||
      "version": "5.0.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz",
 | 
			
		||||
      "integrity": "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==",
 | 
			
		||||
      "funding": [
 | 
			
		||||
        {
 | 
			
		||||
          "type": "github",
 | 
			
		||||
          "url": "https://github.com/sponsors/fastify"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          "type": "opencollective",
 | 
			
		||||
          "url": "https://opencollective.com/fastify"
 | 
			
		||||
        }
 | 
			
		||||
      ],
 | 
			
		||||
      "license": "MIT"
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/pump": {
 | 
			
		||||
      "version": "3.0.3",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz",
 | 
			
		||||
      "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "end-of-stream": "^1.1.0",
 | 
			
		||||
        "once": "^1.3.1"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/punycode": {
 | 
			
		||||
      "version": "2.3.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
 | 
			
		||||
      "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=6"
 | 
			
		||||
@@ -5643,6 +5976,12 @@
 | 
			
		||||
      ],
 | 
			
		||||
      "license": "MIT"
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/quick-format-unescaped": {
 | 
			
		||||
      "version": "4.0.4",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz",
 | 
			
		||||
      "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==",
 | 
			
		||||
      "license": "MIT"
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/react-is": {
 | 
			
		||||
      "version": "18.3.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
 | 
			
		||||
@@ -5650,6 +5989,15 @@
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "license": "MIT"
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/real-require": {
 | 
			
		||||
      "version": "0.2.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz",
 | 
			
		||||
      "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">= 12.13.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/require-directory": {
 | 
			
		||||
      "version": "2.1.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
 | 
			
		||||
@@ -5738,6 +6086,22 @@
 | 
			
		||||
        "queue-microtask": "^1.2.2"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/safe-stable-stringify": {
 | 
			
		||||
      "version": "2.5.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz",
 | 
			
		||||
      "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=10"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/secure-json-parse": {
 | 
			
		||||
      "version": "2.7.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz",
 | 
			
		||||
      "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "license": "BSD-3-Clause"
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/semver": {
 | 
			
		||||
      "version": "7.7.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
 | 
			
		||||
@@ -5774,6 +6138,12 @@
 | 
			
		||||
        "node": ">=8"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/sift": {
 | 
			
		||||
      "version": "17.1.3",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/sift/-/sift-17.1.3.tgz",
 | 
			
		||||
      "integrity": "sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ==",
 | 
			
		||||
      "license": "MIT"
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/signal-exit": {
 | 
			
		||||
      "version": "4.1.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
 | 
			
		||||
@@ -5797,6 +6167,15 @@
 | 
			
		||||
        "node": ">=8"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/sonic-boom": {
 | 
			
		||||
      "version": "4.2.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.0.tgz",
 | 
			
		||||
      "integrity": "sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "atomic-sleep": "^1.0.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/source-map": {
 | 
			
		||||
      "version": "0.6.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
 | 
			
		||||
@@ -5818,6 +6197,24 @@
 | 
			
		||||
        "source-map": "^0.6.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/sparse-bitfield": {
 | 
			
		||||
      "version": "3.0.3",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz",
 | 
			
		||||
      "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "memory-pager": "^1.0.2"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/split2": {
 | 
			
		||||
      "version": "4.2.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
 | 
			
		||||
      "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
 | 
			
		||||
      "license": "ISC",
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">= 10.x"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/sprintf-js": {
 | 
			
		||||
      "version": "1.0.3",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
 | 
			
		||||
@@ -6112,6 +6509,15 @@
 | 
			
		||||
        "node": "*"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/thread-stream": {
 | 
			
		||||
      "version": "3.1.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.1.0.tgz",
 | 
			
		||||
      "integrity": "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "real-require": "^0.2.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/tmpl": {
 | 
			
		||||
      "version": "1.0.5",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz",
 | 
			
		||||
@@ -6132,6 +6538,18 @@
 | 
			
		||||
        "node": ">=8.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/tr46": {
 | 
			
		||||
      "version": "5.1.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz",
 | 
			
		||||
      "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "punycode": "^2.3.1"
 | 
			
		||||
      },
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=18"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/ts-api-utils": {
 | 
			
		||||
      "version": "2.1.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz",
 | 
			
		||||
@@ -6463,6 +6881,28 @@
 | 
			
		||||
        "makeerror": "1.0.12"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/webidl-conversions": {
 | 
			
		||||
      "version": "7.0.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
 | 
			
		||||
      "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
 | 
			
		||||
      "license": "BSD-2-Clause",
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=12"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/whatwg-url": {
 | 
			
		||||
      "version": "14.2.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz",
 | 
			
		||||
      "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "tr46": "^5.1.0",
 | 
			
		||||
        "webidl-conversions": "^7.0.0"
 | 
			
		||||
      },
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=18"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/which": {
 | 
			
		||||
      "version": "2.0.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
 | 
			
		||||
 
 | 
			
		||||
@@ -30,8 +30,13 @@
 | 
			
		||||
    "@typescript-eslint/parser": "^8.38.0",
 | 
			
		||||
    "eslint": "^9.32.0",
 | 
			
		||||
    "jest": "^30.0.5",
 | 
			
		||||
    "pino-pretty": "^13.0.0",
 | 
			
		||||
    "ts-jest": "^29.4.0",
 | 
			
		||||
    "tsx": "^4.20.3",
 | 
			
		||||
    "typescript": "^5.8.3"
 | 
			
		||||
  },
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "mongoose": "^8.16.5",
 | 
			
		||||
    "pino": "^9.7.0"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										13
									
								
								src/__tests__/config.test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/__tests__/config.test.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
			
		||||
import { config } from '../config/config.js';
 | 
			
		||||
 | 
			
		||||
describe('Configuration', () => {
 | 
			
		||||
  test('should load configuration successfully', () => {
 | 
			
		||||
    expect(config).toBeDefined();
 | 
			
		||||
    expect(config.mongodbUri).toBeDefined();
 | 
			
		||||
    expect(config.nodeEnv).toBeDefined();
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  test('should have valid environment', () => {
 | 
			
		||||
    expect(['development', 'production', 'test']).toContain(config.nodeEnv);
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
@@ -1,6 +1,220 @@
 | 
			
		||||
// test temporal para probar que jest funciona
 | 
			
		||||
import mongoose from 'mongoose';
 | 
			
		||||
import { DatabaseConnection } from '../config/database.js';
 | 
			
		||||
import { Logger } from '../utils/logger.js';
 | 
			
		||||
 | 
			
		||||
// Mock mongoose
 | 
			
		||||
jest.mock('mongoose', () => {
 | 
			
		||||
  const mockConnection = {
 | 
			
		||||
    readyState: 0,
 | 
			
		||||
    on: jest.fn(),
 | 
			
		||||
    once: jest.fn()
 | 
			
		||||
  };
 | 
			
		||||
  
 | 
			
		||||
  return {
 | 
			
		||||
    connect: jest.fn(),
 | 
			
		||||
    disconnect: jest.fn(),
 | 
			
		||||
    connection: mockConnection
 | 
			
		||||
  };
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// Mock Logger
 | 
			
		||||
jest.mock('../utils/logger.js', () => ({
 | 
			
		||||
  Logger: {
 | 
			
		||||
    database: {
 | 
			
		||||
      connected: jest.fn(),
 | 
			
		||||
      disconnected: jest.fn(),
 | 
			
		||||
      reconnected: jest.fn(),
 | 
			
		||||
      alreadyConnected: jest.fn(),
 | 
			
		||||
      notConnected: jest.fn()
 | 
			
		||||
    },
 | 
			
		||||
    error: jest.fn()
 | 
			
		||||
  }
 | 
			
		||||
}));
 | 
			
		||||
 | 
			
		||||
// Mock config
 | 
			
		||||
jest.mock('../config/config.js', () => ({
 | 
			
		||||
  config: {
 | 
			
		||||
    mongodbUri: 'mongodb://localhost:27017/test'
 | 
			
		||||
  }
 | 
			
		||||
}));
 | 
			
		||||
 | 
			
		||||
describe('DatabaseConnection', () => {
 | 
			
		||||
  let mockMongoose: jest.Mocked<typeof mongoose>;
 | 
			
		||||
  let mockLogger: jest.Mocked<typeof Logger>;
 | 
			
		||||
  let databaseConnection: DatabaseConnection;
 | 
			
		||||
 | 
			
		||||
  beforeEach(() => {
 | 
			
		||||
    jest.clearAllMocks();
 | 
			
		||||
    mockMongoose = mongoose as jest.Mocked<typeof mongoose>;
 | 
			
		||||
    mockLogger = Logger as jest.Mocked<typeof Logger>;
 | 
			
		||||
    
 | 
			
		||||
    // Reset singleton instance
 | 
			
		||||
    (DatabaseConnection as any).instance = null;
 | 
			
		||||
    databaseConnection = DatabaseConnection.getInstance();
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  describe('Singleton Pattern', () => {
 | 
			
		||||
        test('should return the same instance', () => {})
 | 
			
		||||
    })
 | 
			
		||||
})
 | 
			
		||||
    test('should return the same instance', () => {
 | 
			
		||||
      const instance1 = DatabaseConnection.getInstance();
 | 
			
		||||
      const instance2 = DatabaseConnection.getInstance();
 | 
			
		||||
      expect(instance1).toBe(instance2);
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  describe('connect', () => {
 | 
			
		||||
    test('should connect to MongoDB successfully', async () => {
 | 
			
		||||
      mockMongoose.connect.mockResolvedValueOnce(mockMongoose);
 | 
			
		||||
      Object.defineProperty(mockMongoose.connection, 'readyState', { value: 1, writable: true });
 | 
			
		||||
 | 
			
		||||
      await databaseConnection.connect();
 | 
			
		||||
 | 
			
		||||
      expect(mockMongoose.connect).toHaveBeenCalledWith('mongodb://localhost:27017/test', {
 | 
			
		||||
        maxPoolSize: 10,
 | 
			
		||||
        serverSelectionTimeoutMS: 5000,
 | 
			
		||||
        socketTimeoutMS: 45000,
 | 
			
		||||
        bufferCommands: false
 | 
			
		||||
      });
 | 
			
		||||
      expect(mockLogger.database.connected).toHaveBeenCalled();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    test('should not connect if already connected', async () => {
 | 
			
		||||
      // Set as already connected
 | 
			
		||||
      (databaseConnection as any).isConnected = true;
 | 
			
		||||
 | 
			
		||||
      await databaseConnection.connect();
 | 
			
		||||
 | 
			
		||||
      expect(mockMongoose.connect).not.toHaveBeenCalled();
 | 
			
		||||
      expect(mockLogger.database.alreadyConnected).toHaveBeenCalled();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    test('should handle connection errors', async () => {
 | 
			
		||||
      const error = new Error('Connection failed');
 | 
			
		||||
      mockMongoose.connect.mockRejectedValueOnce(error);
 | 
			
		||||
 | 
			
		||||
      await expect(databaseConnection.connect()).rejects.toThrow('Connection failed');
 | 
			
		||||
      expect(mockLogger.error).toHaveBeenCalledWith('Failed to connect to MongoDB', { error });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    test('should set up connection event listeners', async () => {
 | 
			
		||||
      mockMongoose.connect.mockResolvedValueOnce(mockMongoose);
 | 
			
		||||
      Object.defineProperty(mockMongoose.connection, 'readyState', { value: 1, writable: true });
 | 
			
		||||
 | 
			
		||||
      await databaseConnection.connect();
 | 
			
		||||
 | 
			
		||||
      expect(mockMongoose.connection.on).toHaveBeenCalledWith('error', expect.any(Function));
 | 
			
		||||
      expect(mockMongoose.connection.on).toHaveBeenCalledWith('disconnected', expect.any(Function));
 | 
			
		||||
      expect(mockMongoose.connection.on).toHaveBeenCalledWith('reconnected', expect.any(Function));
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  describe('disconnect', () => {
 | 
			
		||||
    test('should disconnect from MongoDB successfully', async () => {
 | 
			
		||||
      // Set as connected
 | 
			
		||||
      (databaseConnection as any).isConnected = true;
 | 
			
		||||
      mockMongoose.disconnect.mockResolvedValueOnce(undefined);
 | 
			
		||||
 | 
			
		||||
      await databaseConnection.disconnect();
 | 
			
		||||
 | 
			
		||||
      expect(mockMongoose.disconnect).toHaveBeenCalled();
 | 
			
		||||
      expect(mockLogger.database.disconnected).toHaveBeenCalled();
 | 
			
		||||
      expect((databaseConnection as any).isConnected).toBe(false);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    test('should not disconnect if not connected', async () => {
 | 
			
		||||
      // Set as not connected
 | 
			
		||||
      (databaseConnection as any).isConnected = false;
 | 
			
		||||
 | 
			
		||||
      await databaseConnection.disconnect();
 | 
			
		||||
 | 
			
		||||
      expect(mockMongoose.disconnect).not.toHaveBeenCalled();
 | 
			
		||||
      expect(mockLogger.database.notConnected).toHaveBeenCalled();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    test('should handle disconnection errors', async () => {
 | 
			
		||||
      const error = new Error('Disconnection failed');
 | 
			
		||||
      (databaseConnection as any).isConnected = true;
 | 
			
		||||
      mockMongoose.disconnect.mockRejectedValueOnce(error);
 | 
			
		||||
 | 
			
		||||
      await expect(databaseConnection.disconnect()).rejects.toThrow('Disconnection failed');
 | 
			
		||||
      expect(mockLogger.error).toHaveBeenCalledWith('Error disconnecting from MongoDB', { error });
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  describe('isConnected', () => {
 | 
			
		||||
    test('should return true when connected', () => {
 | 
			
		||||
      (databaseConnection as any).isConnected = true;
 | 
			
		||||
      expect(databaseConnection.getConnectionStatus()).toBe(true);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    test('should return false when not connected', () => {
 | 
			
		||||
      (databaseConnection as any).isConnected = false;
 | 
			
		||||
      expect(databaseConnection.getConnectionStatus()).toBe(false);
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  describe('Event Handlers', () => {
 | 
			
		||||
    test('should handle connection error event', async () => {
 | 
			
		||||
      mockMongoose.connect.mockResolvedValueOnce(mockMongoose);
 | 
			
		||||
      Object.defineProperty(mockMongoose.connection, 'readyState', { value: 1, writable: true });
 | 
			
		||||
      
 | 
			
		||||
      let errorHandler: Function;
 | 
			
		||||
      (mockMongoose.connection.on as jest.Mock).mockImplementation((event, handler) => {
 | 
			
		||||
        if (event === 'error') {
 | 
			
		||||
          errorHandler = handler;
 | 
			
		||||
        }
 | 
			
		||||
        return mockMongoose.connection;
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      await databaseConnection.connect();
 | 
			
		||||
 | 
			
		||||
      // Simulate error event
 | 
			
		||||
      const testError = new Error('Connection error');
 | 
			
		||||
      errorHandler!(testError);
 | 
			
		||||
 | 
			
		||||
      expect(mockLogger.error).toHaveBeenCalledWith('MongoDB connection error', { error: testError });
 | 
			
		||||
      expect((databaseConnection as any).isConnected).toBe(false);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    test('should handle disconnected event', async () => {
 | 
			
		||||
      mockMongoose.connect.mockResolvedValueOnce(mockMongoose);
 | 
			
		||||
      Object.defineProperty(mockMongoose.connection, 'readyState', { value: 1, writable: true });
 | 
			
		||||
      
 | 
			
		||||
      let disconnectedHandler: Function;
 | 
			
		||||
      (mockMongoose.connection.on as jest.Mock).mockImplementation((event, handler) => {
 | 
			
		||||
        if (event === 'disconnected') {
 | 
			
		||||
          disconnectedHandler = handler;
 | 
			
		||||
        }
 | 
			
		||||
        return mockMongoose.connection;
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      await databaseConnection.connect();
 | 
			
		||||
 | 
			
		||||
      // Simulate disconnected event
 | 
			
		||||
      disconnectedHandler!();
 | 
			
		||||
 | 
			
		||||
      expect(mockLogger.database.disconnected).toHaveBeenCalled();
 | 
			
		||||
      expect((databaseConnection as any).isConnected).toBe(false);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    test('should handle reconnected event', async () => {
 | 
			
		||||
      mockMongoose.connect.mockResolvedValueOnce(mockMongoose);
 | 
			
		||||
      Object.defineProperty(mockMongoose.connection, 'readyState', { value: 1, writable: true });
 | 
			
		||||
      
 | 
			
		||||
      let reconnectedHandler: Function;
 | 
			
		||||
      (mockMongoose.connection.on as jest.Mock).mockImplementation((event, handler) => {
 | 
			
		||||
        if (event === 'reconnected') {
 | 
			
		||||
          reconnectedHandler = handler;
 | 
			
		||||
        }
 | 
			
		||||
        return mockMongoose.connection;
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      await databaseConnection.connect();
 | 
			
		||||
 | 
			
		||||
      // Simulate reconnected event
 | 
			
		||||
      reconnectedHandler!();
 | 
			
		||||
 | 
			
		||||
      expect(mockLogger.database.reconnected).toHaveBeenCalled();
 | 
			
		||||
      expect((databaseConnection as any).isConnected).toBe(true);
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										49
									
								
								src/config/config.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								src/config/config.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,49 @@
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export interface IConfig {
 | 
			
		||||
  mongodbUri: string;
 | 
			
		||||
  nodeEnv: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class Config implements IConfig {
 | 
			
		||||
  private static instance: Config;
 | 
			
		||||
 | 
			
		||||
  public readonly mongodbUri: string;
 | 
			
		||||
  public readonly nodeEnv: string;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  private constructor() {
 | 
			
		||||
    this.mongodbUri = process.env.MONGODB_URI || 'mongodb://localhost:27017/dailytrends';
 | 
			
		||||
    this.nodeEnv = process.env.NODE_ENV || 'development';
 | 
			
		||||
 | 
			
		||||
    this.validateConfig();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public static getInstance(): Config {
 | 
			
		||||
    if (!Config.instance) {
 | 
			
		||||
      Config.instance = new Config();
 | 
			
		||||
    }
 | 
			
		||||
    return Config.instance;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private validateConfig(): void {
 | 
			
		||||
    if (!this.mongodbUri) {
 | 
			
		||||
      throw new Error('MONGODB_URI is required');
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public isDevelopment(): boolean {
 | 
			
		||||
    return this.nodeEnv === 'development';
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public isProduction(): boolean {
 | 
			
		||||
    return this.nodeEnv === 'production';
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public isTest(): boolean {
 | 
			
		||||
    return this.nodeEnv === 'test';
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const config = Config.getInstance();
 | 
			
		||||
export default config;
 | 
			
		||||
@@ -1,10 +1,10 @@
 | 
			
		||||
// aqui la conexión con MongoDB, usando mongoose o cualquier otro odm que vaya a usar
 | 
			
		||||
// las conexión a bases de datos normalmente deberían ser Singleton para reutilizar la conexión
 | 
			
		||||
// motivo: pues no saturar la base de datos ni saturarla con multiples conexiones
 | 
			
		||||
import mongoose from 'mongoose';
 | 
			
		||||
import { config } from '../config/config.js';
 | 
			
		||||
import { Logger } from '../utils/logger.js';
 | 
			
		||||
 | 
			
		||||
export class DatabaseConnection {
 | 
			
		||||
  private static instance: DatabaseConnection;
 | 
			
		||||
    // private isConnected: boolean = false; // a implementar
 | 
			
		||||
  private isConnected: boolean = false;
 | 
			
		||||
 | 
			
		||||
  private constructor() {}
 | 
			
		||||
 | 
			
		||||
@@ -14,4 +14,82 @@ export class DatabaseConnection {
 | 
			
		||||
    }
 | 
			
		||||
    return DatabaseConnection.instance;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public async connect(): Promise<void> {
 | 
			
		||||
    if (this.isConnected) {
 | 
			
		||||
      Logger.database.alreadyConnected();
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      await mongoose.connect(config.mongodbUri, {
 | 
			
		||||
        maxPoolSize: 10,
 | 
			
		||||
        serverSelectionTimeoutMS: 5000,
 | 
			
		||||
        socketTimeoutMS: 45000,
 | 
			
		||||
        bufferCommands: false,
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      this.isConnected = true;
 | 
			
		||||
      Logger.database.connected();
 | 
			
		||||
 | 
			
		||||
      mongoose.connection.on('error', (error) => {
 | 
			
		||||
        Logger.error('MongoDB connection error', { error });
 | 
			
		||||
        this.isConnected = false;
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      mongoose.connection.on('disconnected', () => {
 | 
			
		||||
        Logger.database.disconnected();
 | 
			
		||||
        this.isConnected = false;
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      mongoose.connection.on('reconnected', () => {
 | 
			
		||||
        Logger.database.reconnected();
 | 
			
		||||
        this.isConnected = true;
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
      Logger.error('Failed to connect to MongoDB', { error });
 | 
			
		||||
      this.isConnected = false;
 | 
			
		||||
      throw error;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public async disconnect(): Promise<void> {
 | 
			
		||||
    if (!this.isConnected) {
 | 
			
		||||
      Logger.database.notConnected();
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      await mongoose.disconnect();
 | 
			
		||||
      this.isConnected = false;
 | 
			
		||||
      Logger.database.disconnected();
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
      Logger.error('Error disconnecting from MongoDB', { error });
 | 
			
		||||
      throw error;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public getConnectionStatus(): boolean {
 | 
			
		||||
    return this.isConnected && mongoose.connection.readyState === 1;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public async healthCheck(): Promise<{ status: string; message: string }> {
 | 
			
		||||
    try {
 | 
			
		||||
      if (!this.isConnected) {
 | 
			
		||||
        return { status: 'error', message: 'Database not connected' };
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      await mongoose.connection.db?.admin().ping();
 | 
			
		||||
      return { status: 'ok', message: 'Database connection is healthy' };
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
      return { 
 | 
			
		||||
        status: 'error', 
 | 
			
		||||
        message: `Database health check failed: ${error instanceof Error ? error.message : 'Unknown error'}` 
 | 
			
		||||
      };
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const database = DatabaseConnection.getInstance();
 | 
			
		||||
export default database;
 | 
			
		||||
@@ -1,5 +1,31 @@
 | 
			
		||||
// Aquí el modelo Feed
 | 
			
		||||
import mongoose, { Schema, Document } from 'mongoose';
 | 
			
		||||
import { IFeed } from '../types/Feed.js';
 | 
			
		||||
 | 
			
		||||
// Si usase mongoose, supongo que será diretamente el modelo de mongoose
 | 
			
		||||
// Ya veré si uso algun otro ODM
 | 
			
		||||
export interface IFeedDocument extends IFeed, Document {
 | 
			
		||||
    _id: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const feedSchema = new Schema<IFeedDocument>({
 | 
			
		||||
  }, {
 | 
			
		||||
    timestamps: true,
 | 
			
		||||
    toJSON: {
 | 
			
		||||
      transform: function(doc, ret) {
 | 
			
		||||
        ret.id = ret._id;
 | 
			
		||||
        delete (ret as any)._id;
 | 
			
		||||
        delete (ret as any).__v;
 | 
			
		||||
        return ret;
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    toObject: {
 | 
			
		||||
      transform: function(doc, ret) {
 | 
			
		||||
        ret.id = ret._id;
 | 
			
		||||
        delete (ret as any)._id;
 | 
			
		||||
        delete (ret as any).__v;
 | 
			
		||||
        return ret;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export const Feed = mongoose.model<IFeedDocument>('Feed', feedSchema);
 | 
			
		||||
export default Feed;
 | 
			
		||||
@@ -1 +1,18 @@
 | 
			
		||||
// Aquí exportaré las interfaces que vaya a necesitas: las básicas, dtos, enums, etc.
 | 
			
		||||
export enum NewsSource {
 | 
			
		||||
    EL_PAIS = 'El País',
 | 
			
		||||
    EL_MUNDO = 'El Mundo',
 | 
			
		||||
    MANUAL = 'Manual'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface IFeed {
 | 
			
		||||
    _id?: string;
 | 
			
		||||
    title: string;
 | 
			
		||||
    description: string;
 | 
			
		||||
    url: string;
 | 
			
		||||
    source: NewsSource;
 | 
			
		||||
    publishedAt: Date;
 | 
			
		||||
    imageUrl?: string;
 | 
			
		||||
    category?: string;
 | 
			
		||||
    createdAt?: Date;
 | 
			
		||||
    updatedAt?: Date;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,2 +1,51 @@
 | 
			
		||||
// Aquí "abstraeré" los logs, para evitar el uso de console.log (por seguridad, y por si queremos extraerlo a algun servicio)
 | 
			
		||||
// En mi dia usaba winston, llegué a usar pino que era más ligero, supongo que usaré pino, ya veremos.
 | 
			
		||||
import pino from 'pino';
 | 
			
		||||
import { config } from '../config/config.js';
 | 
			
		||||
 | 
			
		||||
// Create the logger instance with Pino
 | 
			
		||||
const logger = pino({
 | 
			
		||||
  level: config.nodeEnv === 'production' ? 'info' : 'debug',
 | 
			
		||||
  transport: config.nodeEnv === 'production' ? undefined : {
 | 
			
		||||
    target: 'pino-pretty',
 | 
			
		||||
    options: {
 | 
			
		||||
      colorize: true,
 | 
			
		||||
      translateTime: 'SYS:standard',
 | 
			
		||||
      ignore: 'pid,hostname'
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  // Production configuration
 | 
			
		||||
  ...(config.nodeEnv === 'production' && {
 | 
			
		||||
    formatters: {
 | 
			
		||||
      level: (label) => {
 | 
			
		||||
        return { level: label };
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  })
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export const Logger = {
 | 
			
		||||
  error: (message: string, meta?: any) => {
 | 
			
		||||
    logger.error(message, meta);
 | 
			
		||||
  },
 | 
			
		||||
  
 | 
			
		||||
  warn: (message: string, meta?: any) => {
 | 
			
		||||
    logger.warn(message, meta);
 | 
			
		||||
  },
 | 
			
		||||
  
 | 
			
		||||
  info: (message: string, meta?: any) => {
 | 
			
		||||
    logger.info(message, meta);
 | 
			
		||||
  },
 | 
			
		||||
  
 | 
			
		||||
  debug: (message: string, meta?: any) => {
 | 
			
		||||
    logger.debug(message, meta);
 | 
			
		||||
  },
 | 
			
		||||
  
 | 
			
		||||
  database: {
 | 
			
		||||
    connected: () => logger.info('✅ Database connected successfully'),
 | 
			
		||||
    disconnected: () => logger.info('✅ Database disconnected'),
 | 
			
		||||
    reconnected: () => logger.info('📡 MongoDB reconnected'),
 | 
			
		||||
    alreadyConnected: () => logger.info('🔄 Database is already connected'),
 | 
			
		||||
    notConnected: () => logger.warn('⚠️ Database is not connected')
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default Logger;
 | 
			
		||||
		Reference in New Issue
	
	Block a user