hasta inicio de microservicios

This commit is contained in:
Incuba devs 2025-11-02 17:26:52 +01:00
parent 61a1e4ee09
commit 76b1e964d1
30 changed files with 11410 additions and 0 deletions

17
.env.template Normal file
View File

@ -0,0 +1,17 @@
PORT=3001
# This was inserted by `prisma init`:
# Environment variables declared in this file are NOT automatically loaded by Prisma.
# Please add `import "dotenv/config";` to your `prisma.config.ts` file, or use the Prisma CLI with Bun
# to load environment variables from .env files: https://pris.ly/prisma-config-env-vars.
# Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB.
# See the documentation for all the connection string options: https://pris.ly/d/connection-strings
# The following `prisma+postgres` URL is similar to the URL produced by running a local Prisma Postgres
# server with the `prisma dev` CLI command, when not choosing any non-default ports or settings. The API key, unlike the
# one found in a remote Prisma Postgres URL, does not contain any sensitive information.
#DATABASE_URL="prisma+postgres://localhost:51213/?api_key=eyJkYXRhYmFzZVVybCI6InBvc3RncmVzOi8vcG9zdGdyZXM6cG9zdGdyZXNAbG9jYWxob3N0OjUxMjE0L3RlbXBsYXRlMT9zc2xtb2RlPWRpc2FibGUmY29ubmVjdGlvbl9saW1pdD0xJmNvbm5lY3RfdGltZW91dD0wJm1heF9pZGxlX2Nvbm5lY3Rpb25fbGlmZXRpbWU9MCZwb29sX3RpbWVvdXQ9MCZzaW5nbGVfdXNlX2Nvbm5lY3Rpb25zPXRydWUmc29ja2V0X3RpbWVvdXQ9MCIsIm5hbWUiOiJkZWZhdWx0Iiwic2hhZG93RGF0YWJhc2VVcmwiOiJwb3N0Z3JlczovL3Bvc3RncmVzOnBvc3RncmVzQGxvY2FsaG9zdDo1MTIxNS90ZW1wbGF0ZTE_c3NsbW9kZT1kaXNhYmxlJmNvbm5lY3Rpb25fbGltaXQ9MSZjb25uZWN0X3RpbWVvdXQ9MCZtYXhfaWRsZV9jb25uZWN0aW9uX2xpZmV0aW1lPTAmcG9vbF90aW1lb3V0PTAmc2luZ2xlX3VzZV9jb25uZWN0aW9ucz10cnVlJnNvY2tldF90aW1lb3V0PTAifQ"
DATABASE_URL="file:./dev.db"

25
.eslintrc.js Normal file
View File

@ -0,0 +1,25 @@
module.exports = {
parser: '@typescript-eslint/parser',
parserOptions: {
project: 'tsconfig.json',
tsconfigRootDir: __dirname,
sourceType: 'module',
},
plugins: ['@typescript-eslint/eslint-plugin'],
extends: [
'plugin:@typescript-eslint/recommended',
'plugin:prettier/recommended',
],
root: true,
env: {
node: true,
jest: true,
},
ignorePatterns: ['.eslintrc.js'],
rules: {
'@typescript-eslint/interface-name-prefix': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-explicit-any': 'off',
},
};

58
.gitignore vendored Normal file
View File

@ -0,0 +1,58 @@
# compiled output
/dist
/node_modules
/build
# Logs
logs
*.log
npm-debug.log*
pnpm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# OS
.DS_Store
# Tests
/coverage
/.nyc_output
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# temp directory
.temp
.tmp
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
/generated/prisma

4
.prettierrc Normal file
View File

@ -0,0 +1,4 @@
{
"singleQuote": true,
"trailingComma": "all"
}

8
nest-cli.json Normal file
View File

@ -0,0 +1,8 @@
{
"$schema": "https://json.schemastore.org/nest-cli",
"collection": "@nestjs/schematics",
"sourceRoot": "src",
"compilerOptions": {
"deleteOutDir": true
}
}

10759
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

77
package.json Normal file
View File

