From 78db9bfdb9a3ad47ec71d7143f7a5616d1eee1f3 Mon Sep 17 00:00:00 2001 From: AlexVillaTor Date: Sat, 21 Mar 2026 14:14:34 -0400 Subject: [PATCH 1/2] feat: modulo de autenticacion con JWT y bcrypt --- package-lock.json | 154 ++++++++++++++++++++++++++++++++++++ package.json | 3 + src/app.module.ts | 3 +- src/auth/auth.controller.ts | 25 ++++++ src/auth/auth.module.ts | 18 +++++ src/auth/auth.service.ts | 69 ++++++++++++++++ ts_errors.txt | 0 7 files changed, 271 insertions(+), 1 deletion(-) create mode 100644 src/auth/auth.controller.ts create mode 100644 src/auth/auth.module.ts create mode 100644 src/auth/auth.service.ts create mode 100644 ts_errors.txt diff --git a/package-lock.json b/package-lock.json index b684fe3..7b919bc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,9 +14,11 @@ "@azure/openai": "^1.0.0-beta.13", "@nestjs/common": "^10.0.0", "@nestjs/core": "^10.0.0", + "@nestjs/jwt": "^11.0.2", "@nestjs/platform-express": "^10.0.0", "@nestjs/swagger": "^7.4.2", "axios": "^1.13.6", + "bcryptjs": "^3.0.3", "class-transformer": "^0.5.1", "class-validator": "^0.14.1", "dotenv": "^16.4.5", @@ -30,6 +32,7 @@ "swagger-ui-express": "^5.0.1" }, "devDependencies": { + "@types/bcryptjs": "^2.4.6", "@types/node": "^18.0.0", "ts-node-dev": "^2.0.0", "typescript": "^5.0.0" @@ -360,6 +363,19 @@ } } }, + "node_modules/@nestjs/jwt": { + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/@nestjs/jwt/-/jwt-11.0.2.tgz", + "integrity": "sha512-rK8aE/3/Ma45gAWfCksAXUNbOoSOUudU0Kn3rT39htPF7wsYXtKfjALKeKKJbFrIWbLjsbqfXX5bIJNvgBugGA==", + "license": "MIT", + "dependencies": { + "@types/jsonwebtoken": "9.0.10", + "jsonwebtoken": "9.0.3" + }, + "peerDependencies": { + "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0" + } + }, "node_modules/@nestjs/mapped-types": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/@nestjs/mapped-types/-/mapped-types-2.0.5.tgz", @@ -567,6 +583,29 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/bcryptjs": { + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/@types/bcryptjs/-/bcryptjs-2.4.6.tgz", + "integrity": "sha512-9xlo6R2qDs5uixm0bcIqCeMCE6HiQsIyel9KQySStiyqNl2tnj2mP3DX1Nf56MD6KMenNNlBBsy3LJ7gUEQPXQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/jsonwebtoken": { + "version": "9.0.10", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz", + "integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==", + "license": "MIT", + "dependencies": { + "@types/ms": "*", + "@types/node": "*" + } + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, "node_modules/@types/node": { "version": "18.19.130", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz", @@ -803,6 +842,15 @@ ], "license": "MIT" }, + "node_modules/bcryptjs": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-3.0.3.tgz", + "integrity": "sha512-GlF5wPWnSa/X5LKM1o0wz0suXIINz1iHRLvTS+sLyi7XPbe5ycmYI3DlZqVGZZtDgl4DmasFg7gOB3JYbphV5g==", + "license": "BSD-3-Clause", + "bin": { + "bcrypt": "bin/bcrypt" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -908,6 +956,12 @@ "ieee754": "^1.1.13" } }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -1348,6 +1402,15 @@ "xtend": "^4.0.0" } }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -2118,6 +2181,55 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsonwebtoken": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", + "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", + "license": "MIT", + "dependencies": { + "jws": "^4.0.1", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jsonwebtoken/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==", + "license": "MIT" + }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "license": "MIT", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, "node_modules/libphonenumber-js": { "version": "1.12.40", "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.12.40.tgz", @@ -2130,6 +2242,48 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "license": "MIT" }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" + }, "node_modules/long": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", diff --git a/package.json b/package.json index 30ea720..386fbf4 100644 --- a/package.json +++ b/package.json @@ -17,9 +17,11 @@ "@azure/openai": "^1.0.0-beta.13", "@nestjs/common": "^10.0.0", "@nestjs/core": "^10.0.0", + "@nestjs/jwt": "^11.0.2", "@nestjs/platform-express": "^10.0.0", "@nestjs/swagger": "^7.4.2", "axios": "^1.13.6", + "bcryptjs": "^3.0.3", "class-transformer": "^0.5.1", "class-validator": "^0.14.1", "dotenv": "^16.4.5", @@ -33,6 +35,7 @@ "swagger-ui-express": "^5.0.1" }, "devDependencies": { + "@types/bcryptjs": "^2.4.6", "@types/node": "^18.0.0", "ts-node-dev": "^2.0.0", "typescript": "^5.0.0" diff --git a/src/app.module.ts b/src/app.module.ts index 2529a61..c824733 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -4,9 +4,10 @@ import { AppService } from './app.service'; import { AiModule } from './ai/ai.module'; import { DatabaseModule } from './database/database.module'; import { PlatformModule } from './platform/platform.module'; +import { AuthModule } from './auth/auth.module'; @Module({ - imports: [DatabaseModule, AiModule, PlatformModule], + imports: [DatabaseModule, AiModule, PlatformModule, AuthModule], controllers: [AppController], providers: [AppService], }) diff --git a/src/auth/auth.controller.ts b/src/auth/auth.controller.ts new file mode 100644 index 0000000..7b0f6f8 --- /dev/null +++ b/src/auth/auth.controller.ts @@ -0,0 +1,25 @@ +import { Controller, Post, Body, HttpCode, HttpStatus } from '@nestjs/common'; +import { AuthService } from './auth.service'; + +@Controller('auth') +export class AuthController { + constructor(private authService: AuthService) {} + + @HttpCode(HttpStatus.OK) + @Post('login') + login(@Body() signInDto: Record) { + return this.authService.login(signInDto.email, signInDto.password); + } + + @HttpCode(HttpStatus.CREATED) + @Post('register') + register(@Body() signUpDto: Record) { + return this.authService.register(signUpDto.name, signUpDto.email, signUpDto.password); + } + + @HttpCode(HttpStatus.OK) + @Post('forgot-password') + forgotPassword(@Body() resetDto: Record) { + return this.authService.forgotPassword(resetDto.email); + } +} diff --git a/src/auth/auth.module.ts b/src/auth/auth.module.ts new file mode 100644 index 0000000..e258a1c --- /dev/null +++ b/src/auth/auth.module.ts @@ -0,0 +1,18 @@ +import { Module } from '@nestjs/common'; +import { JwtModule } from '@nestjs/jwt'; +import { AuthController } from './auth.controller'; +import { AuthService } from './auth.service'; + +@Module({ + imports: [ + JwtModule.register({ + global: true, + secret: 'EL_SECRETO_SUPER_SEGURO_DE_SAPIENTLAB_123', // En producción debería ir en un archivo .env + signOptions: { expiresIn: '1d' }, + }), + ], + controllers: [AuthController], + providers: [AuthService], + exports: [AuthService], +}) +export class AuthModule {} \ No newline at end of file diff --git a/src/auth/auth.service.ts b/src/auth/auth.service.ts new file mode 100644 index 0000000..e711c32 --- /dev/null +++ b/src/auth/auth.service.ts @@ -0,0 +1,69 @@ +import { Injectable, UnauthorizedException, BadRequestException } from '@nestjs/common'; +import { JwtService } from '@nestjs/jwt'; +import * as bcrypt from 'bcryptjs'; + +// Base de datos en memoria para el prototipo rápido (hasta que se conecte a MySQL/SQLite) +const users = []; + +@Injectable() +export class AuthService { + constructor(private jwtService: JwtService) {} + + async register(name: string, email: string, passwordString: string) { + const userExists = users.find(u => u.email === email); + if (userExists) { + throw new BadRequestException({ message: 'El correo ya está registrado' }); + } + + const hashedPassword = await bcrypt.hash(passwordString, 10); + const newUser = { + id: Date.now().toString(), + name, + email, + password: hashedPassword + }; + + users.push(newUser); + + const payload = { sub: newUser.id, email: newUser.email, name: newUser.name }; + const token = await this.jwtService.signAsync(payload); + + return { + message: 'Registro exitoso', + token, + user: { name: newUser.name, email: newUser.email } + }; + } + + async login(email: string, passwordString: string) { + const user = users.find(u => u.email === email); + if (!user) { + throw new UnauthorizedException({ message: 'Credenciales inválidas' }); + } + + const isMatch = await bcrypt.compare(passwordString, user.password); + if (!isMatch) { + throw new UnauthorizedException({ message: 'Credenciales inválidas' }); + } + + const payload = { sub: user.id, email: user.email, name: user.name }; + const token = await this.jwtService.signAsync(payload); + + return { + message: 'Login exitoso', + token, + user: { name: user.name, email: user.email } + }; + } + + async forgotPassword(email: string) { + const user = users.find(u => u.email === email); + if (!user) { + // Por seguridad, siempre devolvemos "éxito" aunque no exista el correo. + return { success: true, message: 'Si el correo existe, te hemos enviado un enlace.' }; + } + + // Aquí iría la lógica de NodeMailer o Resend para enviar el correo real + return { success: true, message: 'Si el correo existe, te hemos enviado un enlace.' }; + } +} diff --git a/ts_errors.txt b/ts_errors.txt new file mode 100644 index 0000000..e69de29 From 7ed3c98e4e15786c157efb808b44e8d05c56e34a Mon Sep 17 00:00:00 2001 From: AlexVillaTor Date: Sat, 21 Mar 2026 22:45:12 -0400 Subject: [PATCH 2/2] feat(ai): endpoints listos para integracion de Vision y prompts --- package-lock.json | 99 +++++++++++++++++++++++++++++++++++++++ package.json | 1 + src/ai/ai.controller.ts | 12 ++++- src/ai/ai.service.ts | 16 +++++++ src/auth/auth.service.ts | 60 ++++++++++++++++-------- temp_db.ts | Bin 0 -> 55348 bytes 6 files changed, 168 insertions(+), 20 deletions(-) create mode 100644 temp_db.ts diff --git a/package-lock.json b/package-lock.json index 7b919bc..a6809cd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,6 +33,7 @@ }, "devDependencies": { "@types/bcryptjs": "^2.4.6", + "@types/multer": "^2.1.0", "@types/node": "^18.0.0", "ts-node-dev": "^2.0.0", "typescript": "^5.0.0" @@ -590,6 +591,59 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/express": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz", + "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^5.0.0", + "@types/serve-static": "^2" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.1.tgz", + "integrity": "sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/jsonwebtoken": { "version": "9.0.10", "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz", @@ -606,6 +660,16 @@ "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", "license": "MIT" }, + "node_modules/@types/multer": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/multer/-/multer-2.1.0.tgz", + "integrity": "sha512-zYZb0+nJhOHtPpGDb3vqPjwpdeGlGC157VpkqNQL+UU2qwoacoQ7MpsAmUptI/0Oa127X32JzWDqQVEXp2RcIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express": "*" + } + }, "node_modules/@types/node": { "version": "18.19.130", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz", @@ -615,6 +679,41 @@ "undici-types": "~5.26.4" } }, + "node_modules/@types/qs": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.0.tgz", + "integrity": "sha512-JawvT8iBVWpzTrz3EGw9BTQFg3BQNmwERdKE22vlTxawwtbyUSlMppvZYKLZzB5zgACXdXxbD3m1bXaMqP/9ow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*" + } + }, "node_modules/@types/strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/strip-bom/-/strip-bom-3.0.0.tgz", diff --git a/package.json b/package.json index 386fbf4..4ff4c99 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ }, "devDependencies": { "@types/bcryptjs": "^2.4.6", + "@types/multer": "^2.1.0", "@types/node": "^18.0.0", "ts-node-dev": "^2.0.0", "typescript": "^5.0.0" diff --git a/src/ai/ai.controller.ts b/src/ai/ai.controller.ts index 48c3e2e..456dc0e 100644 --- a/src/ai/ai.controller.ts +++ b/src/ai/ai.controller.ts @@ -1,4 +1,5 @@ -import { Body, Controller, Get, Post } from '@nestjs/common'; +import { Body, Controller, Get, Post, UploadedFiles, UseInterceptors } from '@nestjs/common'; +import { FilesInterceptor } from '@nestjs/platform-express'; import { AiService } from './ai.service'; import { AgentRunDto, @@ -38,6 +39,15 @@ export class AiController { return this.aiService.providerStatus(); } + @Post('documents/upload') + @UseInterceptors(FilesInterceptor('files')) + async uploadProjectDocuments( + @UploadedFiles() files: Array, + @Body('projectId') projectId: string, + ) { + return this.aiService.processUploadedDocuments(files, projectId); + } + @Post('protocol/interpret') interpretProtocol(@Body() dto: ProtocolInterpretationDto) { return this.aiService.interpretProtocol(dto); diff --git a/src/ai/ai.service.ts b/src/ai/ai.service.ts index 407b63b..88aea4b 100644 --- a/src/ai/ai.service.ts +++ b/src/ai/ai.service.ts @@ -39,6 +39,22 @@ export class AiService { }; } + async processUploadedDocuments(files: Array, projectId: string) { + if (!files || files.length === 0) { + throw new BadRequestException('No se han proporcionado archivos válidos'); + } + + // Aquí en el futuro puedes parsear PDFs o crear Embeddings de IA reales + // Por el momento devolveremos la lista de nombres como prueba de guardado exitoso + const fileNames = files.map(file => file.originalname); + return { + success: true, + message: `¡Se subieron ${fileNames.length} archivos para procesarlos!`, + project: projectId, + processedFiles: fileNames + }; + } + async interpretProtocol(dto: ProtocolInterpretationDto) { const system = 'You are a Responsible AI lab co-pilot. Always return valid JSON with keys: summary, checklist (array with title, action, cautions, riskLevel), instrumentation, hazards, compliance. Flag any disallowed content.'; diff --git a/src/auth/auth.service.ts b/src/auth/auth.service.ts index e711c32..e91a1cf 100644 --- a/src/auth/auth.service.ts +++ b/src/auth/auth.service.ts @@ -1,42 +1,60 @@ -import { Injectable, UnauthorizedException, BadRequestException } from '@nestjs/common'; +import { Injectable, UnauthorizedException, BadRequestException, InternalServerErrorException } from '@nestjs/common'; import { JwtService } from '@nestjs/jwt'; import * as bcrypt from 'bcryptjs'; - -// Base de datos en memoria para el prototipo rápido (hasta que se conecte a MySQL/SQLite) -const users = []; +import { DatabaseService } from '../database/database.service'; @Injectable() export class AuthService { - constructor(private jwtService: JwtService) {} + constructor( + private jwtService: JwtService, + private databaseService: DatabaseService + ) {} async register(name: string, email: string, passwordString: string) { - const userExists = users.find(u => u.email === email); - if (userExists) { + // Validar si el usuario existe + const existingUser = await this.databaseService.queryOne( + 'SELECT id FROM users WHERE email = ?', + [email] + ); + + if (existingUser) { throw new BadRequestException({ message: 'El correo ya está registrado' }); } const hashedPassword = await bcrypt.hash(passwordString, 10); - const newUser = { - id: Date.now().toString(), - name, - email, - password: hashedPassword - }; + const now = this.databaseService.getDialect() === 'mysql' + ? new Date().toISOString().slice(0, 19).replace('T', ' ') + : new Date().toISOString(); - users.push(newUser); + // Insertar nuevo usuario + let insertId; + try { + const result = await this.databaseService.execute( + 'INSERT INTO users (name, email, password, created_at) VALUES (?, ?, ?, ?)', + [name, email, hashedPassword, now] + ); + insertId = result.insertId; + } catch (error) { + throw new InternalServerErrorException({ message: 'Error al crear el usuario en la base de datos' }); + } - const payload = { sub: newUser.id, email: newUser.email, name: newUser.name }; + const payload = { sub: insertId, email, name }; const token = await this.jwtService.signAsync(payload); return { message: 'Registro exitoso', token, - user: { name: newUser.name, email: newUser.email } + user: { id: insertId, name, email } }; } async login(email: string, passwordString: string) { - const user = users.find(u => u.email === email); + // Buscar usuario en base de datos + const user = await this.databaseService.queryOne( + 'SELECT * FROM users WHERE email = ?', + [email] + ); + if (!user) { throw new UnauthorizedException({ message: 'Credenciales inválidas' }); } @@ -52,12 +70,16 @@ export class AuthService { return { message: 'Login exitoso', token, - user: { name: user.name, email: user.email } + user: { id: user.id, name: user.name, email: user.email } }; } async forgotPassword(email: string) { - const user = users.find(u => u.email === email); + const user = await this.databaseService.queryOne( + 'SELECT id FROM users WHERE email = ?', + [email] + ); + if (!user) { // Por seguridad, siempre devolvemos "éxito" aunque no exista el correo. return { success: true, message: 'Si el correo existe, te hemos enviado un enlace.' }; diff --git a/temp_db.ts b/temp_db.ts new file mode 100644 index 0000000000000000000000000000000000000000..bc5bec36daee86442b9b82bec948586ba47a781d GIT binary patch literal 55348 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 Zptq}nVoE*+aW_Bi{?m{dNWBdG{tvIs_(T8z literal 0 HcmV?d00001