@ -0,0 +1,77 @@
{
"name": "products-ms",
"version": "0.0.1",
"description": "",
"author": "",
"private": true,
"license": "UNLICENSED",
"scripts": {
"build": "nest build",
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
"start": "nest start",
"start:dev": "nest start --watch",
"start:debug": "nest start --debug --watch",
"start:prod": "node dist/main",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
"test": "jest",
"test:watch": "jest --watch",
"test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "jest --config ./test/jest-e2e.json"
},
"dependencies": {
"@nestjs/common": "^11.1.8",
"@nestjs/core": "^11.1.8",
"@nestjs/mapped-types": "*",
"@nestjs/microservices": "^11.1.8",
"@nestjs/platform-express": "^11.1.8",
"@prisma/client": "^6.18.0",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.2",
"dotenv": "^17.2.3",
"joi": "^18.0.1",
"reflect-metadata": "^0.2.2",
"rxjs": "^7.8.2"
},
"devDependencies": {
"@nestjs/cli": "^11.0.10",
"@nestjs/schematics": "^11.0.9",
"@nestjs/testing": "^11.1.8",
"@types/express": "^5.0.5",
"@types/jest": "^30.0.0",
"@types/node": "^24.9.2",
"@types/supertest": "^6.0.3",
"@typescript-eslint/eslint-plugin": "^8.46.2",
"@typescript-eslint/parser": "^8.46.2",
"eslint": "^9.38.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-prettier": "^5.5.4",
"jest": "^30.2.0",
"prettier": "^3.6.2",
"prisma": "^6.18.0",
"source-map-support": "^0.5.21",
"supertest": "^7.1.4",
"ts-jest": "^29.4.5",
"ts-loader": "^9.5.4",
"ts-node": "^10.9.2",
"tsconfig-paths": "^4.2.0",
"typescript": "^5.9.3"
},
"jest": {
"moduleFileExtensions": [
"js",
"json",
"ts"
],
"rootDir": "src",
"testRegex": ".*\\.spec\\.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
},
"collectCoverageFrom": [
"**/*.(t|j)s"
],
"coverageDirectory": "../coverage",
"testEnvironment": "node"
}
}

14
prisma.config.ts Normal file
View File

@ -0,0 +1,14 @@
import "dotenv/config";
import { defineConfig, env } from "prisma/config";
export default defineConfig({
schema: "prisma/schema.prisma",
migrations: {
path: "prisma/migrations",
},
engine: "classic",
datasource: {
url: env("DATABASE_URL"),
},
});

BIN
prisma/dev.db Normal file

Binary file not shown.

View File

@ -0,0 +1,8 @@
-- CreateTable
CREATE TABLE "Product" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"name" TEXT NOT NULL,
"price" REAL NOT NULL,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL
);

View File

@ -0,0 +1,16 @@
-- RedefineTables
PRAGMA defer_foreign_keys=ON;
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_Product" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"name" TEXT NOT NULL,
"price" REAL NOT NULL,
"available" BOOLEAN NOT NULL DEFAULT true,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL
);
INSERT INTO "new_Product" ("createdAt", "id", "name", "price", "updatedAt") SELECT "createdAt", "id", "name", "price", "updatedAt" FROM "Product";
DROP TABLE "Product";
ALTER TABLE "new_Product" RENAME TO "Product";
PRAGMA foreign_keys=ON;
PRAGMA defer_foreign_keys=OFF;

View File

@ -0,0 +1,2 @@
-- CreateIndex
CREATE INDEX "Product_available_idx" ON "Product"("available");

View File

@ -0,0 +1,3 @@
# Please do not edit this file manually
# It should be added in your version-control system (e.g., Git)
provider = "sqlite"

36
prisma/schema.prisma Normal file
View File

@ -0,0 +1,36 @@
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?
// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init
generator cliente {
provider = "prisma-client-js"
output = "../generated/prisma"
}
// generator client {
// provider = "prisma-client"
// output = "../generated/prisma"
// }
datasource db {
//provider = "postgresql"
provider = "sqlite"
url = env("DATABASE_URL")
}
model Product {
id Int @id @default(autoincrement())
name String
price Float
available Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([available])
}

9
src/app.module.ts Normal file
View File

@ -0,0 +1,9 @@
import { Module } from '@nestjs/common';
import { ProductsModule } from './products/products.module';
@Module({
imports: [ProductsModule],
controllers: [],
providers: [],
})
export class AppModule {}

View File

@ -0,0 +1,16 @@
import { Type } from "class-transformer";
import { IsOptional, IsPositive } from "class-validator";
export class PaginationDto {
@IsPositive()
@IsOptional()
@Type(()=> Number)
page?: number = 1;
@IsPositive()
@IsOptional()
@Type(()=> Number)
limit?: number = 10;
}

1
src/comun/index.ts Normal file
View File

@ -0,0 +1 @@
export * from './dto/pagintation.dto';

31
src/config/envs.ts Normal file
View File

@ -0,0 +1,31 @@
import 'dotenv/config';
import * as joi from 'joi';
interface EnvVars {
PORT: number;
DATABASE_URL: string;
}
const envsSchema = joi.object({
PORT: joi.number().required(),
DATABASE_URL: joi.string().required(),
})
.unknown(true);
const {error, value} = envsSchema.validate(process.env);
if (error){
throw new Error(`Config validation error: ${ error.message}`);
}
const envVars: EnvVars = value;
export const envs = {
port: envVars.PORT,
databaseUrl: envVars.DATABASE_URL,
}

1
src/config/index.ts Normal file
View File

@ -0,0 +1 @@
export * from './envs';

60
src/main.ts Normal file
View File

@ -0,0 +1,60 @@
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { Logger, ValidationPipe } from '@nestjs/common';
import { envs } from './config';
import { MicroserviceOptions, Transport } from '@nestjs/microservices';
async function bootstrap() {
// const logger = new Logger('From main.ts');
// const app = await NestFactory.create(AppModule);
// app.useGlobalPipes(
// new ValidationPipe({
// whitelist: true,
// forbidNonWhitelisted: true,
// }),
// );
// //await app.listen(process.env.PORT ?? 3000);
// await app.listen(envs.port);
// logger.log(`App runing on port ${ envs.port }`);
// }
const logger = new Logger('From main.ts');
const app = await NestFactory.createMicroservice<MicroserviceOptions>(
AppModule,
{
transport: Transport.TCP,
options:{
port: envs.port
}
}
);
app.useGlobalPipes(
new ValidationPipe({
whitelist: true,
forbidNonWhitelisted: true,
}),
);
//await app.listen(process.env.PORT ?? 3000);
await app.listen();
logger.log(`Products Microservice runing on port ${ envs.port }`);
}
bootstrap();

View File

@ -0,0 +1,15 @@
import { Type } from "class-transformer";
import { IsNumber, IsString, Min } from "class-validator";
export class CreateProductDto {
@IsString()
public name:string;
@IsNumber({
maxDecimalPlaces: 4,
})
@Min(0)
@Type(() => Number)
public price:number;
}

View File

@ -0,0 +1,10 @@
import { PartialType } from '@nestjs/mapped-types';
import { CreateProductDto } from './create-product.dto';
import { IsNumber, IsPositive } from 'class-validator';
export class UpdateProductDto extends PartialType(CreateProductDto) {
@IsNumber()
@IsPositive()
id:number;
}

View File

@ -0,0 +1,5 @@
export class Product {
public id:number;
public name:string;
public price:number;
}

View File

@ -0,0 +1,58 @@
import { Controller, Get, Post, Body, Patch, Param, Delete, Query, ParseIntPipe } from '@nestjs/common';
import { ProductsService } from './products.service';
import { CreateProductDto } from './dto/create-product.dto';
import { UpdateProductDto } from './dto/update-product.dto';
import { PaginationDto } from 'src/comun';
import { MessagePattern, Payload } from '@nestjs/microservices';
@Controller('products')
export class ProductsController {
constructor(private readonly productsService: ProductsService) {}
//@Post()
//create(@Body() createProductDto: CreateProductDto) {
@MessagePattern({cmd: 'create_product'})
create(@Payload() createProductDto: CreateProductDto) {
return this.productsService.create(createProductDto);
//return createProductDto;
}
//@Get()
//findAll(@Query() paginationDto: PaginationDto) {
@MessagePattern({cmd: 'find_all_products'})
findAll(@Payload() paginationDto: PaginationDto) {
return this.productsService.findAll(paginationDto);
}
//@Get(':id')
//findOne(@Param('id') id: string) {
@MessagePattern({cmd: 'find_one_product'})
findOne(@Payload('id', ParseIntPipe) id: number) {
return this.productsService.findOne(+id);
}
//@Patch(':id')
//update(@Param('id',ParseIntPipe) id: number, @Body() updateProductDto: UpdateProductDto) {
@MessagePattern({cmd: 'update_product'})
update(
//@Param('id',ParseIntPipe) id: number,
//@Body() updateProductDto: UpdateProductDto) {
@Payload() updateProductDto: UpdateProductDto){
// return {
// id, updateProductDto
// }
return this.productsService.update(updateProductDto.id, updateProductDto);
}
//@Delete(':id')
@MessagePattern({cmd: 'delete_product'})
//remove(@Param('id') id: string) {
remove(@Payload('id',ParseIntPipe) id: string) {
return this.productsService.remove(+id);
}
}

View File

@ -0,0 +1,9 @@
import { Module } from '@nestjs/common';
import { ProductsService } from './products.service';
import { ProductsController } from './products.controller';
@Module({
controllers: [ProductsController],
providers: [ProductsService],
})
export class ProductsModule {}

View File

@ -0,0 +1,109 @@
import { Injectable, Logger, NotFoundException, OnModuleInit } from '@nestjs/common';
import { CreateProductDto } from './dto/create-product.dto';
import { UpdateProductDto } from './dto/update-product.dto';
import { PrismaClient } from 'generated/prisma/client';
import { PaginationDto } from 'src/comun';
@Injectable()
export class ProductsService extends PrismaClient implements OnModuleInit{
private readonly logger = new Logger('ProductsService');
onModuleInit() {
//throw new Error('Method not implemented.');
this.$connect();
this.logger.log('Database coonnected');
}
create(createProductDto: CreateProductDto) {
return this.product.create({
data:createProductDto});
//return 'This action adds a new product';
}
async findAll(paginationDto: PaginationDto) {
//return `This action returns all products`;
const {page, limit} = paginationDto;
const totalPages = await this.product.count({
where: {available:true}
});
const lastPage = Math.ceil(totalPages/limit);
// return this.product.findMany({
// skip: (page -1) * limit,
// take: limit
// });
return {
data: await this.product.findMany({
skip: (page -1) * limit,
take: limit,
where:{available:true}
}),
meta:{
total: totalPages,
page:page,
lastpage:lastPage
}
}
}
async findOne(id: number) {
const product = await this.product.findFirst({
where: {id, available:true}
});
if (!product){
throw new NotFoundException(`Producto con id #${id} no encontrado`);
}
return product;
//return `This action returns a #${id} product`;
}
async update(id: number, updateProductDto: UpdateProductDto) {
const {id:_ , ...data} = updateProductDto;
await this.findOne(id);
return this.product.update({
where:{id},
data: data
})
//return `This action updates a #${id} product`;
}
async remove(id: number) {
//hard delete
// await this.findOne(id);
// return this.product.delete({
// where: {id}
// })
//soft delete (marcado como borrado.)
await this.findOne(id);
const product = this.product.update({
where: {id, available:true},
data: {
available: false
}
});
return product;
//return `This action removes a #${id} product`;
}
}

24
test/app.e2e-spec.ts Normal file
View File

@ -0,0 +1,24 @@
import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import * as request from 'supertest';
import { AppModule } from './../src/app.module';
describe('AppController (e2e)', () => {
let app: INestApplication;
beforeEach(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleFixture.createNestApplication();
await app.init();
});
it('/ (GET)', () => {
return request(app.getHttpServer())
.get('/')
.expect(200)
.expect('Hello World!');
});
});

9
test/jest-e2e.json Normal file
View File

@ -0,0 +1,9 @@
{
"moduleFileExtensions": ["js", "json", "ts"],
"rootDir": ".",
"testEnvironment": "node",
"testRegex": ".e2e-spec.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
}
}

5
tsconfig.build.json Normal file
View File

@ -0,0 +1,5 @@
{
"extends": "./tsconfig.json",
"include": ["src", "generated"],
"exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
}

21
tsconfig.json Normal file
View File

@ -0,0 +1,21 @@
{
"compilerOptions": {
"module": "commonjs",
"declaration": true,
"removeComments": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true,
"target": "ES2021",
"sourceMap": true,
"outDir": "./dist",
"baseUrl": "./",
"incremental": true,
"skipLibCheck": true,
"strictNullChecks": false,
"noImplicitAny": false,
"strictBindCallApply": false,
"forceConsistentCasingInFileNames": false,
"noFallthroughCasesInSwitch": false
}
}